@@ -0,0 +1,660 @@
1// Tests for mmap and munmap. Prints one line per check;
2// "mmaptest NAME" runs only that check.
3
4#include "kernel/param.h"
5#include "kernel/types.h"
6#include "kernel/stat.h"
7#include "kernel/riscv.h"
8#include "kernel/fcntl.h"
9#include "user/user.h"
10
11int failed;
12char buf[PGSIZE]; // the user stack is one page: keep big buffers off it 13
14void
16{
17 printf("mmaptest: %s: FAIL (%s)\n", test, what); 18 failed = 1;
19}
20
21// The byte at offset i of every test file.
22char
23fbyte(uint i)
24{
25 return (i % 251) ^ (i / PGSIZE); 26}
27
28// Create name with size bytes of fbyte().
29void
31{
33 uint i, j, n;
34 int fd;
35
39 }
40 for (i = 0; i < size; i += n) { 42 for (j = 0; j < n; j++)
43 buf[j] = fbyte(i + j); 47 }
48 }
50}
51
52// Read n bytes at offset off of name into buf.
53int
55{
56 static char skip[512];
57 int fd, k;
58
60 return -1;
62 k = off < sizeof(skip) ? off : sizeof(skip); 63 if (read(fd, skip, k) != k) { 65 return -1;
66 }
68 }
71 return k;
72}
73
74int
76{
78
80 return -1;
82}
83
84// Free pages, counted the way usertests counts them.
85int
87{
88 int n = 0;
89 uint64 sz0 = (uint64)sbrk(0); 90
92 n++;
94 return n;
95}
96
97// Run f in a child; return the child's exit status.
98int
99inchild(void (*f)(char *), char *arg)
100{
102
104 printf("mmaptest: fork failed\n"); 106 }
108 f(arg);
110 }
112 return -100;
113 return xs;
114}
115
116void
117store(char *a)
118{
119 *(volatile char *)a = 1;
120}
121
122void
123load(char *a)
124{
125 printf("mmaptest: loaded %d: should have been killed\n", 126 *(volatile char *)a);
127}
128
129// Map a file read-only, close it, read it through memory.
130void
131readtest(void)
132{
133 char *t = "read";
135 int fd, f0, f1, bad = 0; 136 char *m;
137
138 makefile("mm.read", size); 141 return;
142 }
143 m = mmap(0, 3 * PGSIZE, PROT_READ, MAP_PRIVATE, fd, 0); 144 close(fd); // the mapping keeps its own reference 145 if (m == MAP_FAILED) {
147 return;
148 }
149 if ((uint64)m % PGSIZE != 0 || (uint64)m < (uint64)sbrk(0)) 151
152 countfree(); // warm-up: the heap's page-table pages 157 printf("mmaptest: read: touching 1 of 3 mapped pages took %d free pages\n", 158 f0 - f1);
159 if (f0 - f1 != 1)
161
162 for (i = 0; i < 3 * PGSIZE; i++) 163 if (m[i] != (i < size ? fbyte(i) : 0)) 167 if (munmap(m, 3 * PGSIZE) != 0) 169 if (inchild(load, m) != -1)
170 fail(t, "still mapped after munmap"); 172}
173
174// Stores through a shared mapping reach the file at munmap; the
175// file never grows.
176void
177sharedtest(void)
178{
179 char *t = "shared";
182 char *m;
183
184 makefile("mm.shared", size); 187 return;
188 }
189 m = mmap(0, 3 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 191 if (m == MAP_FAILED) {
193 return;
194 }
195 strcpy(m + 10, "hello"); // page 0 196 m[2 * PGSIZE + 50] = 'X'; // page 2, inside the file 197 m[2 * PGSIZE + 3000] = 'Y'; // page 2, past the end of the file 198 if (munmap(m, 3 * PGSIZE) != 0) { 200 return;
201 }
202
203 if (filesize("mm.shared") != size) 204 fail(t, "the file changed size"); 207 fail(t, "page 0 not written back"); 208 for (i = 0; i < PGSIZE; i++) 209 if (i < 10 || i > 15)
210 bad += buf[i] != fbyte(i); 212 fail(t, "read page 1"); 213 for (i = 0; i < PGSIZE; i++) 216 fail(t, "page 2 not written back"); 217 for (i = 0; i < 100; i++)
218 if (i != 50)
221 fail(t, "other bytes changed"); 223}
224
225// Private stores never reach the file; prot is enforced.
226void
227privatetest(void)
228{
229 char *t = "private";
230 int fd, pfd[2], i, bad = 0; 231 char *m, *r;
232
233 makefile("mm.priv", PGSIZE); 236 return;
237 }
238 if (mmap(0, PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) != 239 MAP_FAILED)
240 fail(t, "shared writable map of a read-only file"); 241 m = mmap(0, PGSIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); 242 r = mmap(0, PGSIZE, PROT_READ, MAP_SHARED, fd, 0); 244 if (m == MAP_FAILED || r == MAP_FAILED) {
246 return;
247 }
248 for (i = 0; i < PGSIZE; i++) 249 m[i] = 'P';
250 if (m[100] != 'P' || r[100] != fbyte(100))
252 if (inchild(store, r) != -1)
253 fail(t, "store to a read-only mapping"); 255 fail(t, "read() into a read-only mapping"); 261 for (i = 0; i < PGSIZE; i++) 262 bad += buf[i] != fbyte(i); 264 fail(t, "a private store reached the file"); 265
267 if (mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, pfd[0], 0) != MAP_FAILED) 268 fail(t, "mapped a pipe"); 272}
273
274// munmap of a part at the start or at the end of a mapping.
275void
276partialtest(void)
277{
278 char *t = "partial";
279 int fd, i;
280 char *m;
281
282 makefile("mm.part", 4 * PGSIZE); 285 return;
286 }
287 m = mmap(0, 4 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 289 if (m == MAP_FAILED) {
291 return;
292 }
293 for (i = 0; i < 4; i++)
296 fail(t, "unmapped a hole in the middle"); 297 if (munmap(m, PGSIZE) != 0) // the first page 298 fail(t, "munmap start"); 299 if (munmap(m + 3 * PGSIZE, PGSIZE) != 0) // the last page 300 fail(t, "munmap end"); 301 if (inchild(load, m) != -1 || inchild(load, m + 3 * PGSIZE) != -1) 302 fail(t, "still mapped after munmap"); 304 fail(t, "the rest lost its contents"); 307 fail(t, "munmap rest"); 308 for (i = 0; i < 4; i++)
309 if (readat("mm.part", i * PGSIZE, buf, 2) != 2 || buf[0] != 'A' + i || 310 buf[1] != (i == 2 ? 'c' : fbyte(i * PGSIZE + 1))) 311 fail(t, "not written back"); 313}
314
315char *forkmap;
316
317void
318forkchild(char *arg)
319{
320 int i;
321
322 // page 1: the parent never touched it.
323 for (i = 0; i < PGSIZE; i++) 327 exit(0); // no munmap: exit writes page 1 back 328}
329
330// A child has its parent's mappings.
331void
333{
334 char *t = "fork";
335
336 makefile("mm.fork", 2 * PGSIZE); 338 forkmap = mmap(0, 2 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 340 if (forkmap == MAP_FAILED) {
342 return;
343 }
344 forkmap[0] = 'P';
345 if (inchild(forkchild, 0) != 0)
347 if (forkmap[PGSIZE] != 'C') // loaded now, after the child's exit 348 fail(t, "the child's store did not reach the file"); 349 if (munmap(forkmap, 2 * PGSIZE) != 0) 351 if (readat("mm.fork", 0, buf, 1) != 1 || buf[0] != 'P' || 352 readat("mm.fork", PGSIZE, buf, 1) != 1 || buf[0] != 'C') 355}
356
357void
358exitchild(char *how)
359{
361 char *m = mmap(0, 2 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 362 char *argv[] = {"mmaptest", "nop", 0}; 363
365 if (m == MAP_FAILED)
367 if (how[0] == 'x') {
368 m[0] = 'x';
370 }
374}
375
376// exit and exec write dirty shared pages back.
377void
378exittest(void)
379{
380 char *t = "exit";
381
382 makefile("mm.exit", 2 * PGSIZE); 383 if (inchild(exitchild, "x") != 0 || inchild(exitchild, "e") != 0)
385 if (readat("mm.exit", 0, buf, 1) != 1 || buf[0] != 'x') 386 fail(t, "exit did not write back"); 387 if (readat("mm.exit", PGSIZE, buf, 1) != 1 || buf[0] != 'e') 388 fail(t, "exec did not write back"); 390}
391
392// The table fills up; nothing leaks.
393void
394manytest(void)
395{
396 char *t = "many";
397 char *m[NVMA + 1];
398 int fd, i, f0, f1;
399
400 makefile("mm.many", PGSIZE); 405 return;
406 }
407 for (i = 0; i < NVMA; i++) {
408 m[i] = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd, 0); 409 if (m[i] == MAP_FAILED || m[i][7] != fbyte(7)) {
411 return;
412 }
413 }
414 if (mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd, 0) != MAP_FAILED) 415 fail(t, "more than NVMA mappings"); 417 for (i = 0; i < NVMA; i++)
418 if (munmap(m[i], PGSIZE) != 0) 420
421 // each round takes a file and a page; if munmap failed to give
422 // them back, open() would run out of files after NFILE rounds.
423 for (i = 0; i < 3 * NFILE; i++) { 425 fail(t, "open failed: files leak"); 426 return;
427 }
428 m[0] = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd, 0); 430 if (m[0] == MAP_FAILED || m[0][9] != fbyte(9) ||
431 munmap(m[0], PGSIZE) != 0) { 433 return;
434 }
435 }
437 if (f1 != f0) {
438 printf("mmaptest: many: %d free pages before, %d after\n", f0, f1); 439 fail(t, "pages leak"); 440 }
441
442 // a mapping keeps an unlinked file alive.
444 m[0] = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd, 0); 447 if (m[0] == MAP_FAILED || m[0][11] != fbyte(11))
448 fail(t, "unlinked file"); 450}
451
452// System calls that copy into or out of unloaded mapped pages
453// while they hold spinlocks.
454void
455lockedtest(void)
456{
457 char *t = "locked";
458 int fd, pfd[2], i, bad = 0; 459 char *m;
460
461 makefile("mm.lock", 3 * PGSIZE); 463 m = mmap(0, 3 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 465 if (m == MAP_FAILED) {
467 return;
468 }
469 // pipewrite copies page 0 in, piperead copies into page 1, both
470 // holding the pipe's spinlock.
476 // kwait copies the status into page 2 holding two spinlocks.
480 if (*(int *)(m + 2 * PGSIZE) != 7) 482 if (munmap(m, 3 * PGSIZE) != 0) { 484 return;
485 }
486 // what the kernel wrote into pages 1 and 2 went back to the file:
487 // copyout marked them dirty.
489 for (i = 0; i < 8; i++)
490 bad += buf[i] != fbyte(i); 494 fail(t, "file contents"); 496}
497
498// read() and write() of a file, with the buffer in an unloaded page
499// of a mapping of that same file.
500void
501selftest(void)
502{
503 char *t = "self";
504 int fd, fd2, i, bad = 0; 505 char *m;
506
507 makefile("mm.self", 3 * PGSIZE); 509 m = mmap(0, 3 * PGSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 510 if (m == MAP_FAILED) {
512 return;
513 }
514 // fileread holds the file's inode lock while it copies into page 1.
517 fail(t, "read into the file's own mapping"); 519 // filewrite holds the inode lock and a transaction while it
520 // copies from page 2; the bytes go to the start of the file.
522 fail(t, "write from the file's own mapping"); 524 if (munmap(m, 3 * PGSIZE) != 0) { 526 return;
527 }
528 // page 1 (written by read) went back; page 2 was only read, and
529 // page 0 was never loaded: neither was written back.
531 for (i = 0; i < PGSIZE; i++) 532 bad += buf[i] != (i < 16 ? fbyte(2 * PGSIZE + i) : fbyte(i)); 534 for (i = 0; i < PGSIZE; i++) 535 bad += buf[i] != fbyte(i); 537 for (i = 0; i < PGSIZE; i++) 540 fail(t, "file contents"); 542}
543
544#define NCHILD 3
545#define PERCHILD 4
546#define ROUNDS 30
547
548// Each child, ROUNDS times: map the whole file shared, check every
549// page, fill its own pages, unmap.
550void
551stresschild(char *arg)
552{
553 int c = arg[0] - '0', r, i, k, fd;
554 int *m;
555
556 for (r = 0; r < ROUNDS; r++) {
558 m = mmap(0, NCHILD * PERCHILD * PGSIZE, PROT_READ | PROT_WRITE, 559 MAP_SHARED, fd, 0);
561 if (m == MAP_FAILED)
563 for (k = 0; k < NCHILD * PERCHILD; k++) { 564 int *pg = m + k * PGSIZE / sizeof(int); 565 for (i = 1; i < PGSIZE / sizeof(int); i++) 566 if (pg[i] != pg[0])
567 exit(2); // a page must never be half written 568 }
569 for (k = c * PERCHILD; k < (c + 1) * PERCHILD; k++)
570 for (i = 0; i < PGSIZE / sizeof(int); i++) 571 m[k * PGSIZE / sizeof(int) + i] = (c + 1) * 1000 + r; 574 }
576}
577
578void
579stresstest(void)
580{
581 char *t = "stress";
582 int fd, c, k, xs, f0, f1, bad = 0; 583 int *ibuf = (int *)buf; 584 char arg[2];
585
588 for (k = 0; k < NCHILD * PERCHILD; k++) 593 for (c = 0; c < NCHILD; c++) { 595 arg[0] = '0' + c;
596 arg[1] = 0;
597 stresschild(arg);
598 }
599 }
600 for (c = 0; c < NCHILD; c++) { 602 if (xs != 0) {
603 printf("mmaptest: stress: a child exited with %d\n", xs); 605 }
606 }
608 for (k = 0; k < NCHILD * PERCHILD; k++) { 610 for (c = 0; c < PGSIZE / sizeof(int); c++) 611 bad += ibuf[c] != (k / PERCHILD + 1) * 1000 + ROUNDS - 1; 612 }
615 fail(t, "file contents"); 617 if (f1 != f0) {
618 printf("mmaptest: stress: %d free pages before, %d after\n", f0, f1); 619 fail(t, "pages leak"); 620 }
622}
623
625 void (*f)(void);
627} tests[] = {
628 {readtest, "read"}, {sharedtest, "shared"}, {privatetest, "private"},
629 {partialtest, "partial"}, {forktest, "fork"}, {exittest, "exit"}, 630 {manytest, "many"}, {lockedtest, "locked"}, {selftest, "self"},
631 {stresstest, "stress"},
632};
633
634int
636{
638 int ran = 0;
639
641 exit(0); // exittest's exec 642 for (t = tests; t < &tests[sizeof(tests) / sizeof(tests[0])]; t++) {
644 continue;
645 int before = failed;
646 failed = 0;
647 t->f();
648 if (!failed)
650 failed |= before;
651 ran++;
652 }
653 if (ran == 0) {
656 }
657 if (argc == 1)
658 printf(failed ? "mmaptest: SOME FAILED\n" : "mmaptest: ALL OK\n"); 660}