]> Zhao Yanbai Git Server - minix.git/commitdiff
test74: add mmap-related regression tests
authorBen Gras <ben@minix3.org>
Fri, 28 Feb 2014 15:56:40 +0000 (16:56 +0100)
committerLionel Sambuc <lionel@minix3.org>
Mon, 28 Jul 2014 15:05:14 +0000 (17:05 +0200)
tests many complex system/process memory interaction cases.

has to run as root so it can flush the FS cache; needed to
force FS cache misses for unmapped pages.

See the comment in test74.c for a full description of what the tested
cases are.

also re-enable filemap on arm

releasetools/gen_uEnv.txt.sh
test/run
test/test74.c

index f53f3889f93bce565b686f112fb0d3d35beb1d51..b369a9de94d8f182d55a83f3e7d91963d487457a 100755 (executable)
@@ -75,7 +75,7 @@ fill_cmd() {
 
 echo "# Set the command to be executed"
 echo "uenvcmd=run $BOOT"
-echo "bootargs=console=$CONSOLE rootdevname=c0d0p1 verbose=$VERBOSE hz=$HZ filemap=0"
+echo "bootargs=console=$CONSOLE rootdevname=c0d0p1 verbose=$VERBOSE hz=$HZ"
 echo
 echo 'bootminix=setenv bootargs \$bootargs board_name=\$board_name ; echo \$bootargs; go  0x80200000 \\\"$bootargs\\\"'
 echo 
index a61a4dcf2bea12ace4677d13d159b49dc3dcde63..2236520334d70c53496b66cf067eae5a025efd0c 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -21,7 +21,7 @@ badones=                      # list of tests that failed
 
 # Programs that require setuid
 setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \
-        test69 test76 test73 test77 test78"
+        test69 test76 test73 test74 test77 test78"
 # Scripts that require to be run as root
 rootscripts="testisofs testvnd"
 
index da92740dc4ca71b38750b04e07ec40539ff2e589..7d2d23fb2c0fa890c78444efa1f3dc9995815634 100644 (file)
@@ -1,19 +1,54 @@
-/* Test 74 - mmap functionality test.
+/* Test 74 - mmap functionality & regression test.
+ *
+ * This test tests some basic functionality of mmap, and also some
+ * cases that are quite complex for the system to handle.
+ *
+ * Memory pages are generally made available on demand. Memory copying
+ * is done by the kernel. As the kernel may encounter pagefaults in
+ * legitimate memory ranges (e.g. pages that aren't mapped; pages that
+ * are mapped RO as they are COW), it cooperates with VM to make the
+ * mappings and let the copy succeed transparently.
+ *
+ * With file-mapped ranges this can result in a deadlock, if care is
+ * not taken, as the copy might be request by VFS or an FS. This test
+ * triggers as many of these states as possible to ensure they are
+ * successful or (where appropriate) fail gracefully, i.e. without 
+ * deadlock.
+ *
+ * To do this, system calls are done with source or target buffers with
+ * missing or readonly mappings, both anonymous and file-mapped. The
+ * cache is flushed before mmap() so that we know the mappings should
+ * not be present on mmap() time. Then e.g. a read() or write() is
+ * executed with that buffer as target. This triggers a FS copying
+ * to or from a missing range that it itself is needed to map in first.
+ * VFS detects this, requests VM to map in the pages, which does so with
+ * the help of another VFS thread and the FS, and then re-issues the
+ * request to the FS.
+ *
+ * Another case is the VFS itself does such a copy. This is actually
+ * unusual as filenames are already faulted in by the requesting process
+ * in libc by strlen(). select() allows such a case, however, so this
+ * is tested too. We are satisfied if the call completes.
  */
 
 #include <sys/types.h>
 #include <sys/mman.h>
+#include <sys/ioctl.h>
 #include <sys/ioc_memory.h>
+#include <sys/param.h>
 #include <stdio.h>
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <dirent.h>
 
 #include "common.h"
 #include "testcache.h"
 
+int max_error = 0;     /* make all e()'s fatal */
+
 int
 dowriteblock(int b, int blocksize, u32_t seed, char *data)
 {
@@ -75,19 +110,346 @@ readblock(int b, int blocksize, u32_t seed, char *data)
 
 void testend(void) { }
 
+static void do_read(void *buf, int fd, int writable)
+{
+       ssize_t ret;
+       size_t n = PAGE_SIZE;
+       struct stat sb;
+       if(fstat(fd, &sb) < 0) e(1);
+       if(S_ISDIR(sb.st_mode)) return;
+       ret = read(fd, buf, n);
+
+       /* if the buffer is writable, it should succeed */
+       if(writable) { if(ret != n) e(3); return; }
+
+       /* if the buffer is not writable, it should fail with EFAULT */
+       if(ret >= 0) e(4);
+       if(errno != EFAULT) e(5);
+}
+
+static void do_write(void *buf, int fd, int writable)
+{
+       size_t n = PAGE_SIZE;
+       struct stat sb;
+       if(fstat(fd, &sb) < 0) e(1);
+       if(S_ISDIR(sb.st_mode)) return;
+       if(write(fd, buf, n) != n) e(3);
+}
+
+static void do_stat(void *buf, int fd, int writable)
+{
+       int r;
+       struct stat sb;
+       r = fstat(fd, (struct stat *) buf);
+
+       /* should succeed if buf is writable */
+       if(writable) { if(r < 0) e(3); return; }
+
+       /* should fail with EFAULT if buf is not */
+       if(r >= 0) e(4);
+       if(errno != EFAULT) e(5);
+}
+
+static void do_getdents(void *buf, int fd, int writable)
+{
+       struct stat sb;
+       int r;
+       if(fstat(fd, &sb) < 0) e(1);
+       if(!S_ISDIR(sb.st_mode)) return;        /* OK */
+       r = getdents(fd, buf, PAGE_SIZE);
+       if(writable) { if(r < 0) e(3); return; }
+
+       /* should fail with EFAULT if buf is not */
+       if(r >= 0) e(4);
+       if(errno != EFAULT) e(5);
+}
+
+static void do_readlink1(void *buf, int fd, int writable)
+{
+       char target[200];
+       /* the system call just has to fail gracefully */
+       readlink(buf, target, sizeof(target));
+}
+
+#define NODENAME       "a"
+#define TARGETNAME     "b"
+
+static void do_readlink2(void *buf, int fd, int writable)
+{
+       ssize_t rl;
+       unlink(NODENAME);
+       if(symlink(TARGETNAME, NODENAME) < 0) e(1);
+       rl=readlink(NODENAME, buf, sizeof(buf));
+
+       /* if buf is writable, it should succeed, with a certain result */
+       if(writable) {
+               if(rl < 0) e(2);
+               ((char *) buf)[rl] = '\0';
+               if(strcmp(buf, TARGETNAME)) {
+                       fprintf(stderr, "readlink: expected %s, got %s\n",
+                               TARGETNAME, buf);
+                       e(3);
+               }
+               return;
+       }
+
+       /* if buf is not writable, it should fail with EFAULT */
+       if(rl >= 0) e(4);
+
+       if(errno != EFAULT) e(5);
+}
+
+static void do_symlink1(void *buf, int fd, int writable)
+{
+       int r;
+       /* the system call just has to fail gracefully */
+       r = symlink(buf, NODENAME);
+}
+
+static void do_symlink2(void *buf, int fd, int writable)
+{
+       int r;
+       /* the system call just has to fail gracefully */
+       r = symlink(NODENAME, buf);
+}
+
+static void do_open(void *buf, int fd, int writable)
+{
+       int r;
+       /* the system call just has to fail gracefully */
+       r = open(buf, O_RDONLY);
+       if(r >= 0) close(r);
+}
+
+static void do_select1(void *buf, int fd, int writable)
+{
+       int r;
+       struct timeval timeout = { 0, 200000 }; /* 0.2 sec */
+       /* the system call just has to fail gracefully */
+       r = select(1, buf, NULL, NULL, &timeout);
+}
+
+static void do_select2(void *buf, int fd, int writable)
+{
+       int r;
+       struct timeval timeout = { 0, 200000 }; /* 1 sec */
+       /* the system call just has to fail gracefully */
+       r = select(1, NULL, buf, NULL, &timeout);
+}
+
+static void do_select3(void *buf, int fd, int writable)
+{
+       int r;
+       struct timeval timeout = { 0, 200000 }; /* 1 sec */
+       /* the system call just has to fail gracefully */
+       r = select(1, NULL, NULL, buf, &timeout);
+}
+
+static void fillfile(int fd, int size)
+{
+       char *buf = malloc(size);
+
+       if(size < 1 || size % PAGE_SIZE || !buf) { e(1); }
+       memset(buf, 'A', size);
+       buf[50] = '\0'; /* so it can be used as a filename arg */
+       buf[size-1] = '\0';
+       if(write(fd, buf, size) != size) { e(2); }
+       if(lseek(fd, SEEK_SET, 0) < 0) { e(3); }
+       free(buf);
+}
+
+static void make_buffers(int size,
+       int *ret_fd_rw, int *ret_fd_ro,
+       void **filebuf_rw, void **filebuf_ro, void **anonbuf)
+{
+       char fn_rw[] = "testfile_rw.XXXXXX", fn_ro[] = "testfile_ro.XXXXXX";
+       *ret_fd_rw = mkstemp(fn_rw);
+       *ret_fd_ro = mkstemp(fn_ro);
+
+       if(size < 1 || size % PAGE_SIZE) { e(2); }
+       if(*ret_fd_rw < 0) { e(1); }
+       if(*ret_fd_ro < 0) { e(1); }
+       fillfile(*ret_fd_rw, size);
+       fillfile(*ret_fd_ro, size);
+       if(fcntl(*ret_fd_rw, F_FLUSH_FS_CACHE) < 0) { e(4); }
+       if(fcntl(*ret_fd_ro, F_FLUSH_FS_CACHE) < 0) { e(4); }
+
+       if((*filebuf_rw = mmap(0, size, PROT_READ | PROT_WRITE,
+               MAP_PRIVATE | MAP_FILE, *ret_fd_rw, 0)) == MAP_FAILED) {
+               e(5);
+               quit();
+       }
+
+       if((*filebuf_ro = mmap(0, size, PROT_READ,
+               MAP_PRIVATE | MAP_FILE, *ret_fd_ro, 0)) == MAP_FAILED) {
+               e(5);
+               quit();
+       }
+
+       if((*anonbuf = mmap(0, size, PROT_READ | PROT_WRITE,
+               MAP_PRIVATE | MAP_ANON, -1, 0)) == MAP_FAILED) {
+               e(6);
+               quit();
+       }
+
+       if(unlink(fn_rw) < 0) { e(12); }
+       if(unlink(fn_ro) < 0) { e(12); }
+}
+
+static void forget_buffers(void *buf1, void *buf2, void *buf3, int fd1, int fd2, int size)
+{
+       if(munmap(buf1, size) < 0) { e(1); }
+       if(munmap(buf2, size) < 0) { e(2); }
+       if(munmap(buf3, size) < 0) { e(2); }
+       if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(3); }
+       if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(3); }
+       if(close(fd1) < 0) { e(4); }
+       if(close(fd2) < 0) { e(4); }
+}
+
+#define NEXPERIMENTS 12
+struct {
+       void (*do_operation)(void * buf, int fd, int writable);
+} experiments[NEXPERIMENTS] = {
+       { do_read },
+       { do_write },
+       { do_stat },
+       { do_getdents },
+       { do_readlink1 },
+       { do_readlink2 },
+       { do_symlink1 },
+       { do_symlink2 },
+       { do_open, },
+       { do_select1 },
+       { do_select2 },
+       { do_select3 },
+};
+
+void test_memory_types_vs_operations(void)
+{
+#define NFDS 4
+#define BUFSIZE (10 * PAGE_SIZE)
+       int exp, fds[NFDS];
+       int f = 0, size = BUFSIZE;
+
+       /* open some test fd's */
+#define OPEN(fn, mode) { assert(f >= 0 && f < NFDS); \
+       fds[f] = open(fn, mode); if(fds[f] < 0) { e(2); } f++; }
+       OPEN("regular", O_RDWR | O_CREAT);
+       OPEN(".", O_RDONLY);
+       OPEN("/dev/ram", O_RDWR);
+       OPEN("/dev/zero", O_RDWR);
+
+       /* make sure the regular file has plenty of size to play with */
+       fillfile(fds[0], BUFSIZE);
+
+       /* and the ramdisk too */
+        if(ioctl(fds[2], MIOCRAMSIZE, &size) < 0) { e(3); }
+
+       for(exp = 0; exp < NEXPERIMENTS; exp++) {
+               for(f = 0; f < NFDS; f++) {
+                       void *anonmem, *filemem_rw, *filemem_ro;
+                       int buffd_rw, buffd_ro;
+
+                       make_buffers(BUFSIZE, &buffd_rw, &buffd_ro,
+                               &filemem_rw, &filemem_ro, &anonmem);
+
+                       if(lseek(fds[f], 0, SEEK_SET) != 0) { e(10); }
+                       experiments[exp].do_operation(anonmem, fds[f], 1);
+
+                       if(lseek(fds[f], 0, SEEK_SET) != 0) { e(11); }
+                       experiments[exp].do_operation(filemem_rw, fds[f], 1);
+
+                       if(lseek(fds[f], 0, SEEK_SET) != 0) { e(12); }
+                       experiments[exp].do_operation(filemem_ro, fds[f], 0);
+
+                       forget_buffers(filemem_rw, filemem_ro, anonmem, buffd_rw, buffd_ro, BUFSIZE);
+               }
+       }
+}
+
 void basic_regression(void)
 {
-       void *block;
+       int fd, fd1, fd2;
+       ssize_t rb, wr;
+       char buf[PAGE_SIZE*2];
+       void *block, *block1, *block2;
 #define BLOCKSIZE (PAGE_SIZE*10)
        block = mmap(0, BLOCKSIZE, PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANON, -1, 0);
 
-       if(block == MAP_FAILED) { e(1); exit(1); }
+       if(block == MAP_FAILED) { e(1); }
 
        memset(block, 0, BLOCKSIZE);
 
        /* shrink from bottom */
        munmap(block, PAGE_SIZE);
+
+       /* Next test: use a system call write() to access a block of
+        * unavailable file-mapped memory.
+        * 
+        * This is a thorny corner case to make succeed transparently
+        * because 
+        *   (1) it is a filesystem that is doing the memory access
+        *       (copy from the constblock1 range in this process to the
+        *       FS) but is also the FS needed to satisfy the range if it
+        *       isn't in the cache.
+        *   (2) there are two separate memory regions involved, requiring
+        *       separate VFS requests from VM to properly satisfy, requiring
+        *       some complex state to be kept.
+        */
+
+       fd1 = open("../testsh1", O_RDONLY);
+       fd2 = open("../testsh2", O_RDONLY);
+       if(fd1 < 0 || fd2 < 0) { e(2); }
+
+       /* clear cache of files before mmap so pages won't be present already */
+       if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(1); }
+       if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(1); }
+
+#define LOCATION1 (void *) 0x90000000
+#define LOCATION2 (LOCATION1 + PAGE_SIZE)
+       block1 = mmap(LOCATION1, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd1, 0);
+       if(block1 == MAP_FAILED) { e(4); }
+       if(block1 != LOCATION1) { e(5); }
+
+       block2 = mmap(LOCATION2, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd2, 0);
+       if(block2 == MAP_FAILED) { e(10); }
+       if(block2 != LOCATION2) { e(11); }
+
+       unlink("testfile");
+       fd = open("testfile", O_CREAT | O_RDWR);
+       if(fd < 0) { e(15); }
+
+       /* write() using the mmap()ped memory as buffer */
+
+       if((wr=write(fd, LOCATION1, sizeof(buf))) != sizeof(buf)) {
+               fprintf(stderr, "wrote %zd bytes instead of %zd\n",
+                       wr, sizeof(buf));
+               e(20);
+               quit();
+       }
+
+       /* verify written contents */
+
+       if((rb=pread(fd, buf, sizeof(buf), 0)) != sizeof(buf)) {
+               if(rb < 0) perror("pread");
+               fprintf(stderr, "wrote %zd bytes\n", wr);
+               fprintf(stderr, "read %zd bytes instead of %zd\n",
+                       rb, sizeof(buf));
+               e(21);
+               quit();
+       }
+
+       if(memcmp(buf, LOCATION1, sizeof(buf))) {
+               e(22);
+               quit();
+       }
+
+       close(fd);
+       close(fd1);
+       close(fd2);
+
 }
 
 int
@@ -99,6 +461,8 @@ main(int argc, char *argv[])
 
        basic_regression();
 
+       test_memory_types_vs_operations();
+
        makefiles(MAXFILES);
 
        cachequiet(!bigflag);