[kernel][vm] Add ability to take runs of pages from VmPageList
This functionality will be used in a subsequent pager change.
Test: k ut vmpl
Change-Id: Ifd54a8fe0a226e1cbfe622e41ca6ab42a06a5613
diff --git a/kernel/vm/include/vm/vm_page_list.h b/kernel/vm/include/vm/vm_page_list.h
index 1ba1eb3..d569923 100644
--- a/kernel/vm/include/vm/vm_page_list.h
+++ b/kernel/vm/include/vm/vm_page_list.h
@@ -101,6 +101,40 @@
vm_page* pages_[kPageFanOut] = {};
};
+class VmPageList;
+
+// Class which holds the list of vm_page structs removed from a VmPageList
+// by TakePages. The list include information about uncommitted pages.
+class VmPageSpliceList final {
+public:
+ VmPageSpliceList();
+ VmPageSpliceList(VmPageSpliceList&& other);
+ VmPageSpliceList& operator=(VmPageSpliceList&& other_tree);
+ ~VmPageSpliceList();
+
+ // Pops the next page off of the splice. If the next page was
+ // not commited, returns null.
+ vm_page* Pop();
+
+ // Returns true after the whole collection has been processed by Pop.
+ bool IsDone() const { return pos_ >= length_; }
+
+ DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(VmPageSpliceList);
+private:
+ VmPageSpliceList(uint64_t offset, uint64_t length);
+ void FreeAllPages();
+
+ uint64_t offset_;
+ uint64_t length_;
+ uint64_t pos_ = 0;
+
+ VmPageListNode head_ = VmPageListNode(0);
+ fbl::WAVLTree<uint64_t, ktl::unique_ptr<VmPageListNode>> middle_;
+ VmPageListNode tail_ = VmPageListNode(0);
+
+ friend VmPageList;
+};
+
class VmPageList final {
public:
VmPageList();
@@ -190,10 +224,16 @@
zx_status_t AddPage(vm_page*, uint64_t offset);
vm_page* GetPage(uint64_t offset);
- zx_status_t FreePage(uint64_t offset);
+ // Removes the page at |offset| from the list. Returns true if a page
+ // was present, false otherwise. If |page| is non-null, returns the
+ // physical page in it, otherwise frees the page.
+ bool RemovePage(uint64_t offset, vm_page** page);
size_t FreeAllPages();
bool IsEmpty();
+ // Takes the pages in the range [offset, length) out of this page list.
+ VmPageSpliceList TakePages(uint64_t offset, uint64_t length);
+
private:
fbl::WAVLTree<uint64_t, ktl::unique_ptr<VmPageListNode>> list_;
};
diff --git a/kernel/vm/vm_object_paged.cpp b/kernel/vm/vm_object_paged.cpp
index 489c003..64ecde0 100644
--- a/kernel/vm/vm_object_paged.cpp
+++ b/kernel/vm/vm_object_paged.cpp
@@ -681,7 +681,7 @@
// iterate through the pages, freeing them
// TODO: use page_list iterator, move pages to list, free at once
while (start < end) {
- page_list_.FreePage(start);
+ page_list_.RemovePage(start, nullptr);
start += PAGE_SIZE;
}
@@ -839,7 +839,7 @@
// iterate through the pages, freeing them
// TODO: use page_list iterator, move pages to list, free at once
while (start < end) {
- page_list_.FreePage(start);
+ page_list_.RemovePage(start, nullptr);
start += PAGE_SIZE;
}
} else if (s > size_) {
diff --git a/kernel/vm/vm_page_list.cpp b/kernel/vm/vm_page_list.cpp
index 74bdf26..e43330b 100644
--- a/kernel/vm/vm_page_list.cpp
+++ b/kernel/vm/vm_page_list.cpp
@@ -19,6 +19,30 @@
#define LOCAL_TRACE MAX(VM_GLOBAL_TRACE, 0)
+namespace {
+
+inline uint64_t offset_to_node_offset(uint64_t offset) {
+ return ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut);
+}
+
+inline uint64_t offset_to_node_index(uint64_t offset) {
+ return (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut;
+}
+
+inline void move_vm_page_list_node(VmPageListNode* dest, VmPageListNode* src) {
+ // Called by move ctor/assignment. Move assignment clears the dest node first.
+ ASSERT(dest->IsEmpty());
+
+ for (unsigned i = 0; i < VmPageListNode::kPageFanOut; i++) {
+ vm_page* page = src->RemovePage(i);
+ if (page) {
+ dest->AddPage(page, i);
+ }
+ }
+}
+
+} // namespace
+
VmPageListNode::VmPageListNode(uint64_t offset)
: obj_offset_(offset) {
LTRACEF("%p offset %#" PRIx64 "\n", this, obj_offset_);
@@ -73,8 +97,8 @@
}
zx_status_t VmPageList::AddPage(vm_page* p, uint64_t offset) {
- uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut);
- size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut;
+ uint64_t node_offset = offset_to_node_offset(offset);
+ size_t index = offset_to_node_index(offset);
LTRACEF_LEVEL(2, "%p page %p, offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, p, offset,
node_offset, index);
@@ -101,8 +125,8 @@
}
vm_page* VmPageList::GetPage(uint64_t offset) {
- uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut);
- size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut;
+ uint64_t node_offset = offset_to_node_offset(offset);
+ size_t index = offset_to_node_index(offset);
LTRACEF_LEVEL(2, "%p offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, offset, node_offset,
index);
@@ -116,9 +140,9 @@
return pln->GetPage(index);
}
-zx_status_t VmPageList::FreePage(uint64_t offset) {
- uint64_t node_offset = ROUNDDOWN(offset, PAGE_SIZE * VmPageListNode::kPageFanOut);
- size_t index = (offset >> PAGE_SIZE_SHIFT) % VmPageListNode::kPageFanOut;
+bool VmPageList::RemovePage(uint64_t offset, vm_page_t** page_out) {
+ uint64_t node_offset = offset_to_node_offset(offset);
+ size_t index = offset_to_node_index(offset);
LTRACEF_LEVEL(2, "%p offset %#" PRIx64 " node_offset %#" PRIx64 " index %zu\n", this, offset, node_offset,
index);
@@ -126,7 +150,7 @@
// lookup the tree node that holds this page
auto pln = list_.find(node_offset);
if (!pln.IsValid()) {
- return ZX_ERR_NOT_FOUND;
+ return false;
}
// free this page
@@ -138,10 +162,15 @@
list_.erase(*pln);
}
- pmm_free_page(page);
+ if (!page_out) {
+ pmm_free_page(page);
+ } else {
+ *page_out = page;
+ }
+ return true;
+ } else {
+ return false;
}
-
- return ZX_OK;
}
size_t VmPageList::FreeAllPages() {
@@ -177,3 +206,117 @@
bool VmPageList::IsEmpty() {
return list_.is_empty();
}
+
+VmPageSpliceList VmPageList::TakePages(uint64_t offset, uint64_t length) {
+ VmPageSpliceList res(offset, length);
+ const uint64_t end = offset + length;
+
+ // If we can't take the whole node at the start of the range,
+ // the shove the pages into the splice list head_ node.
+ while (offset_to_node_index(offset) != 0 && offset < end) {
+ vm_page_t* page;
+ if (RemovePage(offset, &page)) {
+ res.head_.AddPage(page, offset_to_node_index(offset));
+ }
+ offset += PAGE_SIZE;
+ }
+
+ // As long as the current and end node offsets are different, we
+ // can just move the whole node into the splice list.
+ while (offset_to_node_offset(offset) != offset_to_node_offset(end)) {
+ ktl::unique_ptr<VmPageListNode> node = list_.erase(offset_to_node_offset(offset));
+ if (node) {
+ res.middle_.insert(ktl::move(node));
+ }
+ offset += (PAGE_SIZE * VmPageListNode::kPageFanOut);
+ }
+
+ // Move any remaining pages into the splice list tail_ node.
+ while (offset < end) {
+ vm_page_t* page;
+ if (RemovePage(offset, &page)) {
+ res.tail_.AddPage(page, offset_to_node_index(offset));
+ }
+ offset += PAGE_SIZE;
+ }
+
+ return res;
+}
+
+VmPageSpliceList::VmPageSpliceList() : VmPageSpliceList(0, 0) {}
+
+VmPageSpliceList::VmPageSpliceList(uint64_t offset, uint64_t length)
+ : offset_(offset), length_(length) {
+}
+
+VmPageSpliceList::VmPageSpliceList(VmPageSpliceList&& other)
+ : offset_(other.offset_), length_(other.length_),
+ pos_(other.pos_), middle_(ktl::move(other.middle_)) {
+ move_vm_page_list_node(&head_, &other.head_);
+ move_vm_page_list_node(&tail_, &other.tail_);
+
+ other.offset_ = other.length_ = other.pos_ = 0;
+}
+
+VmPageSpliceList& VmPageSpliceList::operator=(VmPageSpliceList&& other) {
+ FreeAllPages();
+
+ offset_ = other.offset_;
+ length_ = other.length_;
+ pos_ = other.pos_;
+ move_vm_page_list_node(&head_, &other.head_);
+ move_vm_page_list_node(&tail_, &other.tail_);
+ middle_ = ktl::move(other.middle_);
+
+ other.offset_ = other.length_ = other.pos_ = 0;
+
+ return *this;
+}
+
+VmPageSpliceList::~VmPageSpliceList() {
+ FreeAllPages();
+}
+
+void VmPageSpliceList::FreeAllPages() {
+ // Free any pages owned by the splice list.
+ while (!IsDone()) {
+ auto page = Pop();
+ if (page) {
+ pmm_free_page(page);
+ }
+ }
+}
+
+vm_page* VmPageSpliceList::Pop() {
+ if (IsDone()) {
+ DEBUG_ASSERT_MSG(false, "Popped from empty splice list");
+ return nullptr;
+ }
+
+ const uint64_t cur_offset = offset_ + pos_;
+ const auto cur_node_idx = offset_to_node_index(cur_offset);
+ const auto cur_node_offset = offset_to_node_offset(cur_offset);
+
+ vm_page* res;
+ if (offset_to_node_index(offset_) != 0
+ && offset_to_node_offset(offset_) == cur_node_offset) {
+ // If the original offset means that pages were placed in head_
+ // and the current offset points to the same node, look there.
+ res = head_.RemovePage(cur_node_idx);
+ } else if (cur_node_offset != offset_to_node_offset(offset_ + length_)) {
+ // If the current offset isn't pointing to the tail node,
+ // look in the middle tree.
+ auto middle_node = middle_.find(cur_node_offset);
+ if (middle_node.IsValid()) {
+ res = middle_node->RemovePage(cur_node_idx);
+ } else {
+ res = nullptr;
+ }
+ } else {
+ // If none of the other cases, we're in the tail_.
+ res = tail_.RemovePage(cur_node_idx);
+ }
+
+ pos_ += PAGE_SIZE;
+ return res;
+}
diff --git a/kernel/vm/vm_unittest.cpp b/kernel/vm/vm_unittest.cpp
index cd73775..52c10fd 100644
--- a/kernel/vm/vm_unittest.cpp
+++ b/kernel/vm/vm_unittest.cpp
@@ -1038,6 +1038,180 @@
END_TEST;
}
+// Basic test that checks adding/removing a page
+static bool vmpl_add_remove_page_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ vm_page_t test_page{};
+ pl.AddPage(&test_page, 0);
+
+ EXPECT_EQ(&test_page, pl.GetPage(0), "unexpected page\n");
+
+ vm_page* remove_page;
+ EXPECT_TRUE(pl.RemovePage(0, &remove_page), "remove failure\n");
+ EXPECT_EQ(&test_page, remove_page, "unexpected page\n");
+
+ END_TEST;
+}
+
+// Tests taking a page from the start of a VmPageListNode
+static bool vmpl_take_single_page_even_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ vm_page_t test_page{};
+ vm_page_t test_page2{};
+ pl.AddPage(&test_page, 0);
+ pl.AddPage(&test_page2, PAGE_SIZE);
+
+ VmPageSpliceList splice = pl.TakePages(0, PAGE_SIZE);
+
+ EXPECT_EQ(&test_page, splice.Pop(), "wrong page\n");
+ EXPECT_TRUE(splice.IsDone(), "extra page\n");
+ EXPECT_NULL(pl.GetPage(0), "duplicate page\n");
+
+ vm_page* remove_page;
+ EXPECT_TRUE(pl.RemovePage(PAGE_SIZE, &remove_page), "remove failure\n");
+ EXPECT_EQ(&test_page2, remove_page, "unexpected page\n");
+
+ END_TEST;
+}
+
+// Tests taking a page from the middle of a VmPageListNode
+static bool vmpl_take_single_page_odd_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ vm_page_t test_page{};
+ vm_page_t test_page2{};
+ pl.AddPage(&test_page, 0);
+ pl.AddPage(&test_page2, PAGE_SIZE);
+
+ VmPageSpliceList splice = pl.TakePages(PAGE_SIZE, PAGE_SIZE);
+
+ EXPECT_EQ(&test_page2, splice.Pop(), "wrong page\n");
+ EXPECT_TRUE(splice.IsDone(), "extra page\n");
+ EXPECT_NULL(pl.GetPage(PAGE_SIZE), "duplicate page\n");
+
+ vm_page* remove_page;
+ EXPECT_TRUE(pl.RemovePage(0, &remove_page), "remove failure\n");
+ EXPECT_EQ(&test_page, remove_page, "unexpected page\n");
+
+ END_TEST;
+}
+
+// Tests taking all the pages from a range of VmPageListNodes
+static bool vmpl_take_all_pages_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ constexpr uint32_t kCount = 3 * VmPageListNode::kPageFanOut;
+ vm_page_t test_pages[VmPageListNode::kPageFanOut] = {};
+ for (uint32_t i = 0; i < kCount; i++) {
+ pl.AddPage(test_pages + i, i * PAGE_SIZE);
+ }
+
+ VmPageSpliceList splice = pl.TakePages(0, kCount * PAGE_SIZE);
+ EXPECT_TRUE(pl.IsEmpty(), "non-empty list\n");
+
+ for (uint32_t i = 0; i < kCount; i++) {
+ EXPECT_EQ(test_pages + i, splice.Pop(), "wrong page\n");
+ }
+ EXPECT_TRUE(splice.IsDone(), "extra pages\n");
+
+ END_TEST;
+}
+
+// Tests taking the middle pages from a range of VmPageListNodes
+static bool vmpl_take_middle_pages_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ constexpr uint32_t kCount = 3 * VmPageListNode::kPageFanOut;
+ vm_page_t test_pages[VmPageListNode::kPageFanOut] = {};
+ for (uint32_t i = 0; i < kCount; i++) {
+ pl.AddPage(test_pages + i, i * PAGE_SIZE);
+ }
+
+ constexpr uint32_t kTakeOffset = VmPageListNode::kPageFanOut - 1;
+ constexpr uint32_t kTakeCount = VmPageListNode::kPageFanOut + 2;
+ VmPageSpliceList splice = pl.TakePages(kTakeOffset * PAGE_SIZE, kTakeCount * PAGE_SIZE);
+ EXPECT_FALSE(pl.IsEmpty(), "non-empty list\n");
+
+ for (uint32_t i = 0; i < kCount; i++) {
+ if (kTakeOffset <= i && i < kTakeOffset + kTakeCount) {
+ EXPECT_EQ(test_pages + i, splice.Pop(), "wrong page\n");
+ } else {
+ vm_page* remove_page;
+ EXPECT_TRUE(pl.RemovePage(i * PAGE_SIZE, &remove_page), "remove failure\n");
+ EXPECT_EQ(test_pages + i, remove_page, "wrong page\n");
+ }
+ }
+ EXPECT_TRUE(splice.IsDone(), "extra pages\n");
+
+ END_TEST;
+}
+
+// Tests that gaps are preserved in the list
+static bool vmpl_take_gap_test() {
+ BEGIN_TEST;
+
+ VmPageList pl;
+ constexpr uint32_t kCount = VmPageListNode::kPageFanOut;
+ constexpr uint32_t kGapSize = 2;
+ vm_page_t test_pages[VmPageListNode::kPageFanOut] = {};
+ for (uint32_t i = 0; i < kCount; i++) {
+ uint64_t offset = (i * (kGapSize + 1)) * PAGE_SIZE;
+ pl.AddPage(test_pages + i, offset);
+ }
+
+ constexpr uint32_t kListStart = PAGE_SIZE;
+ constexpr uint32_t kListLen = (kCount * (kGapSize + 1) - 2) * PAGE_SIZE;
+ VmPageSpliceList splice = pl.TakePages(kListStart, kListLen);
+
+ vm_page* page;
+ EXPECT_TRUE(pl.RemovePage(0, &page), "wrong page\n");
+ EXPECT_EQ(test_pages, page, "wrong page\n");
+ EXPECT_FALSE(pl.RemovePage(kListLen, &page), "wrong page\n");
+
+ for (uint64_t offset = kListStart; offset < kListStart + kListLen; offset += PAGE_SIZE) {
+ auto page_idx = offset / PAGE_SIZE;
+ if (page_idx % (kGapSize + 1) == 0) {
+ EXPECT_EQ(test_pages + (page_idx / (kGapSize + 1)), splice.Pop(), "wrong page\n");
+ } else {
+ EXPECT_NULL(splice.Pop(), "wrong page\n");
+ }
+ }
+ EXPECT_TRUE(splice.IsDone(), "extra pages\n");
+
+ END_TEST;
+}
+
+// Tests that cleaning up a splice list doesn't blow up
+static bool vmpl_take_cleanup_test() {
+ BEGIN_TEST;
+
+ paddr_t pa;
+ vm_page_t* page;
+
+ zx_status_t status = pmm_alloc_page(0, &page, &pa);
+ ASSERT_EQ(ZX_OK, status, "pmm_alloc single page");
+ ASSERT_NONNULL(page, "pmm_alloc single page");
+ ASSERT_NE(0u, pa, "pmm_alloc single page");
+
+ page->state = VM_PAGE_STATE_OBJECT;
+ page->object.pin_count = 0;
+
+ VmPageList pl;
+ pl.AddPage(page, 0);
+
+ VmPageSpliceList splice = pl.TakePages(0, PAGE_SIZE);
+ EXPECT_TRUE(!splice.IsDone(), "missing page\n");
+
+ END_TEST;
+}
+
// Use the function name as the test name
#define VM_UNITTEST(fname) UNITTEST(#fname, fname)
@@ -1079,3 +1253,13 @@
// runs the system out of memory, uncomment for debugging
//VM_UNITTEST(pmm_oversized_alloc_test)
UNITTEST_END_TESTCASE(pmm_tests, "pmm", "Physical memory manager tests");
+
+UNITTEST_START_TESTCASE(vm_page_list_tests)
+VM_UNITTEST(vmpl_add_remove_page_test)
+VM_UNITTEST(vmpl_take_single_page_even_test)
+VM_UNITTEST(vmpl_take_single_page_odd_test)
+VM_UNITTEST(vmpl_take_all_pages_test)
+VM_UNITTEST(vmpl_take_middle_pages_test)
+VM_UNITTEST(vmpl_take_gap_test)
+VM_UNITTEST(vmpl_take_cleanup_test)
+UNITTEST_END_TESTCASE(vm_page_list_tests, "vmpl", "VmPageList tests");