SerenityOS / serenity

The Serenity Operating System 🐞
https://serenityos.org
BSD 2-Clause "Simplified" License
29.64k stars 3.15k forks source link

Kernel/Memory: Redundant page faults on anonymous mmap regions after fork #24637

Open brody-qq opened 5 days ago

brody-qq commented 5 days ago

If you create an anonymous mmap, and then fork, any writes you make to that mmap will cause 2 page faults instead of 1.

How to reproduce: Apply the following diff, then run test-redundant-fault from the shell

test-redundant-fault simply creates an anonymous mmap, forks, and then writes to each page of the mmap

diff --git a/Kernel/Memory/Region.cpp b/Kernel/Memory/Region.cpp
index 66648dd735..cb03420931 100644
--- a/Kernel/Memory/Region.cpp
+++ b/Kernel/Memory/Region.cpp
@@ -429,7 +429,7 @@ PageFaultResponse Region::handle_fault(PageFault const& fault)
     }
     VERIFY(fault.type() == PageFault::Type::ProtectionViolation);
     if (fault.access() == PageFault::Access::Write && is_writable() && should_cow(page_index_in_region)) {
-        dbgln_if(PAGE_FAULT_DEBUG, "PV(cow) fault in Region({})[{}] at {}", this, page_index_in_region, fault.vaddr());
+        dbgln("PV(cow) fault in Region({})[{}] at {}", this, page_index_in_region, fault.vaddr());
         auto phys_page = physical_page(page_index_in_region);
         if (phys_page->is_shared_zero_page() || phys_page->is_lazy_committed_page()) {
             dbgln_if(PAGE_FAULT_DEBUG, "NP(zero) fault in Region({})[{}] at {}", this, page_index_in_region, fault.vaddr());
diff --git a/Userland/Utilities/test-redundant-fault.cpp b/Userland/Utilities/test-redundant-fault.cpp
new file mode 100644
index 0000000000..733c98adb2
--- /dev/null
+++ b/Userland/Utilities/test-redundant-fault.cpp
@@ -0,0 +1,39 @@
+#include <AK/Assertions.h>
+#include <LibMain/Main.h>
+#include <LibThreading/Thread.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+
+ErrorOr<int> serenity_main(Main::Arguments)
+{
+    size_t pages = 100;
+    size_t size = pages * 4096;
+
+    char *ptr = (char *)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+    if (ptr == MAP_FAILED) {
+        perror(nullptr);
+        return 1;
+    }
+
+    pid_t pid = fork();
+    switch (pid) {
+    case -1:
+        perror(nullptr);
+        exit(1);
+        break;
+    case 0:
+        for (size_t i = 0; i < pages; ++i)
+            ptr[i * 4096] = '$';
+        exit(EXIT_SUCCESS);
+        break;
+    default:
+        wait(nullptr);
+        break;
+    }
+
+    return 0;
+}

You will see dbgln output like the following, note how each page has 2 page faults instead of 1:

65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[0] at V0x0000000000010000
65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[0] at V0x0000000000010000
65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[1] at V0x0000000000011000
65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[1] at V0x0000000000011000
65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[2] at V0x0000000000012000
65.756 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[2] at V0x0000000000012000
65.760 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[3] at V0x0000000000013000
65.760 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[3] at V0x0000000000013000
65.760 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[4] at V0x0000000000014000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[4] at V0x0000000000014000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[5] at V0x0000000000015000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[5] at V0x0000000000015000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[6] at V0x0000000000016000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[6] at V0x0000000000016000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[7] at V0x0000000000017000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[7] at V0x0000000000017000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[8] at V0x0000000000018000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[8] at V0x0000000000018000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[9] at V0x0000000000019000
65.764 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[9] at V0x0000000000019000
65.772 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[10] at V0x000000000001a000
65.772 [#0 test-redundant-fault(47:47)]: PV(cow) fault in Region(0x0000002000fbc940)[10] at V0x000000000001a000