Browse Source

A bugfix for issue #80 (os.fork() sigfaults in Python) (#81)

* A bugfix for issue #80. The migration is not complete when a file-backed VMA is larger than
the file size, but the file size is not page-aligned. In Linux, if the file size is smaller
than the mapped memory, accessing the remaining memory that is not backed by the file will
trigger a SIGBUS. However, it is fine to access the remaining memory within the last page that
still overlaps with file, and won't trigger a SIGBUS. This area is commonly used in ELF binary
to store uninitialized, static variables. To improve fork latency, Graphene chooses to
truncate the migration size as the file size, but missed the memory which belongs to the last
file-backed page. The migration size should be aligned up to the page size.

* adding several test cases for mmap with files

* adding /tmp to the ltp manifest

* add documentation about the corner cases of mmap + fork
Chia-Che Tsai 6 years ago
parent
commit
e583b39cd6

+ 15 - 0
LibOS/shim/src/bookkeep/shim_vma.c

@@ -1166,6 +1166,21 @@ BEGIN_CP_FUNC(vma)
         bool protected = false;
 
         if (vma->file) {
+            /*
+             * Chia-Che 8/13/2017:
+             * A fix for cloning a private VMA which maps a file to a process.
+             *
+             * (1) Application can access any page backed by the file, wholly
+             *     or partially.
+             *
+             * (2) Access beyond the last file-backed page will cause SIGBUS.
+             *     For reducing fork latency, the following code truncates the
+             *     memory size for migrating a process. The memory size is
+             *     truncated to the file size, round up to pages.
+             *
+             * (3) Data in the last file-backed page is valid before or after
+             *     forking. Has to be included in process migration.
+             */
             uint64_t file_len = get_file_size(vma->file);
             if (file_len >= 0 &&
                 vma->offset + vma->length > file_len) {

+ 23 - 0
LibOS/shim/test/regression/30_mmap.py

@@ -0,0 +1,23 @@
+#!/usr/bin/python
+
+import os, sys, mmap
+from regression import Regression
+
+loader = sys.argv[1]
+
+# Running Bootstrap
+regression = Regression(loader, "mmap-file", None, 60000)
+
+regression.add_check(name="Private mmap beyond file range",
+    check=lambda res: "mmap test 6 passed" in res[0].out and \
+                      "mmap test 7 passed" in res[0].out and \
+                      "mmap test 8 passed" in res[0].out)
+
+regression.add_check(name="Private mmap beyond file range (after fork)",
+    check=lambda res: "mmap test 1 passed" in res[0].out and \
+                      "mmap test 2 passed" in res[0].out and \
+                      "mmap test 3 passed" in res[0].out and \
+                      "mmap test 4 passed" in res[0].out and \
+                      "mmap test 5 passed" in res[0].out)
+
+regression.run_checks()

+ 1 - 1
LibOS/shim/test/regression/Makefile

@@ -54,7 +54,7 @@ endif
 regression: $(target)
 	@echo "\n\nBasic Bootstrapping:"
 	@for f in $(wildcard 00_*.py); do env $(PYTHONENV) python $$f $(RUNTIME)/pal-$(PAL_HOST); done
-	@echo "\n\nFutex Support:"
+	@echo "\n\nSyscall Support:"
 	@for f in $(wildcard 30_*.py); do env $(PYTHONENV) python $$f $(RUNTIME)/pal-$(PAL_HOST); done
 	@echo "\n\nLarge File Support:"
 	@for f in $(wildcard 90_*.py); do env $(PYTHONENV) python $$f $(RUNTIME)/pal-$(PAL_HOST); done

+ 88 - 0
LibOS/shim/test/regression/mmap-file.c

@@ -0,0 +1,88 @@
+/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */
+/* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */
+
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+
+const char * message;
+
+void SIGBUS_handler (int sig)
+{
+    puts(message);
+    exit(0);
+}
+
+int main (int argc, const char ** argv)
+{
+    int rv;
+
+    /* Initalization: create a 1025-byte file */
+
+    FILE * fp = fopen("testfile","w+");
+    if (!fp) {
+        perror("fopen"); return 1;
+    }
+
+    rv = ftruncate(fileno(fp), 1024);
+    if (rv) {
+        perror ("ftruncate"); return 1;
+    }
+
+    volatile unsigned char * a
+        = mmap(NULL, 9162, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FILE, fileno(fp), 0);
+    if (a == MAP_FAILED) {
+        perror("mmap"); return 1;
+    }
+
+    a[1023] = 0xff;
+    a[4095] = 0xff;
+
+    asm volatile ("nop" ::: "memory");
+
+    int pid = fork();
+    if (pid == -1) {
+        perror("fork"); return 1;
+    }
+    if (pid != 0) {
+        rv = waitpid(pid, NULL, 0);
+        if (rv == -1) {
+            perror("waitpid"); return 1;
+        }
+    }
+
+    asm volatile ("nop" ::: "memory");
+
+    a[   0] = 0xff;
+    printf(pid == 0 ? "mmap test 1 passed\n" : "mmap test 6 passed\n");
+    a[1024] = 0xff;
+    printf(pid == 0 ? "mmap test 2 passed\n" : "mmap test 7 passed\n");
+
+    asm volatile ("nop" ::: "memory");
+
+    if (pid == 0) {
+        if (a[1023] == 0xff)
+            printf("mmap test 3 passed\n");
+        if (a[4095] == 0xff)
+            printf("mmap test 4 passed\n");
+    }
+
+    asm volatile ("nop" ::: "memory");
+
+    if (signal(SIGBUS, SIGBUS_handler) == SIG_ERR) {
+        perror("signal"); return 1;
+    }
+
+    message = pid == 0 ? "mmap test 5 passed\n" : "mmap test 8 passed\n";
+    a[4096] = 0xff;
+
+    if (signal(SIGBUS, SIG_DFL) == SIG_ERR) {
+        perror("signal"); return 1;
+    }
+
+    return 0;
+}