From 2c0df5e7e7a5bc01ad535fc47515d9fb7de67f5f Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 30 Jul 2021 12:54:13 +0200 Subject: [PATCH] Kernel: Share committed COW pages between whole VMObject lineage When cloning an AnonymousVMObject, committed COW pages are shared between the parent and child object. Whicever object COW's first will take the shared committed page, and if the other object ends up doing a COW as well, it will notice that the page is no longer shared by two objects and simple remap it as read/write. When a child is COW'ed again, while still having shared committed pages with its own parent, the grandchild object will now join in the sharing pool with its parent and grandparent. This means that the first 2 of 3 objects that COW will draw from the shared committed pages, and 3rd will remap read/write. Previously, we would "fork" the shared committed pages when cloning, which could lead to a situation where the grandparent held on to 1 of the 3 needed shared committed pages. If both the child and grandchild COW'ed, they wouldn't have enough pages, and since the grandparent maintained an extra +1 ref count on the page, it wasn't possible to to remap read/write. --- Kernel/VM/AnonymousVMObject.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Kernel/VM/AnonymousVMObject.cpp b/Kernel/VM/AnonymousVMObject.cpp index 2700590f43..25b014c440 100644 --- a/Kernel/VM/AnonymousVMObject.cpp +++ b/Kernel/VM/AnonymousVMObject.cpp @@ -39,17 +39,16 @@ RefPtr AnonymousVMObject::try_clone() if (!MM.commit_user_physical_pages(new_cow_pages_needed)) return {}; - // Create or replace the committed cow pages. When cloning a previously - // cloned vmobject, we want to essentially "fork", leaving us and the - // new clone with one set of shared committed cow pages, and the original - // one would keep the one it still has. This ensures that the original - // one and this one, as well as the clone have sufficient resources - // to cow all pages as needed - m_shared_committed_cow_pages = try_create(new_cow_pages_needed); - - if (!m_shared_committed_cow_pages) { - MM.uncommit_user_physical_pages(new_cow_pages_needed); - return {}; + if (m_shared_committed_cow_pages) { + // We already have shared committed COW pages with another object. + // That sharing pool grows and the clone joins it. + m_shared_committed_cow_pages->m_committed_pages += new_cow_pages_needed; + } else { + m_shared_committed_cow_pages = try_create(new_cow_pages_needed); + if (!m_shared_committed_cow_pages) { + MM.uncommit_user_physical_pages(new_cow_pages_needed); + return {}; + } } // Both original and clone become COW. So create a COW map for ourselves