The Pedigree Project 0.1
scheduling-contract-test/fixture.c
1#define _GNU_SOURCE
2#include <poll.h>
3#include <signal.h>
4#include <string.h>
5#include <time.h>
6#include <unistd.h>
7
8#include "contract.h"
9#include <sys/syscall.h>
10#include <sys/wait.h>
11
12cpu_set_t sc_allowed;
13int sc_cpus[CPU_SETSIZE], sc_count;
14size_t sc_bytes, sc_page;
15_Thread_local unsigned sc_tls;
16
17int sc_init(int expected) {
18 sc_page = (size_t)sysconf(_SC_PAGESIZE);
19 long bytes = syscall(SYS_sched_getaffinity, 0, sizeof(sc_allowed), &sc_allowed);
20 if (bytes <= 0 || bytes > (long)sizeof(sc_allowed) || bytes % sizeof(long)) {
21 fprintf(stderr, "raw affinity size=%ld errno=%d\n", bytes, errno);
22 return -1;
23 }
24 sc_bytes = (size_t)bytes;
25 if (sched_getaffinity(0, sizeof(sc_allowed), &sc_allowed))
26 return -1;
27 for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu)
28 if (CPU_ISSET(cpu, &sc_allowed))
29 sc_cpus[sc_count++] = cpu;
30 if (!sc_count || (expected && expected != sc_count)) {
31 fprintf(stderr, "topology expected=%d allowed=%d\n", expected, sc_count);
32 return -1;
33 }
34 printf("SCHEDULING-CONTRACT: topology cpus=%d mask-bytes=%zu\n", sc_count, sc_bytes);
35 if (sc_count == 1)
36 puts("SCHEDULING-CONTRACT: SKIP cross-CPU placement (one allowed CPU)");
37 return 0;
38}
39int sc_pin(pid_t tid, int cpu) {
40 cpu_set_t mask;
41 CPU_ZERO(&mask);
42 CPU_SET(cpu, &mask);
43 return sched_setaffinity(tid, sizeof(mask), &mask);
44}
45int sc_mask(pid_t tid, int cpu) {
46 cpu_set_t mask;
47 return sched_getaffinity(tid, sizeof(mask), &mask) || CPU_COUNT(&mask) != 1 ||
48 !CPU_ISSET(cpu, &mask)
49 ? -1
50 : 0;
51}
52int sc_sample(int cpu) {
53 unsigned actual = UINT32_MAX, node = UINT32_MAX;
54 if (syscall(SYS_getcpu, &actual, &node, NULL) || actual != (unsigned)cpu || node ||
55 sched_getcpu() != cpu) {
56 fprintf(stderr, "CPU expected=%d actual=%u node=%u errno=%d\n", cpu, actual, node, errno);
57 return -1;
58 }
59 return 0;
60}
61int sc_register_syscall(long number, long a, long b, long c, long* result) {
62#if defined(__x86_64__)
63 const uint64_t input[2] = {UINT64_C(0x729bd064a35ecf18), UINT64_C(0xa148f7328d9605eb)};
64 uint64_t output[2] = {};
65 const long double floating = 0x1.23456789abcdefp+19L;
66 long double restored = 0;
67 long value = number;
68 // Keep live registers across the actual syscall, rather than C ABI spills.
69 __asm__ volatile(
70 "movdqu %[input], %%xmm7\n\t"
71 "fldt %[floating]\n\t"
72 "syscall\n\t"
73 "movdqu %%xmm7, %[output]\n\t"
74 "fstpt %[restored]"
75 : "+a"(value), [output] "=m"(output), [restored] "=m"(restored)
76 : "D"(a), "S"(b), "d"(c), [input] "m"(input), [floating] "m"(floating)
77 : "rcx", "r11", "xmm7", "st", "memory", "cc");
78 *result = value;
79 return memcmp(input, output, sizeof(input)) || memcmp(&floating, &restored, 10) ? -1 : 0;
80#else
81 errno = ENOTSUP;
82 return -1;
83#endif
84}
85int64_t sc_now(void) {
86 struct timespec now;
87 return clock_gettime(CLOCK_MONOTONIC, &now) ? -1 : (int64_t)now.tv_sec * 1000000000 + now.tv_nsec;
88}
89int sc_wait(atomic_uint* value, unsigned expected) {
90 int64_t now = sc_now(), end = now + INT64_C(10000000000);
91 while (now >= 0 && now < end) {
92 if (atomic_load_explicit(value, memory_order_acquire) == expected)
93 return 0;
94 sched_yield();
95 now = sc_now();
96 }
97 fprintf(stderr, "generation timeout expected=%u actual=%u\n", expected,
98 atomic_load_explicit(value, memory_order_acquire));
99 return -1;
100}
101int sc_wait_for_task_retirement(pid_t tid, const struct timespec* expected_interval) {
102 // The clear-TID wake can precede removal from numeric task lookup.
103 int64_t now = sc_now(), end = now + INT64_C(10000000000);
104 while (now >= 0 && now < end) {
105 struct timespec value;
106 errno = 0;
107 int result = sched_rr_get_interval(tid, &value);
108 if (result == -1)
109 return errno == ESRCH ? 0 : -1;
110 if (result != 0 || value.tv_sec != expected_interval->tv_sec ||
111 value.tv_nsec != expected_interval->tv_nsec) {
112 fprintf(stderr, "retiring task %d returned inconsistent interval\n", tid);
113 return -1;
114 }
115 sched_yield();
116 now = sc_now();
117 }
118 if (now >= 0)
119 errno = ETIMEDOUT;
120 fprintf(stderr, "task retirement timeout tid=%d errno=%d\n", tid, errno);
121 return -1;
122}
123int sc_read(int fd, void* buffer, size_t length) {
124 unsigned char* bytes = buffer;
125 while (length) {
126 struct pollfd ready = {.fd = fd, .events = POLLIN};
127 int count;
128 do
129 count = poll(&ready, 1, 10000);
130 while (count < 0 && errno == EINTR);
131 if (count <= 0)
132 return -1;
133 ssize_t n = read(fd, bytes, length);
134 if (n < 0 && errno == EINTR)
135 continue;
136 if (n <= 0)
137 return -1;
138 bytes += n;
139 length -= (size_t)n;
140 }
141 return 0;
142}
143int sc_write(int fd, const void* buffer, size_t length) {
144 const unsigned char* bytes = buffer;
145 while (length) {
146 ssize_t n = write(fd, bytes, length);
147 if (n < 0 && errno == EINTR)
148 continue;
149 if (n <= 0)
150 return -1;
151 bytes += n;
152 length -= (size_t)n;
153 }
154 return 0;
155}
156int sc_send(int fd, char value) {
157 return sc_write(fd, &value, 1);
158}
159int sc_receive(int fd, char value) {
160 char actual;
161 return sc_read(fd, &actual, 1) || actual != value ? -1 : 0;
162}
163int sc_reap(pid_t pid, int milliseconds) {
164 int64_t now = sc_now(), end = now + (int64_t)milliseconds * 1000000;
165 while (now >= 0 && now < end) {
166 int status;
167 pid_t result = waitpid(pid, &status, WNOHANG);
168 if (result == pid)
169 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
170 if (result < 0 && errno != EINTR)
171 return -1;
172 struct timespec delay = {0, 5000000};
173 nanosleep(&delay, NULL);
174 now = sc_now();
175 }
176 kill(pid, SIGKILL);
177 while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) {
178 }
179 return -1;
180}
181int sc_spawn(struct sc_peer* peer, int (*body)(int, int, void*), void* argument) {
182 int command[2], report[2];
183 if (pipe(command))
184 return -1;
185 if (pipe(report)) {
186 close(command[0]);
187 close(command[1]);
188 return -1;
189 }
190 pid_t pid = fork();
191 if (!pid) {
192 close(command[1]);
193 close(report[0]);
194 alarm(40);
195 int result = body(command[0], report[1], argument);
196 fflush(stderr);
197 _exit(result ? 1 : 0);
198 }
199 close(command[0]);
200 close(report[1]);
201 if (pid < 0) {
202 close(command[1]);
203 close(report[0]);
204 return -1;
205 }
206 *peer = (struct sc_peer){pid, command[1], report[0]};
207 return 0;
208}
209int sc_join(struct sc_peer* peer) {
210 int result = sc_reap(peer->pid, 15000);
211 if (result)
212 fprintf(stderr, "scheduling child pid=%d status=%d\n", peer->pid, result);
213 peer->pid = -1;
214 sc_cleanup(peer);
215 return result;
216}
217void sc_cleanup(struct sc_peer* peer) {
218 if (peer->pid > 0) {
219 kill(peer->pid, SIGKILL);
220 sc_reap(peer->pid, 1000);
221 }
222 if (peer->command >= 0)
223 close(peer->command);
224 if (peer->report >= 0)
225 close(peer->report);
226 *peer = (struct sc_peer)SC_PEER_INITIALIZER;
227}