@@ -0,0 +1,514 @@
1//
2// tests for clone, join, futexes and the thread library.
3// each test prints "threadtest: <name>: OK" or "... FAIL".
4// only the first thread prints; other threads report through
5// their exit status or shared memory.
6//
7
8#include "kernel/types.h"
9#include "kernel/riscv.h"
10#include "user/user.h"
11
12#define NT 7 // threads besides the first: the kernel allows 8 in all
13
14int failed;
15
16void
17result(char *name, int ok) 18{
19 printf("threadtest: %s: %s\n", name, ok ? "OK" : "FAIL"); 20 if (!ok)
21 failed = 1;
22}
23
24// count free pages by allocating them all with sbrk.
25// only in a single-threaded process: sbrk(-n) needs that.
26int
28{
30 int n = 0;
31
33 n++;
35 return n;
36}
37
38// a starting gate: threads sleep until gateopen() lets them
39// all go at once, so that they run at the same time.
40int gate;
41
42void
43gatewait(void)
44{
45 while (__atomic_load_n(&gate, __ATOMIC_ACQUIRE) == 0)
46 futex_wait(&gate, 0);
47}
48
49void
50gateopen(void)
51{
52 __atomic_store_n(&gate, 1, __ATOMIC_RELEASE);
53 futex_wake(&gate, NT);
54}
55
56// start n threads running fn(i), i = 0..n-1; tids[i] gets
57// each id. return 1 if all started.
58int
59startall(int (*fn)(void *), int n, int *tids)
60{
61 int ok = 1;
62
63 gate = 0;
64 for (int i = 0; i < n; i++) {
65 tids[i] = thread_create(fn, (void *)(uint64)i);
66 if (tids[i] < 0)
67 ok = 0;
68 }
69 gateopen();
70 return ok;
71}
72
73// join every thread; return 1 if all exited with status 0.
74int
75joinall(void)
76{
77 int st, ok = 1;
78
79 while (thread_join(&st) > 0)
80 if (st != 0)
81 ok = 0;
82 return ok;
83}
84
85// create and join: exit statuses come back to the creator.
86int
87exitfn(void *arg)
88{
89 gatewait();
90 return 100 + (int)(uint64)arg;
91}
92
93void
94jointest(void)
95{
96 int tids[NT], seen[NT] = {0}, st, tid, ok;
97
98 ok = startall(exitfn, NT, tids);
99 // all 8 trapframe slots are taken now (or soon by zombies).
100 if (thread_create(exitfn, 0) >= 0)
101 ok = 0;
102 for (int n = 0; n < NT; n++) {
103 if ((tid = thread_join(&st)) < 0) {
104 ok = 0;
105 break;
106 }
107 for (int i = 0; i < NT; i++)
108 if (tid == tids[i] && st == 100 + i)
109 seen[i]++;
110 }
111 for (int i = 0; i < NT; i++)
112 if (seen[i] != 1)
113 ok = 0;
114 if (thread_join(&st) != -1) // no threads left
115 ok = 0;
116 result("create and join", ok);
117}
118
119// every thread traps into the kernel thousands of times; its
120// registers and system-call results must stay its own.
121int
122regsfn(void *arg)
123{
125 volatile uint64 a = (uint64)arg, b = 3 * a, c = 7 * a;
126
127 gatewait();
128 for (int i = 0; i < 2000; i++) {
130 return 1;
131 a += 1;
132 b += 3;
133 c += 7;
134 }
135 if (a != (uint64)arg + 2000 || b != 3 * (uint64)arg + 6000 ||
136 c != 7 * (uint64)arg + 14000)
137 return 2;
138 return 0;
139}
140
141void
142regstest(void)
143{
144 int tids[NT];
145 int ok = startall(regsfn, NT, tids);
146 result("registers", joinall() && ok);
147}
148
149// a shared counter, incremented slowly (read, wait, write) by
150// all threads: under the futex mutex, and without it.
151#define NINC 20000
152struct mutex m;
153volatile int counter;
154
155void
156slowinc(void)
157{
158 int c = counter;
159 for (volatile int j = 0; j < 200; j++)
160 ;
161 counter = c + 1;
162}
163
164int
165countfn(void *arg)
166{
167 gatewait();
168 for (int i = 0; i < NINC; i++) {
169 mutex_lock(&m);
170 slowinc();
171 mutex_unlock(&m);
172 }
173 return 0;
174}
175
176int
177racefn(void *arg)
178{
179 gatewait();
180 for (int i = 0; i < NINC; i++)
181 slowinc();
182 return 0;
183}
184
185void
186mutextest(void)
187{
188 int tids[NT], ok;
189
190 counter = 0;
191 ok = startall(countfn, NT, tids);
192 ok = joinall() && ok;
193 printf("threadtest: mutex: %d of %d\n", counter, NT * NINC); 194 result("mutex", ok && counter == NT * NINC);
195
196 // the same without the mutex: updates get lost whenever
197 // threads really run at once. printed, not checked.
198 counter = 0;
199 startall(racefn, NT, tids);
200 joinall();
201 printf("threadtest: without the mutex: %d of %d (not checked)\n", counter, 202 NT * NINC);
203}
204
205// two threads hand a turn back and forth through a futex,
206// each sleeping until the other wakes it: a single lost
207// wakeup leaves both asleep for ever.
208#define NPING 5000
209int turn;
210
211int
212pingfn(void *arg)
213{
214 int me = (int)(uint64)arg;
215
216 for (int i = 0; i < NPING; i++) {
217 while (__atomic_load_n(&turn, __ATOMIC_ACQUIRE) != me)
218 futex_wait(&turn, 1 - me);
219 __atomic_store_n(&turn, 1 - me, __ATOMIC_RELEASE);
220 futex_wake(&turn, 1);
221 }
222 return 0;
223}
224
225void
226pingtest(void)
227{
228 int ok = 1;
229
230 turn = 0;
231 if (thread_create(pingfn, (void *)0) < 0 ||
232 thread_create(pingfn, (void *)1) < 0)
233 ok = 0;
234 result("ping-pong", joinall() && ok);
235}
236
237// sbrk from many threads at once: every call must get its
238// own memory.
239#define NSBRK 50
240char *got[NT][NSBRK];
241
242int
243sbrkfn(void *arg)
244{
245 int me = (int)(uint64)arg;
246
247 gatewait();
248 for (int k = 0; k < NSBRK; k++) {
251 return 1;
253 got[me][k] = p;
254 }
255 return 0;
256}
257
258void
259sbrktest(void)
260{
261 int tids[NT], ok;
262 char *before = sbrk(0); 263
264 ok = startall(sbrkfn, NT, tids);
265 ok = joinall() && ok;
267 ok = 0;
268 for (int i = 0; ok && i < NT; i++)
269 for (int k = 0; k < NSBRK; k++)
270 for (int j = 0; j < PGSIZE; j++) 271 if (got[i][k][j] != 'a' + i) {
272 ok = 0;
273 break;
274 }
275 result("sbrk", ok);
276}
277
278// all threads touch the same lazily allocated pages at once:
279// each page faults on several harts at the same moment.
280#define NLAZY 512
281char *lazy;
282
283int
284lazyfn(void *arg)
285{
286 int me = (int)(uint64)arg;
287
288 gatewait();
289 for (int j = 0; j < NLAZY; j++)
290 lazy[j * PGSIZE + me] = 'a' + me; 291 return 0;
292}
293
294void
295lazytest(void)
296{
297 int tids[NT], ok;
298
300 ok = startall(lazyfn, NT, tids);
301 ok = joinall() && ok;
302 for (int j = 0; j < NLAZY; j++)
303 for (int i = 0; i < NT; i++)
304 if (lazy[j * PGSIZE + i] != 'a' + i) 305 ok = 0;
306 result("lazy", ok);
307}
308
309// while another thread exists, the address space may not
310// shrink or be replaced, and an eager sbrk that runs out of
311// memory must give up without mapping anything.
312int waitword;
313
314int
315sleepfn(void *arg)
316{
317 while (__atomic_load_n(&waitword, __ATOMIC_ACQUIRE) == 0)
318 futex_wait(&waitword, 0);
319 return 0;
320}
321
322void
323sharedtest(void)
324{
325 char *argv[] = {"echo", "exec", "succeeded", 0}; 326 int ok = 1;
327
328 waitword = 0;
329 if (thread_create(sleepfn, 0) < 0)
330 ok = 0;
333 ok = 0;
335 ok = 0;
337 ok = 0;
338 __atomic_store_n(&waitword, 1, __ATOMIC_RELEASE);
339 futex_wake(&waitword, 1);
340 ok = joinall() && ok;
341 // alone again: shrinking works.
343 ok = 0;
344 result("shared memory only grows", ok);
345}
346
347// join() reaps threads, wait() reaps child processes.
348int
349slowfn(void *arg)
350{
352 return 7;
353}
354
355void
356waitjointest(void)
357{
358 int pid, tid, st, ok = 1; 359
363 tid = thread_create(slowfn, 0);
364 if (pid < 0 || tid < 0) 365 ok = 0;
366 if (thread_join(&st) != tid || st != 7)
367 ok = 0;
368 if (wait(&st) != pid || st != 42) 369 ok = 0;
370 if (thread_join(&st) != -1 || wait(&st) != -1) 371 ok = 0;
372 result("wait and join", ok);
373}
374
375// the first thread's exit, or a kill, ends every thread.
376int
377spinfn(void *arg)
378{
379 for (;;)
380 ;
381}
382
383int
384blockfn(void *arg)
385{
386 int never = 0;
387 for (;;)
388 futex_wait(&never, 0);
389}
390
391int
392freeagain(int free0)
393{
394 // the killed threads are reaped by init, a moment later.
395 for (int i = 0; i < 50; i++) {
397 return 1;
399 }
400 return 0;
401}
402
403void
404exittest(void)
405{
407
410 thread_create(spinfn, 0);
411 thread_create(spinfn, 0);
412 thread_create(blockfn, 0);
415 }
416 if (wait(&st) != pid || st != 5) 417 ok = 0;
418 result("exit ends all threads", ok && freeagain(free0));
419
420 ok = 1;
423 thread_create(spinfn, 0);
424 thread_create(blockfn, 0);
425 thread_create(blockfn, 0);
426 joinall(); // never returns
428 }
431 if (wait(&st) != pid || st != -1) 432 ok = 0;
433 result("kill ends all threads", ok && freeagain(free0));
434}
435
436// many rounds of everything at once, on all harts: mutex,
437// sbrk, lazy faults, and fork + wait from inside threads.
438#define ROUNDS 20
439
440int
441stressfn(void *arg)
442{
443 int me = (int)(uint64)arg, pid, st; 444
445 gatewait();
446 for (int i = 0; i < 300; i++) {
447 mutex_lock(&m);
448 counter++;
449 mutex_unlock(&m);
450 }
453 return 1;
459 return 2;
460 return 0;
461}
462
463void
464stresstest(void)
465{
466 int tids[NT], ok = 1;
467
468 counter = 0;
469 for (int r = 0; r < ROUNDS; r++) {
470 if (!startall(stressfn, NT, tids))
471 ok = 0;
472 if (!joinall())
473 ok = 0;
474 }
475 result("stress", ok && counter == ROUNDS * NT * 300);
476}
477
478int
480{
481 int free0, free1, pid, st; 482
484
485 // run the in-process tests in a child, so that the memory
486 // they leave allocated is freed before the second count.
489 jointest();
490 regstest();
491 mutextest();
492 pingtest();
493 sbrktest();
494 lazytest();
495 sharedtest();
496 waitjointest();
497 stresstest();
499 }
500 if (wait(&st) != pid || st != 0) 501 failed = 1;
502 if (st == -1)
503 printf("threadtest: the process running the tests was killed\n"); 504
505 exittest();
506
507 if (!freeagain(free0))
508 failed = 1;
510 printf("threadtest: free pages %d before, %d after\n", free0, free1); 511 result("no leaks", free1 == free0);
512 printf("threadtest: %s\n", failed ? "SOME TESTS FAILED" : "ALL OK"); 514}