diff --git a/src/arch/x86_64/interrupt/handler.rs b/src/arch/x86_64/interrupt/handler.rs
index 8ce8e94..38c3314 100644
--- a/src/arch/x86_64/interrupt/handler.rs
+++ b/src/arch/x86_64/interrupt/handler.rs
@@ -17,7 +17,14 @@ interrupt_error_p!(double_fault, stack, {
interrupt_error_p!(page_fault, stack, {
use x86_64::registers::control_regs::cr2;
- println!("\nEXCEPTION: Page Fault\nAddress: {:#x}", cr2());
+ let addr = cr2().0;
+ println!("\nEXCEPTION: Page Fault @ {:#x}", addr);
+
+ use memory::page_fault_handler;
+ if page_fault_handler(addr) {
+ return;
+ }
+
stack.dump();
loop {}
});
diff --git a/src/arch/x86_64/paging/cow.rs b/src/arch/x86_64/paging/cow.rs
new file mode 100644
index 0000000..30d5ba6
--- /dev/null
+++ b/src/arch/x86_64/paging/cow.rs
@@ -0,0 +1,214 @@
+//! 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 super::*;
+use alloc::rc::Rc;
+use alloc::BTreeMap;
+use spin::{Once, Mutex};
+use x86_64::instructions::tlb;
+use x86_64::VirtualAddress;
+
+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: VirtAddr) -> 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(VirtualAddress(page.start_address()));
+ }
+ fn try_copy_on_write(&mut self, addr: VirtAddr) -> 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(VirtualAddress(page.start_address()));
+
+ 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;
+ }
+}
+
+pub use self::test::test_cow;
+
+mod test {
+ use super::*;
+
+ pub fn test_cow() {
+ debug!("Testing: Copy on write");
+
+ 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);
+
+ debug!("Success: Copy on write");
+ }
+}
\ No newline at end of file
diff --git a/src/arch/x86_64/paging/entry.rs b/src/arch/x86_64/paging/entry.rs
index afa86eb..3746074 100644
--- a/src/arch/x86_64/paging/entry.rs
+++ b/src/arch/x86_64/paging/entry.rs
@@ -44,6 +44,9 @@ bitflags! {
const HUGE_PAGE = 1 << 7;
const GLOBAL = 1 << 8;
const NO_EXECUTE = 1 << 63;
+ // Types at bit 9-11
+ const SHARED = 1 << 9;
+ const COW = 2 << 9;
}
}
diff --git a/src/arch/x86_64/paging/mapper.rs b/src/arch/x86_64/paging/mapper.rs
index a8a9cf5..d164e56 100644
--- a/src/arch/x86_64/paging/mapper.rs
+++ b/src/arch/x86_64/paging/mapper.rs
@@ -68,15 +68,19 @@ impl Mapper {
.or_else(huge_page)
}
- pub fn map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags)
- {
+ pub(super) fn entry_mut(&mut self, page: Page) -> &mut Entry {
+ use core::ops::IndexMut;
let p4 = self.p4_mut();
let mut p3 = p4.next_table_create(page.p4_index());
let mut p2 = p3.next_table_create(page.p3_index());
let mut p1 = p2.next_table_create(page.p2_index());
+ p1.index_mut(page.p1_index())
+ }
- assert!(p1[page.p1_index()].is_unused());
- p1[page.p1_index()].set(frame, flags | EntryFlags::PRESENT);
+ pub fn map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags) {
+ let entry = self.entry_mut(page);
+ assert!(entry.is_unused());
+ entry.set(frame, flags | EntryFlags::PRESENT);
}
pub fn map(&mut self, page: Page, flags: EntryFlags)
diff --git a/src/arch/x86_64/paging/mod.rs b/src/arch/x86_64/paging/mod.rs
index 841ad7d..b0cf4a3 100644
--- a/src/arch/x86_64/paging/mod.rs
+++ b/src/arch/x86_64/paging/mod.rs
@@ -3,11 +3,13 @@ pub use self::mapper::Mapper;
use core::ops::{Deref, DerefMut, Add};
use memory::*;
pub use self::temporary_page::TemporaryPage;
+pub use self::cow::*;
mod entry;
mod table;
mod temporary_page;
mod mapper;
+mod cow;
const ENTRY_COUNT: usize = 512;
diff --git a/src/lib.rs b/src/lib.rs
index 4a5dde8..919c604 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -67,6 +67,8 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) -> ! {
arch::gdt::init();
arch::idt::init();
+ arch::paging::test_cow();
+
test!(global_allocator);
test!(guard_page);
test!(find_mp);
diff --git a/src/memory/mod.rs b/src/memory/mod.rs
index 36f5f61..3212924 100644
--- a/src/memory/mod.rs
+++ b/src/memory/mod.rs
@@ -26,6 +26,13 @@ pub fn alloc_frame() -> Frame {
.allocate_frame().expect("no more frame")
}
+// Return true to continue, false to halt
+pub fn page_fault_handler(addr: VirtAddr) -> bool {
+ // Handle copy on write
+ let mut page_table = unsafe { ActivePageTable::new() };
+ page_table.try_copy_on_write(addr)
+}
+
pub fn init(boot_info: BootInformation) -> MemoryController {
assert_has_not_been_called!("memory::init must be called only once");