diff --git a/crate/memory/src/cow.rs b/crate/memory/src/cow.rs index b7f4a98..2093e59 100644 --- a/crate/memory/src/cow.rs +++ b/crate/memory/src/cow.rs @@ -1,4 +1,22 @@ //! Shared memory & Copy-on-write extension for page table +//! +//! 实现共享内存和写时复制机制。 +//! +//! ## 使用说明 +//! +//! 在原页表的基础上套一层:CowExt::new(origin_page_table) +//! 在PageFault时,调用`page_fault_handler()`,如返回true,说明发生了COW,否则再进行其他处理。 +//! +//! ## 实现概述 +//! +//! 我们为页表项定义一个新的状态:共享态。 +//! 使用页表项中2bit,分别表示:只读共享,可写共享。 +//! 在这一状态下,对于CPU而言它是存在+只读的,可以通过不同的页表对该页进行读操作。 +//! 当进行写操作时,会触发PageFault。如果此页实际是只读的,则正常抛出异常。 +//! 否则如果实际是可写的,此时再新分配一个物理页,复制数据,将页表项指向该页,并置为存在+可写。 +//! +//! 对于同一个物理页,允许同时存在读引用和写引用,为此我们需要维护二者的引用计数。 +//! 当PageFault时,如果读引用为0,写引用为1,则直接标记可写。 use super::paging::*; use super::*; @@ -6,7 +24,7 @@ use alloc::BTreeMap; use core::ops::{Deref, DerefMut}; /// Wrapper for page table, supporting shared map & copy-on-write -struct CowExt { +pub struct CowExt { page_table: T, rc_map: FrameRcMap, } @@ -113,8 +131,7 @@ impl FrameRcMap { } } -#[cfg(test)] -mod test { +pub mod test { use super::*; use alloc::boxed::Box; @@ -136,6 +153,11 @@ mod test { pt.page_table.set_handler(Box::new(move |_, addr: VirtAddr| { pt0.page_fault_handler(addr, || alloc.alloc()); })); + + test_with(&mut pt); + } + + pub fn test_with(pt: &mut CowExt) { let target = 0x0; let frame = 0x0; @@ -164,7 +186,7 @@ mod test { pt.unmap_shared(0x3000); assert_eq!(pt.rc_map.read_count(&frame), 0); assert_eq!(pt.rc_map.write_count(&frame), 1); - assert!(!pt.get_entry(0x3000).present()); + // assert!(!pt.get_entry(0x3000).present()); pt.write(0x2000, 3); assert_eq!(pt.rc_map.read_count(&frame), 0); diff --git a/crate/memory/src/paging/mock_page_table.rs b/crate/memory/src/paging/mock_page_table.rs index 78cd33b..fbf4f44 100644 --- a/crate/memory/src/paging/mock_page_table.rs +++ b/crate/memory/src/paging/mock_page_table.rs @@ -81,6 +81,14 @@ impl PageTable for MockPageTable { let pa = self.translate(addr) & !(PAGE_SIZE - 1); self.data[pa..pa + PAGE_SIZE].copy_from_slice(data); } + fn read(&mut self, addr: usize) -> u8 { + self._read(addr); + self.data[self.translate(addr)] + } + fn write(&mut self, addr: usize, data: u8) { + self._write(addr); + self.data[self.translate(addr)] = data; + } } impl MockPageTable { @@ -122,16 +130,6 @@ impl MockPageTable { self.entries[addr / PAGE_SIZE].accessed = true; self.entries[addr / PAGE_SIZE].dirty = true; } - /// Read memory, mark accessed, trigger page fault if not present - pub fn read(&mut self, addr: VirtAddr) -> u8 { - self._read(addr); - self.data[self.translate(addr)] - } - /// Write memory, mark accessed and dirty, trigger page fault if not present - pub fn write(&mut self, addr: VirtAddr, data: u8) { - self._write(addr); - self.data[self.translate(addr)] = data; - } } #[cfg(test)] diff --git a/crate/memory/src/paging/mod.rs b/crate/memory/src/paging/mod.rs index 720224a..8356f97 100644 --- a/crate/memory/src/paging/mod.rs +++ b/crate/memory/src/paging/mod.rs @@ -14,8 +14,11 @@ pub trait PageTable { fn map(&mut self, addr: VirtAddr, target: PhysAddr) -> &mut Self::Entry; fn unmap(&mut self, addr: VirtAddr); fn get_entry(&mut self, addr: VirtAddr) -> &mut Self::Entry; + // For testing with mock fn read_page(&mut self, addr: VirtAddr, data: &mut [u8]); fn write_page(&mut self, addr: VirtAddr, data: &[u8]); + fn read(&mut self, addr: VirtAddr) -> u8; + fn write(&mut self, addr: VirtAddr, data: u8); } pub trait Entry { diff --git a/src/arch/x86_64/interrupt/handler.rs b/src/arch/x86_64/interrupt/handler.rs index 4d9ade3..9c95f98 100644 --- a/src/arch/x86_64/interrupt/handler.rs +++ b/src/arch/x86_64/interrupt/handler.rs @@ -99,7 +99,9 @@ pub extern fn rust_trap(tf: &mut TrapFrame) { } use process::PROCESSOR; - PROCESSOR.try().unwrap().lock().schedule(); + if let Some(processor) = PROCESSOR.try() { + processor.lock().schedule(); + } } fn breakpoint() { diff --git a/src/arch/x86_64/paging/cow.rs b/src/arch/x86_64/paging/cow.rs deleted file mode 100644 index 9743898..0000000 --- a/src/arch/x86_64/paging/cow.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! Shared memory & Copy-on-write extension for page table -//! -//! 利用x86页表项的特性,实现共享内存和写时复制机制。 -//! -//! ## 使用说明 -//! -//! 实现目标机制的全部代码都在此文件中,只对原始代码进行了极小的微调和补充。 -//! 使用时直接 use Trait ,调用相应函数即可。 -//! 此外需要在PageFault时,调用`try_copy_on_write()`,如返回true,说明发生了COW,否则再进行其他处理。 -//! -//! ## 实现概述 -//! -//! 我们为页表项定义一个新的状态:共享态。 -//! 在这一状态下,对于CPU而言它是存在+只读的,可以通过不同的页表对该页进行读操作。 -//! 当进行写操作时,会触发PageFault。如果此页实际是只读的,则正常抛出异常。 -//! 否则如果实际是可写的,此时再新分配一个物理页,复制数据,将页表项指向该页,并置为存在+可写。 -//! -//! 对于同一个物理页,允许同时存在读引用和写引用,为此我们需要维护二者的引用计数。 -//! 当PageFault时,如果读引用为0,写引用为1,则直接标记可写。 -//! -//! ## 各标记位状态 -//! -//! * bit 9-11: 用来识别当前状态,值为001表示只读共享,010表示可写共享 -//! * bit 0: 存在位,值为1 -//! * bit 1: 可写位,值为0 -//! -//! ## 实现细节 -//! -//! * Trait `EntryCowExt` 为页表项定义了辅助函数 -//! -//! * Trait `PageTableCowExt` 为活跃页表定义了操作共享映射的接口函数 -//! 其中 `cow_to_owned()` 是发生PageFault时的处理入口 -//! 注意此处的实现对象是 `ActivePageTable`,因为当写入时需要读取目标页的数据 -//! -//! * 为了维护引用计数,开一个全局映射 `RC_MAP`: Frame -> (read_count, write_count) - -use alloc::BTreeMap; -pub use self::test::test_cow; -use spin::Mutex; -use super::*; -use x86_64::instructions::tlb; -use x86_64::VirtAddr; - -trait EntryCowExt { - fn is_shared(&self) -> bool; - fn is_cow(&self) -> bool; - fn set_shared(&mut self, frame: Frame, flags: EntryFlags); - fn copy_on_write(&mut self, new_frame: Option); - fn reset(&mut self); -} - -pub trait PageTableCowExt { - fn map_to_shared(&mut self, page: Page, frame: Frame, flags: EntryFlags); - fn unmap_shared(&mut self, page: Page); - fn try_copy_on_write(&mut self, addr: usize) -> bool; -} - -impl EntryCowExt for Entry { - fn is_shared(&self) -> bool { - self.flags().contains(EntryFlags::SHARED) - } - fn is_cow(&self) -> bool { - self.flags().contains(EntryFlags::COW) - } - fn set_shared(&mut self, frame: Frame, mut flags: EntryFlags) { - flags |= EntryFlags::PRESENT; - if flags.contains(EntryFlags::WRITABLE) { - flags.remove(EntryFlags::WRITABLE); - flags.insert(EntryFlags::COW); - RC_MAP.write_increase(&frame); - } else { - flags.insert(EntryFlags::SHARED); - RC_MAP.read_increase(&frame); - } - self.set(frame, flags); - } - fn copy_on_write(&mut self, new_frame: Option) { - // assert!(self.is_cow()); - let frame = self.pointed_frame().unwrap(); - RC_MAP.write_decrease(&frame); - let mut flags = self.flags() | EntryFlags::WRITABLE; - flags.remove(EntryFlags::COW); - self.set(new_frame.unwrap_or(frame), flags); - } - fn reset(&mut self) { - let frame = self.pointed_frame().unwrap(); - if self.is_shared() { - RC_MAP.read_decrease(&frame); - } else if self.is_cow() { - RC_MAP.write_decrease(&frame); - } - self.set_unused(); - } -} - -impl PageTableCowExt for ActivePageTable { - fn map_to_shared(&mut self, page: Page, frame: Frame, flags: EntryFlags) { - let entry = self.entry_mut(page); - assert!(entry.is_unused()); - entry.set_shared(frame, flags); - } - fn unmap_shared(&mut self, page: Page) { - self.entry_mut(page).reset(); - tlb::flush(VirtAddr::new(page.start_address() as u64)); - } - fn try_copy_on_write(&mut self, addr: usize) -> bool { - let page = Page::of_addr(addr); - let entry = self.entry_mut(page); - if !entry.is_cow() { - return false; - } - let frame = entry.pointed_frame().unwrap(); - if RC_MAP.read_count(&frame) == 0 && RC_MAP.write_count(&frame) == 1 { - entry.copy_on_write(None); - } else { - use core::{slice, mem::uninitialized}; - let mut temp_data: [u8; PAGE_SIZE] = unsafe { uninitialized() }; - let page_data = unsafe { slice::from_raw_parts_mut(page.start_address() as *mut u8, PAGE_SIZE) }; - temp_data.copy_from_slice(page_data); - - entry.copy_on_write(Some(alloc_frame())); - tlb::flush(VirtAddr::new(page.start_address() as u64)); - - page_data.copy_from_slice(&temp_data); - } - true - } -} - -/// A global map contains reference count for shared frame -lazy_static! { - static ref RC_MAP: FrameRcMap = FrameRcMap::new(); -} -struct FrameRcMap(Mutex>); - -impl FrameRcMap { - fn new() -> Self { - FrameRcMap(Mutex::new(BTreeMap::new())) - } - fn read_count(&self, frame: &Frame) -> u8 { - self.0.lock().get(frame).unwrap_or(&(0, 0)).0 - } - fn write_count(&self, frame: &Frame) -> u8 { - self.0.lock().get(frame).unwrap_or(&(0, 0)).1 - } - fn read_increase(&self, frame: &Frame) { - let mut map = self.0.lock(); - let (r, w) = map.get(&frame).unwrap_or(&(0, 0)).clone(); - map.insert(frame.clone(), (r + 1, w)); - } - fn read_decrease(&self, frame: &Frame) { - let mut map = self.0.lock(); - map.get_mut(frame).unwrap().0 -= 1; - } - fn write_increase(&self, frame: &Frame) { - let mut map = self.0.lock(); - let (r, w) = map.get(&frame).unwrap_or(&(0, 0)).clone(); - map.insert(frame.clone(), (r, w + 1)); - } - fn write_decrease(&self, frame: &Frame) { - let mut map = self.0.lock(); - map.get_mut(frame).unwrap().1 -= 1; - } -} - -mod test { - use super::*; - - pub fn test_cow() { - let mut page_table = unsafe { ActivePageTable::new() }; - let frame = alloc_frame(); - - page_table.map_to(Page::of_addr(0x1000), frame.clone(), EntryFlags::WRITABLE); - unsafe { *(0x1000 as *mut u8) = 1; } - assert_eq!(unsafe { *(0x1000 as *const u8) }, 1); - page_table.unmap(Page::of_addr(0x1000)); - - page_table.map_to_shared(Page::of_addr(0x1000), frame.clone(), EntryFlags::WRITABLE); - page_table.map_to_shared(Page::of_addr(0x2000), frame.clone(), EntryFlags::WRITABLE); - page_table.map_to_shared(Page::of_addr(0x3000), frame.clone(), EntryFlags::PRESENT); - assert_eq!(RC_MAP.read_count(&frame), 1); - assert_eq!(RC_MAP.write_count(&frame), 2); - assert_eq!(unsafe { *(0x1000 as *const u8) }, 1); - assert_eq!(unsafe { *(0x2000 as *const u8) }, 1); - assert_eq!(unsafe { *(0x3000 as *const u8) }, 1); - - unsafe { *(0x1000 as *mut u8) = 2; } - assert_eq!(RC_MAP.read_count(&frame), 1); - assert_eq!(RC_MAP.write_count(&frame), 1); - assert_ne!(page_table.translate_page(Page::of_addr(0x1000)).unwrap(), frame); - assert_eq!(unsafe { *(0x1000 as *const u8) }, 2); - assert_eq!(unsafe { *(0x2000 as *const u8) }, 1); - assert_eq!(unsafe { *(0x3000 as *const u8) }, 1); - - page_table.unmap_shared(Page::of_addr(0x3000)); - assert_eq!(RC_MAP.read_count(&frame), 0); - assert_eq!(RC_MAP.write_count(&frame), 1); - assert_eq!(page_table.translate_page(Page::of_addr(0x3000)), None); - - unsafe { *(0x2000 as *mut u8) = 3; } - assert_eq!(RC_MAP.read_count(&frame), 0); - assert_eq!(RC_MAP.write_count(&frame), 0); - assert_eq!(page_table.translate_page(Page::of_addr(0x2000)).unwrap(), frame, - "The last write reference should not allocate new frame."); - assert_eq!(unsafe { *(0x1000 as *const u8) }, 2); - assert_eq!(unsafe { *(0x2000 as *const u8) }, 3); - } -} \ No newline at end of file diff --git a/src/arch/x86_64/paging/mod.rs b/src/arch/x86_64/paging/mod.rs index 38bb5e0..f99c34e 100644 --- a/src/arch/x86_64/paging/mod.rs +++ b/src/arch/x86_64/paging/mod.rs @@ -1,4 +1,5 @@ use memory::*; +pub use ucore_memory::cow::CowExt; pub use ucore_memory::paging::{Entry, PageTable}; use x86_64::instructions::tlb; use x86_64::registers::control::{Cr3, Cr3Flags}; @@ -51,6 +52,8 @@ impl PageTable for ActivePageTable { } fn get_entry(&mut self, addr: usize) -> &mut PageEntry { + assert!(self.0.translate_page(Page::of_addr(addr)).is_some(), + "page table entry not exist"); let entry_addr = ((addr >> 9) & 0o777_777_777_7770) | 0xffffff80_00000000; unsafe { &mut *(entry_addr as *mut PageEntry) } } @@ -66,6 +69,14 @@ impl PageTable for ActivePageTable { let mem = unsafe { slice::from_raw_parts_mut((addr & !0xfffusize) as *mut u8, 4096) }; mem.copy_from_slice(data); } + + fn read(&mut self, addr: usize) -> u8 { + unsafe { *(addr as *const u8) } + } + + fn write(&mut self, addr: usize, data: u8) { + unsafe { *(addr as *mut u8) = data; } + } } impl ActivePageTable { diff --git a/src/lib.rs b/src/lib.rs index 7ad73b4..bb9e735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,13 +81,13 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) -> ! { arch::gdt::init(); - test!(cow); + memory::test::cow(); test!(global_allocator); test!(guard_page); test!(find_mp); - use memory::*; let acpi = arch::driver::init(rsdt_addr, |addr: usize, count: usize| { + use memory::*; kernel_memory.push(MemoryArea::new_identity(addr, addr + count * 0x1000, MemoryAttr::default(), "acpi")) }); @@ -169,9 +169,4 @@ mod test { println!("It did not crash!"); } - - pub fn cow() { - use arch; -// arch::paging::test_cow(); - } } \ No newline at end of file diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 03e548c..cc25e2e 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -39,13 +39,14 @@ fn alloc_stack(size_in_pages: usize) -> Stack { .alloc_stack(&mut active_table, size_in_pages).expect("no more stack") } +lazy_static! { + static ref ACTIVE_TABLE: Mutex> = Mutex::new(unsafe { + CowExt::new(ActivePageTable::new()) + }); +} + /// The only way to get active page table -fn active_table() -> MutexGuard<'static, ActivePageTable> { - lazy_static! { - static ref ACTIVE_TABLE: Mutex = Mutex::new(unsafe { - ActivePageTable::new() - }); - } +fn active_table() -> MutexGuard<'static, CowExt> { ACTIVE_TABLE.lock() } @@ -56,9 +57,8 @@ pub fn frame_allocator() -> BitAllocGuard { // Return true to continue, false to halt pub fn page_fault_handler(addr: VirtAddr) -> bool { // Handle copy on write - false - // FIXME: enable cow -// active_table().try_copy_on_write(addr) + unsafe { ACTIVE_TABLE.force_unlock(); } + active_table().page_fault_handler(addr, || alloc_frame().start_address().as_u64() as usize) } pub fn init(boot_info: BootInformation) -> MemorySet { @@ -199,3 +199,12 @@ impl From for MemoryAttr { flags } } + +use super::*; + +pub mod test { + pub fn cow() { + use ucore_memory::cow::test::test_with; + test_with(&mut active_table()); + } +} \ No newline at end of file