Skip to content

Commit b517667

Browse files
committed
Add split_accessed_hugepages argument to Machine::prepare_cow
1 parent b33f4aa commit b517667

6 files changed

Lines changed: 92 additions & 13 deletions

File tree

lib/tinykvm/amd64/paging.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ uint64_t setup_amd64_paging(vMemory& memory,
164164
const uint64_t vdso_pdpt_addr = pml4_addr + PD_END + 0x0;
165165
const uint64_t vsyscall_pd_addr = pml4_addr + PD_END + 0x1000;
166166
const uint64_t vsyscall_pt_addr = pml4_addr + PD_END + 0x2000;
167-
auto *vdso_pdpt = (uint64_t *)(pagetable + PD_END + 0x0000);
168-
auto *vsyscall_pd = (uint64_t *)(pagetable + PD_END + 0x1000);
169-
auto *vsyscall_pt = (uint64_t *)(pagetable + PD_END + 0x2000);
167+
auto* vdso_pdpt = (uint64_t *)(pagetable + PD_END + 0x0000);
168+
auto* vsyscall_pd = (uint64_t *)(pagetable + PD_END + 0x1000);
169+
auto* vsyscall_pt = (uint64_t *)(pagetable + PD_END + 0x2000);
170170

171171
// next free page for ELF loader
172172
uint64_t free_page = pml4_addr + PD_END + 0x3000;
@@ -390,6 +390,10 @@ uint64_t setup_amd64_paging(vMemory& memory,
390390
| (base_giga_page << 30) | (base_2mb_page << 21) | (i << 12);
391391
}
392392

393+
if (free_page >= memory.physbase + (2ULL << 20)) {
394+
throw MachineException("Pagetable setup exceeded 2MB limit");
395+
}
396+
393397
/* Verify a kernel page */
394398
page_at(memory, memory.physbase + 0x1000,
395399
[&memory] (uint64_t addr, uint64_t& entry, size_t size) {
@@ -532,8 +536,9 @@ void foreach_page(vMemory& memory, foreach_page_t callback, bool skip_oob_addres
532536
{
533537
if (pd[k] & PDE64_PRESENT) {
534538
const auto [pt_base, pt_mem, pt_size] = pt_from_index(k, pd_base, pd);
539+
const bool is_2mb_page = (pd[k] & PDE64_PS) != 0;
535540
callback(pt_base, pd[k], pt_size);
536-
if (!(pd[k] & PDE64_PS)) { // not 2MB page
541+
if (!is_2mb_page) {
537542
auto* pt = memory.page_at(pt_mem);
538543
for (uint64_t e = 0; e < 512; e++) {
539544
const auto [pte_base, pte_mem, pte_size] = pte_from_index(e, pt_base, pt);
@@ -554,19 +559,49 @@ void foreach_page(const vMemory& mem, foreach_page_t callback, bool skip_oob_add
554559
foreach_page(const_cast<vMemory&>(mem), std::move(callback), skip_oob_addresses);
555560
}
556561

557-
void foreach_page_makecow(vMemory& mem, uint64_t kernel_end, uint64_t shared_memory_boundary)
562+
void foreach_page_makecow(vMemory& mem, uint64_t kernel_end,
563+
uint64_t shared_memory_boundary, bool split_accessed_hugepages)
558564
{
559565
if (UNLIKELY(shared_memory_boundary < kernel_end)) {
560566
memory_exception("Shared memory boundary was illegal (zero)", shared_memory_boundary, 0u);
561567
}
562568
foreach_page(mem,
563-
[=] (uint64_t addr, uint64_t& entry, size_t /*size*/) {
569+
[=, m = &mem] (uint64_t addr, uint64_t& entry, size_t size) {
564570
if (addr < shared_memory_boundary) {
565571
const uint64_t flags = (PDE64_PRESENT | PDE64_RW);
566572
if ((entry & flags) == flags) {
567573
entry &= ~PDE64_RW;
568574
entry |= PDE64_CLONEABLE | PDE64_G; // Global bit for read-only pages
569575
}
576+
if ((entry & PDE64_ACCESSED) != 0)
577+
{
578+
// Since this page has been accessed, check if it's a 2MB leaf page
579+
// and if it is, split it into 4k pages with the ACCESS bit removed.
580+
if (size == (1ULL << 21) && (entry & PDE64_PS) != 0 && split_accessed_hugepages)
581+
{
582+
// Split 2MB page into 4k pages
583+
const uint64_t pd_base = entry & PDE64_ADDR_MASK;
584+
// Allocate new page for page table
585+
const auto new_page = m->allocate_unmapped_kernelpage();
586+
if (new_page.addr != 0) {
587+
// Set default attributes + new page table
588+
const uint64_t pd_entry_flags = entry & ~(PDE64_ADDR_MASK | PDE64_PS | PDE64_ACCESSED);
589+
entry = pd_entry_flags | new_page.addr;
590+
// Fill new page with default attributes
591+
uint64_t* pagetable = new_page.pmem;
592+
for (uint64_t i = 0; i < 512; i++) {
593+
// Set writable 4k attributes
594+
uint64_t addr4k = pd_base | (i << 12);
595+
pagetable[i] = pd_entry_flags | addr4k;
596+
}
597+
return;
598+
} // new_page.addr
599+
// Failing to allocate, so we simply won't
600+
// split the page, but this may lead to extra
601+
// memory usage if forks don't need the whole
602+
// 2MB page.
603+
}
604+
}
570605
}
571606
// Clear accessed bit for *all* pages
572607
// Doing this makes it possible to send a dummy request to estimate which

lib/tinykvm/amd64/paging.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extern void print_pagetables(const vMemory&);
1414
using foreach_page_t = std::function<void(uint64_t, uint64_t&, size_t)>;
1515
extern void foreach_page(vMemory&, foreach_page_t callback, bool skip_oob_addresses = true);
1616
extern void foreach_page(const vMemory&, foreach_page_t callback, bool skip_oob_addresses = true);
17-
extern void foreach_page_makecow(vMemory&, uint64_t kernel_end, uint64_t shared_memory_boundary);
17+
extern void foreach_page_makecow(vMemory&, uint64_t kernel_end, uint64_t shared_memory_boundary, bool split_accessed_hugepages = false);
1818
extern std::vector<std::pair<uint64_t, uint64_t>> get_accessed_pages(const vMemory& memory);
1919

2020
extern void page_at(vMemory&, uint64_t addr, foreach_page_t, bool ignore_missing = false);

lib/tinykvm/machine.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ struct Machine
163163
address_t exit_address() const noexcept;
164164
void set_stack_address(address_t addr) { this->m_stack_address = addr; }
165165
address_t kernel_end_address() const noexcept { return m_kernel_end; }
166+
void set_kernel_end_address(address_t addr) { m_kernel_end = addr; }
166167
address_t max_address() const noexcept { return memory.physbase + memory.size; }
167168

168169
static constexpr uint64_t BRK_MAX = 0x22000;
@@ -241,7 +242,8 @@ struct Machine
241242
/* Make VM copy-on-write in order to support fast forking.
242243
When @max_work_mem is non-zero, the master VM can still
243244
be used after preparation. */
244-
void prepare_copy_on_write(size_t max_work_mem = 0, uint64_t shared_memory_boundary = UINT64_MAX);
245+
void prepare_copy_on_write(size_t max_work_mem = 0, uint64_t shared_memory_boundary = UINT64_MAX,
246+
bool split_accessed_hugepages = false);
245247
void set_main_memory_writable(bool v) { memory.main_memory_writes = v; }
246248
bool is_forked() const noexcept { return m_forked; }
247249
bool uses_cow_memory() const noexcept { return m_forked || m_prepped; }

lib/tinykvm/memory.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,48 @@ void vMemory::increment_unlocked_pages(size_t pages)
573573
}
574574
}
575575

576+
MemoryBank::Page vMemory::allocate_unmapped_kernelpage()
577+
{
578+
if (this->machine.is_forked()) {
579+
// It's too dangerous as forked VMs may have active
580+
// TLB entries that user memory mappings that will
581+
// suddenly become kernel mappings.
582+
memory_exception("Cannot allocate unmapped kernel page in forked VM", 0, 0);
583+
}
584+
585+
MemoryBank::Page result{};
586+
result.pmem = nullptr;
587+
result.addr = 0;
588+
// 1. Check if kernel end is < 2MB userspace area
589+
const uint64_t kernel_end = machine.kernel_end_address();
590+
if (kernel_end < physbase + (1ULL << 21)) {
591+
// Find the page directory entry for the kernel end
592+
tinykvm::page_at(*this, kernel_end,
593+
[&](uint64_t addr, uint64_t& entry, uint64_t page_size) mutable {
594+
static constexpr uint64_t PDE64_ADDR_MASK = ~0x8000000000000FFF;
595+
// Grab physical from the page directory entry
596+
const uint64_t phys_addr = entry & PDE64_ADDR_MASK;
597+
uint64_t* phys_mem = this->page_at(phys_addr);
598+
if (phys_addr < PT_ADDR || phys_mem < (uint64_t*)(this->ptr + PT_ADDR)) {
599+
memory_exception("Invalid page directory entry for kernel end", addr, page_size);
600+
}
601+
// Set up the page directory entry with:
602+
// Kernel + unmapped + no-execute
603+
entry = phys_addr | PDE64_NX;
604+
605+
result = MemoryBank::Page{
606+
phys_mem,
607+
kernel_end,
608+
PageSize(),
609+
false
610+
};
611+
this->machine.set_kernel_end_address(kernel_end + PageSize());
612+
}, false);
613+
}
614+
//printf("*** Allocated unmapped kernel page at 0x%lX\n", result.addr);
615+
return result;
616+
}
617+
576618
uint64_t vMemory::expectedUsermodeFlags() const noexcept
577619
{
578620
uint64_t flags = PDE64_PRESENT | PDE64_USER | PDE64_RW;

lib/tinykvm/memory.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ struct vMemory {
7272
char *get_writable_page(uint64_t addr, uint64_t flags, bool zeroes, bool dirty);
7373
MemoryBank::Page new_page();
7474
MemoryBank::Page new_hugepage();
75+
MemoryBank::Page allocate_unmapped_kernelpage();
7576

7677
bool compare(const vMemory& other);
7778
/* When a main VM has direct memory writes enabled, it can

lib/tinykvm/vcpu.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,10 @@ void vCPU::set_vcpu_table_at(unsigned index, int value)
416416
}
417417
}
418418

419-
void Machine::prepare_copy_on_write(size_t max_work_mem, uint64_t shared_memory_boundary)
419+
void Machine::prepare_copy_on_write(size_t max_work_mem,
420+
uint64_t shared_memory_boundary, bool split_accessed_hugepages)
420421
{
421422
this->m_prepped = true;
422-
if (max_work_mem == 0) {
423-
}
424423

425424
/* Make each writable page read-only, causing page fault.
426425
any page after the @shared_memory_boundary is untouched,
@@ -457,13 +456,13 @@ void Machine::prepare_copy_on_write(size_t max_work_mem, uint64_t shared_memory_
457456
vcpu.set_special_registers(sregs);
458457
this->enter_usermode();
459458

460-
foreach_page_makecow(this->memory, kernel_end_address(), shared_memory_boundary);
459+
foreach_page_makecow(this->memory, kernel_end_address(), shared_memory_boundary, split_accessed_hugepages);
461460
return;
462461
}
463462

464463
/* This call makes this VM usable after making every page in the
465464
page tables read-only, enabling memory through page faults. */
466-
foreach_page_makecow(this->memory, kernel_end_address(), shared_memory_boundary);
465+
foreach_page_makecow(this->memory, kernel_end_address(), shared_memory_boundary, split_accessed_hugepages);
467466
this->setup_cow_mode(this);
468467
}
469468
void Machine::setup_cow_mode(const Machine* other)

0 commit comments

Comments
 (0)