Cow test pass in OS

master
WangRunji 7 years ago
parent ade0f0110f
commit e47f57a12a

@ -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<T: PageTable> {
pub struct CowExt<T: PageTable> {
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<impl PageTable>) {
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);

@ -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)]

@ -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 {

@ -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() {

@ -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<Frame>);
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<Frame>) {
// 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<BTreeMap<Frame, (u8, u8)>>);
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);
}
}

@ -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 {

@ -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();
}
}

@ -39,13 +39,14 @@ fn alloc_stack(size_in_pages: usize) -> Stack {
.alloc_stack(&mut active_table, size_in_pages).expect("no more stack")
}
/// The only way to get active page table
fn active_table() -> MutexGuard<'static, ActivePageTable> {
lazy_static! {
static ref ACTIVE_TABLE: Mutex<ActivePageTable> = Mutex::new(unsafe {
ActivePageTable::new()
lazy_static! {
static ref ACTIVE_TABLE: Mutex<CowExt<ActivePageTable>> = Mutex::new(unsafe {
CowExt::new(ActivePageTable::new())
});
}
}
/// The only way to get active page table
fn active_table() -> MutexGuard<'static, CowExt<ActivePageTable>> {
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<ElfSectionFlags> for MemoryAttr {
flags
}
}
use super::*;
pub mod test {
pub fn cow() {
use ucore_memory::cow::test::test_with;
test_with(&mut active_table());
}
}
Loading…
Cancel
Save