// shmtest -- prove that shared memory is actually shared, and that a // file-backed mmap returns the file. // // Both of these are things you cannot check by reading a return value. shmget // and shmat returned -ENOSYS until now, and mmap of a file "succeeded" while // handing back zeroed anonymous pages -- a lie that looks exactly like // success. The only convincing test is for one process to write and a // different one to read. // // shmtest run every check // // Prints one line per check, and a final verdict a script can grep for. #include "types.h" #include "user.h" #include "fcntl.h" #define PROT_READ 0x1 #define PROT_WRITE 0x2 #define MAP_PRIVATE 0x02 #define MAP_SHARED 0x01 #define IPC_PRIVATE 0 #define IPC_CREAT 01000 static int failures; static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void*)s, n); } static void check(int ok, const char *what) { puts_(ok ? " [ok] " : " FAIL: "); puts_(what); puts_("\n"); if (!ok) failures++; } int main(void) { // --- shared memory ---------------------------------------------------- int id = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0600); check(id >= 0, "shmget created a segment"); if (id < 0) { puts_("shmtest: FAILED\n"); return 1; } char *p = shmat(id, 0, 0); check((long)p > 0, "shmat mapped it"); if ((long)p <= 0) { puts_("shmtest: FAILED\n"); return 1; } // A fresh segment must be zeroed, or it leaks whatever the previous // tenant left in those pages to whoever gets them next. int zeroed = 1; for (int i = 0; i < 8192; i++) if (p[i] != 0) { zeroed = 0; break; } check(zeroed, "a new segment reads as zeros"); p[0] = 'P'; p[1] = 0; int pid = fork(); if (pid == 0) { // The child inherited the attachment through fork. If the pages had // been copied instead of shared, the parent would never see this. p[0] = 'C'; p[100] = 'X'; exit(); } wait(); check(p[0] == 'C', "a write by the child is visible in the parent"); check(p[100] == 'X', "and so is one further into the segment"); // --- file-backed mmap -------------------------------------------------- // Written and compared through one constant, and never by hand-counted // offsets. Those offsets used to spell out SCHUNIX-MMAP-OK a character at a // time -- m[3]=='U' and m[14]=='K' -- with the length 15 written out // separately. Renaming the marker to SCHOS shortened it to 13 and left both // behind: the write ran two bytes off the end of the literal and the // compare read past the data, which failed a file-backed mmap that was // working correctly. static const char MARK[] = "SCHOS-MMAP-OK"; const int MARKLEN = (int)(sizeof(MARK) - 1); int fd = open("/tmp/shmtest.dat", O_CREATE | O_RDWR); check(fd >= 0, "created a file to map"); if (fd >= 0) { write(fd, MARK, MARKLEN); close(fd); } fd = open("/tmp/shmtest.dat", O_RDONLY); if (fd >= 0) { char *m = mmap(0, 4096, PROT_READ, MAP_PRIVATE, fd, 0); int ok = ((long)m > 0); for (int i = 0; ok && i < MARKLEN; i++) ok = (m[i] == MARK[i]); check(ok, "mmap of a file returns the file, not zeros"); close(fd); } else { check(0, "mmap of a file returns the file, not zeros"); } // MAP_SHARED + PROT_WRITE on a file cannot be honoured without a page // cache, and is refused rather than quietly downgraded to a private copy. fd = open("/tmp/shmtest.dat", O_RDWR); if (fd >= 0) { void *m = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); check((long)m < 0, "writable MAP_SHARED on a file is refused, not faked"); close(fd); } check(shmdt(p) == 0, "shmdt released the attachment"); puts_(failures ? "shmtest: FAILED\n" : "shmtest: all checks passed\n"); return failures ? 1 : 0; }