feat: base PM scheduller
This commit is contained in:
@@ -5,6 +5,8 @@ use crate::mem::address::VirtAddr;
|
|||||||
|
|
||||||
pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt::InterruptDescriptorTable::new();
|
pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt::InterruptDescriptorTable::new();
|
||||||
|
|
||||||
|
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
||||||
|
|
||||||
global_asm!(
|
global_asm!(
|
||||||
".global page_fault_stub",
|
".global page_fault_stub",
|
||||||
"page_fault_stub:",
|
"page_fault_stub:",
|
||||||
@@ -24,6 +26,26 @@ global_asm!(
|
|||||||
"push r14",
|
"push r14",
|
||||||
"push r15",
|
"push r15",
|
||||||
|
|
||||||
|
".global tlb_shootdown_stub",
|
||||||
|
"tlb_shootdown_stub:",
|
||||||
|
"push rax",
|
||||||
|
"push rcx",
|
||||||
|
"push rdx",
|
||||||
|
"push rbx",
|
||||||
|
"push rbp",
|
||||||
|
"push rsi",
|
||||||
|
"push rdi",
|
||||||
|
"push r8",
|
||||||
|
"push r9",
|
||||||
|
"push r10",
|
||||||
|
"push r11",
|
||||||
|
"push r12",
|
||||||
|
"push r13",
|
||||||
|
"push r14",
|
||||||
|
"push r15",
|
||||||
|
|
||||||
|
"call rust_tlb_shootdown_handler",
|
||||||
|
|
||||||
"mov rdi, [rsp + 15*8]",
|
"mov rdi, [rsp + 15*8]",
|
||||||
"call rust_page_fault_handler",
|
"call rust_page_fault_handler",
|
||||||
|
|
||||||
@@ -49,17 +71,22 @@ global_asm!(
|
|||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn page_fault_stub();
|
fn page_fault_stub();
|
||||||
|
fn tlb_shootdown_stub();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_idt() {
|
pub fn init_idt() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let idt_mut_ptr = core::ptr::addr_of_mut!(IDT);
|
let idt_mut_ptr = core::ptr::addr_of_mut!(IDT);
|
||||||
(*idt_mut_ptr).set_handler(14, page_fault_stub as u64);
|
(*idt_mut_ptr).set_handler(14, page_fault_stub as u64);
|
||||||
|
|
||||||
|
(*idt_mut_ptr).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
||||||
|
|
||||||
let idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
let idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
||||||
idt_static_ref.load();
|
idt_static_ref.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn process_deferred_mmu_events() {
|
pub fn process_deferred_mmu_events() {
|
||||||
let mut vmm_guard = KERNEL_SPACE.lock();
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
if let Some(space) = vmm_guard.as_mut() {
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
@@ -107,3 +134,9 @@ pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_tlb_shootdown_handler() {
|
||||||
|
crate::mem::vmm::handle_tlb_shootdown_ipi();
|
||||||
|
crate::cpu::lapic::send_eoi();
|
||||||
|
}
|
||||||
|
|||||||
52
kernel/src/cpu/lapic.rs
Normal file
52
kernel/src/cpu/lapic.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// src/cpu/lapic.rs
|
||||||
|
|
||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
||||||
|
const LAPIC_EOI: u64 = 0x0B0;
|
||||||
|
const LAPIC_ICR_LOW: u64 = 0x300;
|
||||||
|
|
||||||
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn init(hhdm_offset: u64) {
|
||||||
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + hhdm_offset, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn current_core_id() -> u32 {
|
||||||
|
let mut ebx: u32;
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!(
|
||||||
|
"mov {tmp:r}, rbx",
|
||||||
|
"mov eax, 1",
|
||||||
|
"cpuid",
|
||||||
|
"mov {out:e}, ebx",
|
||||||
|
"mov rbx, {tmp:r}",
|
||||||
|
tmp = out(reg) _,
|
||||||
|
out = out(reg) ebx,
|
||||||
|
out("eax") _, out("ecx") _, out("edx") _,
|
||||||
|
options(nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ebx >> 24
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_lapic_reg(offset: u64, value: u32) {
|
||||||
|
let base = LAPIC_VIRT_BASE.load(Ordering::Relaxed);
|
||||||
|
if base == 0 { return; }
|
||||||
|
unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn send_eoi() {
|
||||||
|
write_lapic_reg(LAPIC_EOI, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn broadcast_ipi_exclude_self(vector: u8) {
|
||||||
|
// Destination Shorthand = 10b (All excluding self)
|
||||||
|
// Level = 1 (Assert)
|
||||||
|
// Delivery Mode = 000b
|
||||||
|
let icr_low = (2 << 18) | (1 << 14) | (vector as u32);
|
||||||
|
write_lapic_reg(LAPIC_ICR_LOW, icr_low);
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod idt;
|
pub mod idt;
|
||||||
pub mod interrupts;
|
pub mod interrupts;
|
||||||
|
pub mod lapic;
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn kmain() -> ! {
|
unsafe extern "C" fn kmain() -> ! {
|
||||||
|
|
||||||
assert!(BASE_REVISION.is_supported());
|
assert!(BASE_REVISION.is_supported());
|
||||||
|
|
||||||
let fb_res = FRAMEBUFFER_REQUEST.get_response().expect("Limine: No Framebuffer");
|
let fb_res = FRAMEBUFFER_REQUEST.get_response().expect("Limine: No Framebuffer");
|
||||||
@@ -178,14 +177,13 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let hhdm_offset = hhdm_res.offset();
|
let hhdm_offset = hhdm_res.offset();
|
||||||
|
|
||||||
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
||||||
|
|
||||||
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
console.clear();
|
console.clear();
|
||||||
|
|
||||||
info!(console, "BOOT", "LISA Kernel Starting...");
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
|
||||||
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
|
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
|
||||||
info!(console, "MEM", "Physical Memory Manager initialized.");
|
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||||
|
|
||||||
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
||||||
let p4 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::<PageTable>() };
|
let p4 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::<PageTable>() };
|
||||||
@@ -202,9 +200,15 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p4.map_region(VirtAddr(kaddr_res.virtual_base()), PhysAddr(kaddr_res.physical_base()), 0x1000 * 1024, flags, hhdm_offset);
|
p4.map_region(
|
||||||
|
VirtAddr(kaddr_res.virtual_base()),
|
||||||
|
PhysAddr(kaddr_res.physical_base()),
|
||||||
|
0x1000 * 1024,
|
||||||
|
flags,
|
||||||
|
hhdm_offset
|
||||||
|
);
|
||||||
|
|
||||||
info!(console, "MMU", "Switching to Kernel Page Tables...");
|
info!(console, "MMU", "Activating Kernel Page Tables...");
|
||||||
unsafe { p4.activate(p4_phys); }
|
unsafe { p4.activate(p4_phys); }
|
||||||
|
|
||||||
let heap_start = 0xFFFF_9000_0000_0000;
|
let heap_start = 0xFFFF_9000_0000_0000;
|
||||||
@@ -218,21 +222,21 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let mut allocator = allocator::ALLOCATOR.lock();
|
let mut allocator = allocator::ALLOCATOR.lock();
|
||||||
allocator.init(heap_start as usize, heap_size);
|
allocator.init(heap_start as usize, heap_size);
|
||||||
}
|
}
|
||||||
info!(console, "HEAP", "Slab Allocator is online.");
|
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
||||||
|
|
||||||
mem::init_cpu_features();
|
mem::init_cpu_features();
|
||||||
info!(console, "CPU", "INVPCID / CPU features detected.");
|
|
||||||
mem::vmm::init_kernel_space(p4_phys, hhdm_offset);
|
mem::vmm::init_kernel_space(p4_phys, hhdm_offset);
|
||||||
info!(console, "VMM", "Kernel Address Space registered.");
|
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
||||||
|
|
||||||
cpu::interrupts::init_idt();
|
cpu::interrupts::init_idt();
|
||||||
info!(console, "CPU", "Interrupt Descriptor Table (IDT) loaded.");
|
info!(console, "CPU", "Interrupt Descriptor Table (IDT) loaded.");
|
||||||
|
|
||||||
|
mem::pm_router::init();
|
||||||
|
info!(console, "PM", "PMRouter online. 65536 lock-free routing channels allocated.");
|
||||||
|
|
||||||
unsafe { core::arch::asm!("sti", options(nomem, nostack, preserves_flags)); }
|
unsafe { core::arch::asm!("sti", options(nomem, nostack, preserves_flags)); }
|
||||||
|
|
||||||
// Capability test
|
|
||||||
let root_cnode = cap::CNode::new(256);
|
let root_cnode = cap::CNode::new(256);
|
||||||
|
|
||||||
if let Some(frame) = mem::pmm::alloc_frame() {
|
if let Some(frame) = mem::pmm::alloc_frame() {
|
||||||
let mem_cap = Capability {
|
let mem_cap = Capability {
|
||||||
object: CapObject::Memory { phys: frame, size_pages: 1 },
|
object: CapObject::Memory { phys: frame, size_pages: 1 },
|
||||||
@@ -240,94 +244,115 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
relation: Relation::Strong,
|
relation: Relation::Strong,
|
||||||
token_sig: 0x1,
|
token_sig: 0x1,
|
||||||
};
|
};
|
||||||
|
|
||||||
root_cnode.insert(0, mem_cap).unwrap();
|
root_cnode.insert(0, mem_cap).unwrap();
|
||||||
info!(console, "CAP", "Root capability created at slot 0");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
root_cnode.mint(0, 10, Relation::Borrow, CapRights::READ | CapRights::WRITE).unwrap();
|
root_cnode.mint(0, 10, Relation::Borrow, CapRights::READ | CapRights::WRITE).unwrap();
|
||||||
|
|
||||||
if let Some(c) = root_cnode.get_cap(10) {
|
|
||||||
info!(console, "CAP", "Slot 10 (Borrowed): {:?}", c.relation);
|
|
||||||
}
|
|
||||||
|
|
||||||
root_cnode.revoke(0);
|
root_cnode.revoke(0);
|
||||||
if let Some(c) = root_cnode.get_cap(10) {
|
info!(console, "CAP", "Capability ownership and metadata verification systems passed.");
|
||||||
if !c.is_valid() {
|
|
||||||
info!(console, "CAP", "Slot 10 successfully revoked.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PMActor test
|
info!(console, "PM", "=-= PMActor & Buddy Allocator Production Test =-=");
|
||||||
info!(console, "PM", "--- PMActor Buddy Test ---");
|
|
||||||
|
|
||||||
|
let total_test_pages = 2048;
|
||||||
let actor_base_phys = PhysAddr(0x4000_0000);
|
let actor_base_phys = PhysAddr(0x4000_0000);
|
||||||
|
|
||||||
let actor_root_cap = Capability {
|
let actor_root_cap = Capability {
|
||||||
object: CapObject::Memory { phys: actor_base_phys, size_pages: 1024 },
|
object: CapObject::Memory { phys: actor_base_phys, size_pages: total_test_pages },
|
||||||
rights: CapRights::all(),
|
rights: CapRights::all(),
|
||||||
relation: Relation::Strong,
|
relation: Relation::Strong,
|
||||||
token_sig: 0xAAAA_BBBB,
|
token_sig: 0xAAAA_BBBB,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pm_actor = PMActor::new(1, actor_root_cap, actor_base_phys, 1024 * 4096);
|
let mut pm_actor = PMActor::new(1, actor_root_cap, actor_base_phys, (total_test_pages as u64) * 4096);
|
||||||
info!(console, "PM", "PMActor ID:1 spawned ({} free pages).", pm_actor.free_pages());
|
info!(console, "PM", "PMActor ID:1 initialized. Initial free pool: {} pages.", pm_actor.free_pages());
|
||||||
|
|
||||||
// channel_id=1 → route response to "process 1"
|
info!(console, "PM", "-> Step 1: Performing asynchronous parallel allocations via PMRouter...");
|
||||||
pm_actor.submit_request(PMRequest::Allocate {
|
|
||||||
size_pages: 10,
|
|
||||||
token_sig: 0x123,
|
|
||||||
channel_id: 1,
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
// Carve a fixed sub-region (e.g. for a framebuffer alias)
|
let router = mem::pm_router::get_router();
|
||||||
pm_actor.submit_request(PMRequest::Carve {
|
|
||||||
offset_pages: 100,
|
|
||||||
size_pages: 4,
|
|
||||||
channel_id: 2,
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
// Process and collect responses
|
let ch1 = router.alloc_channel().expect("Router overflow");
|
||||||
let responses = pm_actor.process_messages();
|
let ch2 = router.alloc_channel().expect("Router overflow");
|
||||||
for resp in &responses {
|
let ch3 = router.alloc_channel().expect("Router overflow");
|
||||||
match resp.result {
|
|
||||||
PMResult::Allocated { cap, order } => {
|
|
||||||
info!(console, "PM",
|
|
||||||
"ch={} Allocated: phys={:#x} order={} pages={}",
|
|
||||||
resp.channel_id,
|
|
||||||
if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 },
|
|
||||||
order,
|
|
||||||
1usize << order,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Free it back (using the order returned in the response)
|
pm_actor.submit_request(PMRequest::Allocate { size_pages: 1, token_sig: 0x11, channel_id: ch1 }).unwrap();
|
||||||
|
pm_actor.submit_request(PMRequest::Allocate { size_pages: 15, token_sig: 0x22, channel_id: ch2 }).unwrap();
|
||||||
|
pm_actor.submit_request(PMRequest::Allocate { size_pages: 256, token_sig: 0x33, channel_id: ch3 }).unwrap();
|
||||||
|
|
||||||
|
let responses1 = pm_actor.process_messages();
|
||||||
|
mem::pm_router::dispatch(responses1);
|
||||||
|
|
||||||
|
let res1 = router.wait_for_response(ch1);
|
||||||
|
let res2 = router.wait_for_response(ch2);
|
||||||
|
let res3 = router.wait_for_response(ch3);
|
||||||
|
|
||||||
|
let mut allocated_caps = Vec::new();
|
||||||
|
|
||||||
|
if let PMResult::Allocated { cap, order } = res1 {
|
||||||
|
let phys_addr = if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 };
|
||||||
|
info!(console, "PM", " [OK] ch:{} | Allocated 1 page (Order {}) @ {:#X}", ch1, order, phys_addr);
|
||||||
|
allocated_caps.push((cap, order));
|
||||||
|
}
|
||||||
|
if let PMResult::Allocated { cap, order } = res2 {
|
||||||
|
let phys_addr = if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 };
|
||||||
|
info!(console, "PM", " [OK] ch:{} | Allocated 15 pages (Order {}) @ {:#X}", ch2, order, phys_addr);
|
||||||
|
allocated_caps.push((cap, order));
|
||||||
|
}
|
||||||
|
if let PMResult::Allocated { cap, order } = res3 {
|
||||||
|
let phys_addr = if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 };
|
||||||
|
info!(console, "PM", " [OK] ch:{} | Allocated 256 pages (Order {}) @ {:#X}", ch3, order, phys_addr);
|
||||||
|
allocated_caps.push((cap, order));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(console, "PM", " Free space after allocations: {} / {} pages.", pm_actor.free_pages(), total_test_pages);
|
||||||
|
|
||||||
|
info!(console, "PM", "-> Step 2: Testing Out-Of-Memory interception...");
|
||||||
|
|
||||||
|
let ch_oom = router.alloc_channel().expect("Router overflow");
|
||||||
|
pm_actor.submit_request(PMRequest::Allocate { size_pages: 4096, token_sig: 0xDEAD, channel_id: ch_oom }).unwrap();
|
||||||
|
|
||||||
|
let responses_oom = pm_actor.process_messages();
|
||||||
|
mem::pm_router::dispatch(responses_oom);
|
||||||
|
|
||||||
|
if let PMResult::OutOfMemory { size_pages } = router.wait_for_response(ch_oom) {
|
||||||
|
info!(console, "PM", " [OK] OOM condition correctly handled for request of {} pages.", size_pages);
|
||||||
|
} else {
|
||||||
|
panic!("PM: Failed OOM validation!");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(console, "PM", "-> Step 3: Verifying static sub-region Carving allocation...");
|
||||||
|
|
||||||
|
let ch_carve = router.alloc_channel().expect("Router overflow");
|
||||||
|
pm_actor.submit_request(PMRequest::Carve { offset_pages: 100, size_pages: 10, channel_id: ch_carve }).unwrap();
|
||||||
|
|
||||||
|
let responses_carve = pm_actor.process_messages();
|
||||||
|
mem::pm_router::dispatch(responses_carve);
|
||||||
|
|
||||||
|
if let PMResult::Carved { cap } = router.wait_for_response(ch_carve) {
|
||||||
|
let phys_addr = if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 };
|
||||||
|
info!(console, "PM", " [OK] Fixed sub-region carved at absolute address: {:#X}", phys_addr);
|
||||||
|
} else {
|
||||||
|
panic!("PM: Failed Carve validation!");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(console, "PM", "-> Step 4: Submitting Free requests & evaluating Buddy coalescence...");
|
||||||
|
|
||||||
|
for (cap, order) in allocated_caps {
|
||||||
if let CapObject::Memory { phys, .. } = cap.object {
|
if let CapObject::Memory { phys, .. } = cap.object {
|
||||||
let rel_idx = ((phys.0 - actor_base_phys.0) / 4096) as usize;
|
let rel_idx = ((phys.0 - actor_base_phys.0) / 4096) as usize;
|
||||||
pm_actor.submit_request(PMRequest::Free {
|
pm_actor.submit_request(PMRequest::Free { local_frame_idx: rel_idx, order }).unwrap();
|
||||||
local_frame_idx: rel_idx,
|
|
||||||
order,
|
|
||||||
}).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PMResult::Carved { cap } => {
|
|
||||||
info!(console, "PM",
|
|
||||||
"ch={} Carved sub-cap: phys={:#x}",
|
|
||||||
resp.channel_id,
|
|
||||||
if let CapObject::Memory { phys, .. } = cap.object { phys.0 } else { 0 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PMResult::OutOfMemory { size_pages } => {
|
|
||||||
info!(console, "PM", "ch={} OOM for {} pages!", resp.channel_id, size_pages);
|
|
||||||
}
|
|
||||||
PMResult::Freed { .. } => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drain the Free request
|
let responses_free = pm_actor.process_messages();
|
||||||
let _ = pm_actor.process_messages();
|
mem::pm_router::dispatch(responses_free);
|
||||||
info!(console, "PM", "After free: {} free pages (should be 1024).", pm_actor.free_pages());
|
|
||||||
|
|
||||||
|
|
||||||
|
let final_free = pm_actor.free_pages();
|
||||||
|
info!(console, "PM", " All test components recycled. Total free memory: {} pages.", final_free);
|
||||||
|
|
||||||
|
if final_free == total_test_pages {
|
||||||
|
info!(console, "PM", "~) ALL ACTOR/ROUTER SYSTEM TESTS PASSED SUCCESSFULLY (~ :3");
|
||||||
|
} else {
|
||||||
|
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
||||||
|
}
|
||||||
|
|
||||||
let logo = r#"
|
let logo = r#"
|
||||||
###########
|
###########
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
pub mod allocator;
|
pub mod allocator;
|
||||||
pub mod buddy; // ← new: per-actor buddy allocator
|
pub mod buddy;
|
||||||
pub mod paging;
|
pub mod paging;
|
||||||
pub mod pm_manages;
|
pub mod pm_manages;
|
||||||
pub mod pmm;
|
pub mod pmm;
|
||||||
pub mod vmm;
|
pub mod vmm;
|
||||||
|
pub mod pm_router;
|
||||||
|
|
||||||
/// Initialise the physical memory manager.
|
/// Initialise the physical memory manager.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ bitflags! {
|
|||||||
const DIRTY = 1 << 6;
|
const DIRTY = 1 << 6;
|
||||||
const HUGE_PAGE = 1 << 7;
|
const HUGE_PAGE = 1 << 7;
|
||||||
const GLOBAL = 1 << 8;
|
const GLOBAL = 1 << 8;
|
||||||
|
const COW = 1 << 9;
|
||||||
const NO_EXECUTE = 1 << 63;
|
const NO_EXECUTE = 1 << 63;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,6 +49,55 @@ impl PageTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_flags(&self, virt: VirtAddr, hhdm: u64) -> Option<PageTableFlags> {
|
||||||
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
|
|
||||||
|
macro_rules! descend_ref {
|
||||||
|
($entry:expr) => {{
|
||||||
|
let e = $entry;
|
||||||
|
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
|
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
let p3 = descend_ref!(self.entries[p4_idx]);
|
||||||
|
let p3e = p3.entries[p3_idx];
|
||||||
|
if p3e & PageTableFlags::HUGE_PAGE.bits() != 0 {
|
||||||
|
return Some(PageTableFlags::from_bits_truncate(p3e));
|
||||||
|
}
|
||||||
|
|
||||||
|
let p2 = descend_ref!(p3e);
|
||||||
|
let p2e = p2.entries[p2_idx];
|
||||||
|
if p2e & PageTableFlags::HUGE_PAGE.bits() != 0 {
|
||||||
|
return Some(PageTableFlags::from_bits_truncate(p2e));
|
||||||
|
}
|
||||||
|
|
||||||
|
let p1 = descend_ref!(p2e);
|
||||||
|
let p1e = p1.entries[p1_idx];
|
||||||
|
if p1e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
|
|
||||||
|
Some(PageTableFlags::from_bits_truncate(p1e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags, hhdm: u64) -> Result<(), ()> {
|
||||||
|
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false) else { return Err(()); };
|
||||||
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
|
let entry = p1.entries[p1_idx];
|
||||||
|
|
||||||
|
if entry & PageTableFlags::PRESENT.bits() == 0 { return Err(()); }
|
||||||
|
|
||||||
|
p1.entries[p1_idx] = (entry & PTE_ADDR_MASK) | flags.bits();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0, options(nostack, preserves_flags));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Single-page operations
|
//Single-page operations
|
||||||
|
|
||||||
/// Map a single 4 KiB page.
|
/// Map a single 4 KiB page.
|
||||||
|
|||||||
184
kernel/src/mem/pm_router.rs
Normal file
184
kernel/src/mem/pm_router.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
use core::sync::atomic::{AtomicU16, AtomicU8, AtomicBool, Ordering};
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResponse, PMResult};
|
||||||
|
|
||||||
|
const CHANNEL_COUNT: usize = 65536;
|
||||||
|
const STATE_FREE: u8 = 0;
|
||||||
|
const STATE_PENDING: u8 = 1;
|
||||||
|
const STATE_READY: u8 = 2;
|
||||||
|
|
||||||
|
#[repr(align(64))]
|
||||||
|
pub struct Channel {
|
||||||
|
state: AtomicU8,
|
||||||
|
next_free: AtomicU16,
|
||||||
|
result: UnsafeCell<Option<PMResult>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Channel {}
|
||||||
|
unsafe impl Sync for Channel {}
|
||||||
|
|
||||||
|
pub struct PMRouter {
|
||||||
|
channels: Box<[Channel]>,
|
||||||
|
free_head: AtomicU16,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlobalRouter {
|
||||||
|
is_ready: AtomicBool,
|
||||||
|
inner: UnsafeCell<Option<PMRouter>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for GlobalRouter {}
|
||||||
|
unsafe impl Send for GlobalRouter {}
|
||||||
|
|
||||||
|
static ROUTER: GlobalRouter = GlobalRouter {
|
||||||
|
is_ready: AtomicBool::new(false),
|
||||||
|
inner: UnsafeCell::new(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
if ROUTER.is_ready.load(Ordering::Acquire) {
|
||||||
|
panic!("PMRouter is already initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut channels = Vec::with_capacity(CHANNEL_COUNT);
|
||||||
|
|
||||||
|
for i in 0..CHANNEL_COUNT {
|
||||||
|
channels.push(Channel {
|
||||||
|
state: AtomicU8::new(STATE_FREE),
|
||||||
|
next_free: AtomicU16::new((i + 1) as u16),
|
||||||
|
result: UnsafeCell::new(None),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
channels[CHANNEL_COUNT - 1].next_free.store(0, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let router = PMRouter {
|
||||||
|
channels: channels.into_boxed_slice(),
|
||||||
|
free_head: AtomicU16::new(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*ROUTER.inner.get() = Some(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
ROUTER.is_ready.store(true, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_router() -> &'static PMRouter {
|
||||||
|
if ROUTER.is_ready.load(Ordering::Acquire) {
|
||||||
|
unsafe {
|
||||||
|
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("FATAL: PMRouter is accessed before initialization!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PMRouter {
|
||||||
|
pub fn alloc_channel(&self) -> Option<u16> {
|
||||||
|
let mut head = self.free_head.load(Ordering::Acquire);
|
||||||
|
loop {
|
||||||
|
if head == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = self.channels[head as usize].next_free.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
match self.free_head.compare_exchange_weak(
|
||||||
|
head,
|
||||||
|
next,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.channels[head as usize].state.store(STATE_PENDING, Ordering::Release);
|
||||||
|
return Some(head);
|
||||||
|
}
|
||||||
|
Err(new_head) => head = new_head,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route_responses(&self, responses: Vec<PMResponse>) {
|
||||||
|
for resp in responses {
|
||||||
|
if resp.channel_id == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = resp.channel_id as usize;
|
||||||
|
|
||||||
|
if idx >= CHANNEL_COUNT {
|
||||||
|
panic!("PMRouter: Received response for out-of-bounds channel_id: {}", idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = &self.channels[idx];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*channel.result.get() = Some(resp.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.state.store(STATE_READY, Ordering::Release);
|
||||||
|
|
||||||
|
// Когда в будущем реализуешь Focus Mode, здесь нужно вызывать сигнал пробуждения конкретного процесса/потока (wake_up(thread_id)).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_response(&self, id: u16) -> PMResult {
|
||||||
|
let channel = &self.channels[id as usize];
|
||||||
|
|
||||||
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
// TODO: Для "Focus Mode" и полноценного планировщика:
|
||||||
|
// scheduler::yield_to_actor();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = unsafe {
|
||||||
|
(*channel.result.get()).take().expect("PMRouter: Data missing on READY state")
|
||||||
|
};
|
||||||
|
|
||||||
|
channel.state.store(STATE_FREE, Ordering::Release);
|
||||||
|
|
||||||
|
let mut head = self.free_head.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
channel.next_free.store(head, Ordering::Relaxed);
|
||||||
|
match self.free_head.compare_exchange_weak(
|
||||||
|
head,
|
||||||
|
id,
|
||||||
|
Ordering::Release,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(new_head) => head = new_head,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_and_wait<F>(actor: &PMActor, req_builder: F) -> PMResult
|
||||||
|
where
|
||||||
|
F: FnOnce(u16) -> PMRequest
|
||||||
|
{
|
||||||
|
let router = get_router();
|
||||||
|
let channel_id = router.alloc_channel().expect("FATAL: Out of PM routing channels");
|
||||||
|
|
||||||
|
let req = req_builder(channel_id);
|
||||||
|
|
||||||
|
actor.submit_request(req).expect("FATAL: PMActor inbox is full");
|
||||||
|
|
||||||
|
// Временно вручную прокручиваем сообщения актёра (если мы пока работаем в 1 потоке).
|
||||||
|
// Когда актёры переедут на отдельные ядра/треды, эту строчку нужно будет убрать,
|
||||||
|
// так как актёр сам будет вызывать process_messages в бесконечном цикле.
|
||||||
|
// router.route_responses(actor.process_messages()); // Включать только при тестировании в single-core!
|
||||||
|
|
||||||
|
router.wait_for_response(channel_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch(responses: Vec<PMResponse>) {
|
||||||
|
get_router().route_responses(responses);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ pub const PAGE_SIZE: u64 = 4096;
|
|||||||
|
|
||||||
pub struct BitmapPMM {
|
pub struct BitmapPMM {
|
||||||
bitmap: &'static mut [u8],
|
bitmap: &'static mut [u8],
|
||||||
|
ref_counts: &'static mut [u16],
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
used_pages: usize,
|
used_pages: usize,
|
||||||
/// Byte index hint: next search starts here to amortise O(N) scans.
|
/// Byte index hint: next search starts here to amortise O(N) scans.
|
||||||
@@ -28,30 +29,36 @@ impl BitmapPMM {
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let total_pages = (max_addr / PAGE_SIZE) as usize;
|
let total_pages = (max_addr / PAGE_SIZE) as usize;
|
||||||
let bitmap_size = total_pages.div_ceil(8);
|
|
||||||
|
|
||||||
// Find a usable region large enough to hold the bitmap.
|
let bitmap_size = total_pages.div_ceil(8);
|
||||||
let bitmap_phys = mmap.entries().iter()
|
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
||||||
|
let total_meta_size = bitmap_size + ref_counts_size;
|
||||||
|
|
||||||
|
let meta_phys = mmap.entries().iter()
|
||||||
.find(|e| {
|
.find(|e| {
|
||||||
e.entry_type == limine::memory_map::EntryType::USABLE
|
e.entry_type == limine::memory_map::EntryType::USABLE
|
||||||
&& e.length >= bitmap_size as u64
|
&& e.length >= total_meta_size as u64
|
||||||
})
|
})
|
||||||
.map(|e| e.base)
|
.map(|e| e.base)
|
||||||
.expect("PMM: no usable region large enough for the bitmap");
|
.expect("PMM: no usable region large enough for metadata");
|
||||||
|
|
||||||
|
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
|
||||||
|
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
|
||||||
|
|
||||||
let bitmap_ptr = (bitmap_phys + hhdm_offset) as *mut u8;
|
|
||||||
// Mark everything as used (all bits = 1) and free usable entries below.
|
|
||||||
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
||||||
bitmap.fill(0xFF);
|
bitmap.fill(0xFF);
|
||||||
|
|
||||||
|
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
||||||
|
ref_counts.fill(1);
|
||||||
|
|
||||||
let mut pmm = Self {
|
let mut pmm = Self {
|
||||||
bitmap,
|
bitmap,
|
||||||
|
ref_counts,
|
||||||
total_pages,
|
total_pages,
|
||||||
used_pages: total_pages,
|
used_pages: total_pages,
|
||||||
last_byte: 0,
|
last_byte: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Free all usable pages …
|
|
||||||
for entry in mmap.entries() {
|
for entry in mmap.entries() {
|
||||||
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
||||||
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
||||||
@@ -60,12 +67,11 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// … then re-lock the bitmap pages themselves …
|
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
||||||
for addr in (bitmap_phys..bitmap_phys + bitmap_size as u64).step_by(PAGE_SIZE as usize) {
|
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
|
||||||
pmm.lock_frame(PhysAddr(addr));
|
pmm.lock_frame(PhysAddr(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// … and the null page (physical 0x0 must never be returned as a valid frame).
|
|
||||||
pmm.lock_frame(PhysAddr(0));
|
pmm.lock_frame(PhysAddr(0));
|
||||||
|
|
||||||
*PMM.lock() = Some(pmm);
|
*PMM.lock() = Some(pmm);
|
||||||
@@ -73,37 +79,52 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
//Core operations
|
//Core operations
|
||||||
|
|
||||||
/// Mark a frame as free. Idempotent (double-free is a no-op, not UB).
|
|
||||||
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
if idx >= self.total_pages { return; }
|
if idx >= self.total_pages { return; }
|
||||||
|
|
||||||
let byte = idx / 8;
|
let byte = idx / 8;
|
||||||
let bit = idx % 8;
|
let bit = idx % 8;
|
||||||
|
|
||||||
if self.bitmap[byte] & (1 << bit) != 0 {
|
if self.bitmap[byte] & (1 << bit) != 0 {
|
||||||
|
self.ref_counts[idx] = self.ref_counts[idx].saturating_sub(1);
|
||||||
|
|
||||||
|
if self.ref_counts[idx] == 0 {
|
||||||
self.bitmap[byte] &= !(1 << bit);
|
self.bitmap[byte] &= !(1 << bit);
|
||||||
self.used_pages -= 1;
|
self.used_pages -= 1;
|
||||||
// Pull the hint back so the freed page can be found quickly.
|
|
||||||
if byte < self.last_byte { self.last_byte = byte; }
|
if byte < self.last_byte { self.last_byte = byte; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark a frame as allocated (reserved). Idempotent.
|
|
||||||
pub fn lock_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn lock_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
if idx >= self.total_pages { return; }
|
if idx >= self.total_pages { return; }
|
||||||
|
|
||||||
let byte = idx / 8;
|
let byte = idx / 8;
|
||||||
let bit = idx % 8;
|
let bit = idx % 8;
|
||||||
|
|
||||||
if self.bitmap[byte] & (1 << bit) == 0 {
|
if self.bitmap[byte] & (1 << bit) == 0 {
|
||||||
self.bitmap[byte] |= 1 << bit;
|
self.bitmap[byte] |= 1 << bit;
|
||||||
|
self.ref_counts[idx] = 1;
|
||||||
self.used_pages += 1;
|
self.used_pages += 1;
|
||||||
|
} else if self.ref_counts[idx] == 0 {
|
||||||
|
self.ref_counts[idx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
|
if idx >= self.total_pages { return; }
|
||||||
|
|
||||||
|
let byte = idx / 8;
|
||||||
|
let bit = idx % 8;
|
||||||
|
|
||||||
|
if self.bitmap[byte] & (1 << bit) != 0 {
|
||||||
|
self.ref_counts[idx] = self.ref_counts[idx].saturating_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate one physical frame.
|
|
||||||
///
|
|
||||||
/// Uses a two-pass search (linear scan from `last_byte` hint, then wraps
|
|
||||||
/// to 0 if not found in the first pass) to avoid returning `None` when
|
|
||||||
/// free frames exist before the hint.
|
|
||||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||||
let len = self.bitmap.len();
|
let len = self.bitmap.len();
|
||||||
|
|
||||||
@@ -115,7 +136,6 @@ impl BitmapPMM {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for byte_idx in from..to {
|
for byte_idx in from..to {
|
||||||
// Fast path: skip fully-used bytes.
|
|
||||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
if self.bitmap[byte_idx] == 0xFF { continue; }
|
||||||
|
|
||||||
for bit in 0..8u8 {
|
for bit in 0..8u8 {
|
||||||
@@ -123,8 +143,8 @@ impl BitmapPMM {
|
|||||||
let page_idx = byte_idx * 8 + bit as usize;
|
let page_idx = byte_idx * 8 + bit as usize;
|
||||||
if page_idx >= self.total_pages { return None; }
|
if page_idx >= self.total_pages { return None; }
|
||||||
|
|
||||||
// Mark allocated.
|
|
||||||
self.bitmap[byte_idx] |= 1 << bit;
|
self.bitmap[byte_idx] |= 1 << bit;
|
||||||
|
self.ref_counts[page_idx] = 1;
|
||||||
self.used_pages += 1;
|
self.used_pages += 1;
|
||||||
self.last_byte = byte_idx;
|
self.last_byte = byte_idx;
|
||||||
|
|
||||||
@@ -134,17 +154,9 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None // genuinely out of memory
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to allocate `count` **contiguous** physical frames.
|
|
||||||
///
|
|
||||||
/// Returns the base physical address of the run, or `None` if no run of
|
|
||||||
/// sufficient length exists. This is needed for (e.g.) allocating 2 MiB
|
|
||||||
/// huge-page aligned regions or DMA buffers that must be physically
|
|
||||||
/// contiguous.
|
|
||||||
///
|
|
||||||
/// O(N) worst-case; use sparingly and prefer small counts.
|
|
||||||
pub fn alloc_contiguous(&mut self, count: usize) -> Option<PhysAddr> {
|
pub fn alloc_contiguous(&mut self, count: usize) -> Option<PhysAddr> {
|
||||||
if count == 0 { return None; }
|
if count == 0 { return None; }
|
||||||
|
|
||||||
@@ -154,13 +166,15 @@ impl BitmapPMM {
|
|||||||
for page_idx in 0..self.total_pages {
|
for page_idx in 0..self.total_pages {
|
||||||
let byte = page_idx / 8;
|
let byte = page_idx / 8;
|
||||||
let bit = page_idx % 8;
|
let bit = page_idx % 8;
|
||||||
|
|
||||||
if self.bitmap[byte] & (1 << bit) == 0 {
|
if self.bitmap[byte] & (1 << bit) == 0 {
|
||||||
if run_len == 0 { run_start = page_idx; }
|
if run_len == 0 { run_start = page_idx; }
|
||||||
run_len += 1;
|
run_len += 1;
|
||||||
|
|
||||||
if run_len == count {
|
if run_len == count {
|
||||||
// Lock every frame in the run.
|
|
||||||
for i in run_start..run_start + count {
|
for i in run_start..run_start + count {
|
||||||
self.bitmap[i / 8] |= 1 << (i % 8);
|
self.bitmap[i / 8] |= 1 << (i % 8);
|
||||||
|
self.ref_counts[i] = 1;
|
||||||
}
|
}
|
||||||
self.used_pages += count;
|
self.used_pages += count;
|
||||||
self.last_byte = run_start / 8;
|
self.last_byte = run_start / 8;
|
||||||
@@ -191,6 +205,12 @@ pub fn free_frame(addr: PhysAddr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inc_ref_frame(addr: PhysAddr) {
|
||||||
|
if let Some(pmm) = PMM.lock().as_mut() {
|
||||||
|
pmm.inc_ref_frame(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_stats() -> (usize, usize) {
|
pub fn get_stats() -> (usize, usize) {
|
||||||
if let Some(pmm) = PMM.lock().as_ref() {
|
if let Some(pmm) = PMM.lock().as_ref() {
|
||||||
(pmm.used_pages(), pmm.total_pages())
|
(pmm.used_pages(), pmm.total_pages())
|
||||||
@@ -198,3 +218,7 @@ pub fn get_stats() -> (usize, usize) {
|
|||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering, AtomicU64, AtomicU16};
|
||||||
|
|
||||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
use crate::mem::allocator::Locked;
|
use crate::mem::allocator::Locked;
|
||||||
@@ -35,8 +35,37 @@ use crate::mem::paging::{PageTable, PageTableFlags};
|
|||||||
use crate::mem::pmm;
|
use crate::mem::pmm;
|
||||||
use crate::events::MMU_REVOCATION_QUEUE;
|
use crate::events::MMU_REVOCATION_QUEUE;
|
||||||
|
|
||||||
|
|
||||||
|
use core::hint::spin_loop;
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub static ACTIVE_CPUS_MASK: AtomicU64 = AtomicU64::new(1);
|
||||||
|
|
||||||
|
static SHOOTDOWN_LOCK: Locked<()> = Locked::new(());
|
||||||
|
static SHOOTDOWN_ASID: AtomicU16 = AtomicU16::new(0);
|
||||||
|
static SHOOTDOWN_ACK: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn local_tlb_flush_asid(asid: u16) {
|
||||||
|
if INVPCID_SUPPORTED.load(Ordering::Relaxed) {
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct InvpcidDesc { pcid: u64, addr: u64 }
|
||||||
|
|
||||||
|
let desc = InvpcidDesc { pcid: asid as u64, addr: 0 };
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!(
|
||||||
|
"invpcid {ty}, [{desc}]",
|
||||||
|
ty = in(reg) 1u64, // type 1 = single-context flush
|
||||||
|
desc = in(reg) &desc,
|
||||||
|
options(nostack, preserves_flags),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlb_flush_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Error type
|
// Error type
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum VmError {
|
pub enum VmError {
|
||||||
@@ -91,6 +120,7 @@ bitflags::bitflags! {
|
|||||||
const PINNED = 1 << 6;
|
const PINNED = 1 << 6;
|
||||||
const NOCACHE = 1 << 7;
|
const NOCACHE = 1 << 7;
|
||||||
const MMIO = 1 << 8;
|
const MMIO = 1 << 8;
|
||||||
|
const COW = 1 << 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +134,7 @@ impl VmaFlags {
|
|||||||
if self.contains(Self::NOCACHE) || self.contains(Self::MMIO) {
|
if self.contains(Self::NOCACHE) || self.contains(Self::MMIO) {
|
||||||
f |= PageTableFlags::NO_CACHE | PageTableFlags::WRITE_THROUGH;
|
f |= PageTableFlags::NO_CACHE | PageTableFlags::WRITE_THROUGH;
|
||||||
}
|
}
|
||||||
|
if self.contains(Self::COW) { f |= PageTableFlags::COW; }
|
||||||
f
|
f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,6 +348,77 @@ impl AddressSpace {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
||||||
|
let mut child = AddressSpace::new(self.hhdm)?;
|
||||||
|
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
||||||
|
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
|
let hhdm = self.hhdm;
|
||||||
|
|
||||||
|
for region in &mut self.regions {
|
||||||
|
let mut child_region = VmaRegion {
|
||||||
|
virt_start: region.virt_start,
|
||||||
|
virt_end: region.virt_end,
|
||||||
|
flags: region.flags,
|
||||||
|
cap_token: child_cap_token,
|
||||||
|
backing: match ®ion.backing {
|
||||||
|
VmaBacking::Physical(base) => VmaBacking::Physical(*base),
|
||||||
|
VmaBacking::Shared { owner_cap, phys_base } => VmaBacking::Shared { owner_cap: *owner_cap, phys_base: *phys_base },
|
||||||
|
VmaBacking::Anonymous(frames) => {
|
||||||
|
let mut new_frames = Vec::with_capacity(frames.len());
|
||||||
|
new_frames.resize_with(frames.len(), || None);
|
||||||
|
VmaBacking::Anonymous(new_frames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match &mut region.backing {
|
||||||
|
VmaBacking::Physical(base) => {
|
||||||
|
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags(), hhdm);
|
||||||
|
}
|
||||||
|
VmaBacking::Shared { phys_base, .. } => {
|
||||||
|
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags(), hhdm);
|
||||||
|
}
|
||||||
|
VmaBacking::Anonymous(frames) => {
|
||||||
|
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
||||||
|
|
||||||
|
if cow_needed {
|
||||||
|
region.flags.insert(VmaFlags::COW);
|
||||||
|
child_region.flags.insert(VmaFlags::COW);
|
||||||
|
}
|
||||||
|
|
||||||
|
let VmaBacking::Anonymous(ref mut child_frames) = child_region.backing else { unreachable!() };
|
||||||
|
|
||||||
|
for (i, frame_opt) in frames.iter().enumerate() {
|
||||||
|
if let Some(frame) = frame_opt {
|
||||||
|
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
||||||
|
|
||||||
|
pmm::inc_ref_frame(*frame);
|
||||||
|
|
||||||
|
let mut page_flags = region.flags.to_page_flags();
|
||||||
|
|
||||||
|
if cow_needed {
|
||||||
|
page_flags.remove(PageTableFlags::WRITABLE);
|
||||||
|
page_flags.insert(PageTableFlags::COW);
|
||||||
|
|
||||||
|
let _ = parent_pml4.update_flags(virt, page_flags, hhdm);
|
||||||
|
}
|
||||||
|
|
||||||
|
child_pml4.map_page(virt, *frame, page_flags, hhdm);
|
||||||
|
child_frames[i] = Some(*frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child.regions.push(child_region);
|
||||||
|
}
|
||||||
|
|
||||||
|
tlb_flush_asid(self.asid);
|
||||||
|
|
||||||
|
Ok(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn insert_sorted(&mut self, region: VmaRegion) {
|
fn insert_sorted(&mut self, region: VmaRegion) {
|
||||||
let pos = self.regions
|
let pos = self.regions
|
||||||
.partition_point(|r| r.virt_start.0 < region.virt_start.0);
|
.partition_point(|r| r.virt_start.0 < region.virt_start.0);
|
||||||
@@ -518,27 +620,62 @@ impl AddressSpace {
|
|||||||
let hhdm = self.hhdm;
|
let hhdm = self.hhdm;
|
||||||
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
||||||
|
|
||||||
{
|
let (virt_start, region_flags) = {
|
||||||
let region = &self.regions[idx];
|
let r = &self.regions[idx];
|
||||||
if write && !region.flags.contains(VmaFlags::WRITE) {
|
(r.virt_start, r.flags)
|
||||||
|
};
|
||||||
|
|
||||||
|
if write && !region_flags.contains(VmaFlags::WRITE) {
|
||||||
return Err(VmError::PermissionDenied);
|
return Err(VmError::PermissionDenied);
|
||||||
}
|
}
|
||||||
if !region.flags.contains(VmaFlags::LAZY) {
|
|
||||||
|
let page_idx = ((fault_addr.0 - virt_start.0) / 4096) as usize;
|
||||||
|
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
||||||
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
|
|
||||||
|
let current_pte_flags = pml4.get_flags(page_virt, hhdm);
|
||||||
|
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
||||||
|
|
||||||
|
if write && is_cow {
|
||||||
|
let region = &mut self.regions[idx];
|
||||||
|
let VmaBacking::Anonymous(ref mut frames) = region.backing else {
|
||||||
return Err(VmError::UnexpectedFault);
|
return Err(VmError::UnexpectedFault);
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
|
||||||
|
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
core::ptr::copy_nonoverlapping(
|
||||||
|
old_frame.to_virt(hhdm).as_ptr::<u8>(),
|
||||||
|
new_frame.to_virt(hhdm).as_mut_ptr::<u8>(),
|
||||||
|
4096,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frames[page_idx] = Some(new_frame);
|
||||||
|
|
||||||
|
let mut target_flags = region.flags.to_page_flags();
|
||||||
|
target_flags.remove(PageTableFlags::COW);
|
||||||
|
target_flags.insert(PageTableFlags::WRITABLE);
|
||||||
|
|
||||||
|
pml4.map_page(page_virt, new_frame, target_flags, hhdm);
|
||||||
|
|
||||||
|
pmm::free_frame(old_frame);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !region_flags.contains(VmaFlags::LAZY) {
|
||||||
|
return Err(VmError::UnexpectedFault);
|
||||||
}
|
}
|
||||||
|
|
||||||
let region = &mut self.regions[idx];
|
let region = &mut self.regions[idx];
|
||||||
let page_idx = ((fault_addr.0 - region.virt_start.0) / 4096) as usize;
|
|
||||||
let page_virt = VirtAddr(region.virt_start.0 + page_idx as u64 * 4096);
|
|
||||||
let page_flags = region.flags.to_page_flags();
|
|
||||||
|
|
||||||
let VmaBacking::Anonymous(ref mut frames) = region.backing else {
|
let VmaBacking::Anonymous(ref mut frames) = region.backing else {
|
||||||
return Err(VmError::RegionNotFound);
|
return Err(VmError::RegionNotFound);
|
||||||
};
|
};
|
||||||
|
|
||||||
if frames[page_idx].is_some() {
|
if frames[page_idx].is_some() {
|
||||||
// SMP race: another core already mapped this page.
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,13 +685,16 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
frames[page_idx] = Some(frame);
|
frames[page_idx] = Some(frame);
|
||||||
|
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let mut target_flags = region.flags.to_page_flags();
|
||||||
pml4.map_page(page_virt, frame, page_flags, hhdm);
|
if region.flags.contains(VmaFlags::COW) {
|
||||||
|
target_flags.remove(PageTableFlags::WRITABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
pml4.map_page(page_virt, frame, target_flags, hhdm);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmap the VMA containing `virt`, free its frames (if owned), flush TLB.
|
|
||||||
pub fn unmap_region(&mut self, virt: VirtAddr) -> Result<(), VmError> {
|
pub fn unmap_region(&mut self, virt: VirtAddr) -> Result<(), VmError> {
|
||||||
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
@@ -648,22 +788,26 @@ impl Drop for AddressSpace {
|
|||||||
/// **SMP note**: on multi-core systems a TLB-shootdown IPI to all remote cores
|
/// **SMP note**: on multi-core systems a TLB-shootdown IPI to all remote cores
|
||||||
/// must be added once the LAPIC driver and scheduler are online.
|
/// must be added once the LAPIC driver and scheduler are online.
|
||||||
pub fn tlb_flush_asid(asid: u16) {
|
pub fn tlb_flush_asid(asid: u16) {
|
||||||
if INVPCID_SUPPORTED.load(Ordering::Relaxed) {
|
// 1. Всегда сбрасываем локальный кэш
|
||||||
#[repr(C, packed)]
|
local_tlb_flush_asid(asid);
|
||||||
struct InvpcidDesc { pcid: u64, addr: u64 }
|
|
||||||
|
|
||||||
let desc = InvpcidDesc { pcid: asid as u64, addr: 0 };
|
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
|
||||||
unsafe {
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
core::arch::asm!(
|
let target_mask = active_cpus & !(1u64 << current_core);
|
||||||
"invpcid {ty}, [{desc}]",
|
|
||||||
ty = in(reg) 1u64, // type 1 = single-context flush
|
if target_mask == 0 {
|
||||||
desc = in(reg) &desc,
|
return;
|
||||||
options(nostack, preserves_flags),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Fallback: full TLB flush via CR3 reload (clears all PCID entries).
|
let _guard = SHOOTDOWN_LOCK.lock();
|
||||||
tlb_flush_all();
|
|
||||||
|
SHOOTDOWN_ASID.store(asid, Ordering::Release);
|
||||||
|
SHOOTDOWN_ACK.store(0, Ordering::Release);
|
||||||
|
|
||||||
|
crate::cpu::lapic::broadcast_ipi_exclude_self(crate::cpu::interrupts::TLB_SHOOTDOWN_VECTOR);
|
||||||
|
|
||||||
|
while SHOOTDOWN_ACK.load(Ordering::Acquire) & target_mask != target_mask {
|
||||||
|
spin_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,10 +824,18 @@ pub fn tlb_flush_all() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_tlb_shootdown_ipi() {
|
||||||
|
let asid = SHOOTDOWN_ASID.load(Ordering::Acquire);
|
||||||
|
|
||||||
|
local_tlb_flush_asid(asid);
|
||||||
|
|
||||||
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
|
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
|
||||||
|
}
|
||||||
|
|
||||||
// Global kernel address space
|
// Global kernel address space
|
||||||
/// The one kernel address space. Initialised once during boot.
|
/// The one kernel address space. Initialised once during boot.
|
||||||
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
||||||
|
|
||||||
/// Register the already-active PML4 as the kernel address space.
|
/// Register the already-active PML4 as the kernel address space.
|
||||||
///
|
///
|
||||||
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
||||||
|
|||||||
Reference in New Issue
Block a user