From bf3fe99c7b7540022bf479a7f543140c5122847b Mon Sep 17 00:00:00 2001 From: Yu Chen Date: Thu, 12 Aug 2021 09:33:45 +0800 Subject: [PATCH 1/2] update README.md --- README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a15be7..1da773d 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,43 @@ mdbook serve docs rustc 1.56.0-nightly (08095fc1f 2021-07-26) ``` +## 学习顺序建议 + +### 初步了解 + +1. 阅读有关fuchsia/zircon的概述/简介文章,如 https://zh.wikipedia.org/zh-hans/Google_Fuchsia + +2. 阅读 https://fuchsia.dev/fuchsia-src/concepts/kernel 了解zircon基本思想 + +3. 阅读潘庆霖毕设论文前两章,了解zCore的基本思想 + +### 逐渐深入 +1. 阅读 https://fuchsia.dev/fuchsia-src/reference/syscalls 了解应用程序对Kernel的需求 +2. 阅读 https://fuchsia.dev/fuchsia-src/reference/kernel_objects/objects 了解Kernel中各种object的含义和行为 + +### 理解设计实现 + +1. 阅读&分析本项目中的文档和代码,并对照上面的kernel概念,了解kernel概念和设计实现的对应关系 + +### 动手实践 + +1. 在分析和理解的基础上,改进本项目对应章节的文档 + +2. 在分析和理解的基础上,改进/优化本项目的代码,增加测试用例,增加功能 + +3. 在大致掌握本项目后,通过进一步理解和改进zCore,对zCore等新型操作系统有很好的感悟,提升自身实践能力 + + + ## 参考 + - https://fuchsia.dev/ - https://fuchsia.dev/fuchsia-src/concepts/kernel - https://fuchsia.dev/fuchsia-src/reference/kernel_objects/objects - https://fuchsia.dev/fuchsia-src/reference/syscalls - https://github.com/zhangpf/fuchsia-docs-zh-CN/tree/master/zircon - [许中兴博士演讲:Fuchsia OS 简介](https://xuzhongxing.github.io/201806fuchsia.pdf) - + - 毕设论文 - [Rust语言操作系统的设计与实现,王润基本科毕设论文,2019](https://github.com/rcore-os/zCore/wiki/files/wrj-thesis.pdf) - [zCore操作系统内核的设计与实现,潘庆霖本科毕设论文,2020](https://github.com/rcore-os/zCore/wiki/files/pql-thesis.pdf) From 66e3f8b697ca832094fac84b54127c0e4ef94b64 Mon Sep 17 00:00:00 2001 From: hwangwy <1183598761@qq.com> Date: Mon, 23 Aug 2021 10:31:40 +0800 Subject: [PATCH 2/2] docs: Finished a simple ch03 docs Signed-off-by: hwangwy <1183598761@qq.com> --- docs/.gitignore | 1 + docs/src/ch03-01-zircon-memory.md | 1 + docs/src/ch03-02-vmo.md | 339 ++++++++++++++++++++++++++ docs/src/ch03-03-vmo-paged.md | 381 ++++++++++++++++++++++++++++++ docs/src/ch03-04-vmar.md | 252 ++++++++++++++++++++ docs/src/img/mmap.png | Bin 0 -> 37142 bytes 6 files changed, 974 insertions(+) create mode 100644 docs/src/img/mmap.png diff --git a/docs/.gitignore b/docs/.gitignore index 7585238..fcbea52 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ book +.DS_Store \ No newline at end of file diff --git a/docs/src/ch03-01-zircon-memory.md b/docs/src/ch03-01-zircon-memory.md index 146173c..711e72c 100644 --- a/docs/src/ch03-01-zircon-memory.md +++ b/docs/src/ch03-01-zircon-memory.md @@ -1 +1,2 @@ # Zircon 内存管理模型 +Zircon 中有两个跟内存管理有关的对象 VMO(Virtual Memory Object)和 VMAR (Virtual Memory Address Region)。VMO 主要负责管理物理内存页面,VMAR 主要负责进程虚拟内存管理。当创建一个进程的时候,需要使用到内存的时候,都需要创建一个 VMO,然后将这个 VMO map 到 VMAR上面。 diff --git a/docs/src/ch03-02-vmo.md b/docs/src/ch03-02-vmo.md index 9da0eae..491ff10 100644 --- a/docs/src/ch03-02-vmo.md +++ b/docs/src/ch03-02-vmo.md @@ -14,6 +14,188 @@ > 实现 VmObject 结构,其中定义 VmObjectTrait 接口,并提供三个具体实现 Paged, Physical, Slice +VmObject 结构体 + +```rust +// vm/vmo/mod.rs +pub struct VmObject { + base: KObjectBase, + resizable: bool, + trait_: Arc, + inner: Mutex, +} + +impl_kobject!(VmObject); + +#[derive(Default)] +struct VmObjectInner { + parent: Weak, + children: Vec>, + mapping_count: usize, + content_size: usize, +} +``` +`trait_` 指向实现了 VMObjectTrait 的对象,它由三个具体实现,分别是 VMObjectPage, VMObjectPhysical, VMObjectSlice。VMObjectPaged 是按页分配内存,VMObjectSlice 主要用于共享内存,VMObjectPhysical 在 zCore-Tutorial 中暂时不会使用到。 +`mapping_count` 表示这个 VmObject 被 map 到几个 VMAR 中。 +`content_size` 是分配的物理内存的大小。 +VmObjectTrait 定义了一组 VMObject* 共有的方法 +```rust +pub trait VMObjectTrait: Sync + Send { + /// Read memory to `buf` from VMO at `offset`. + fn read(&self, offset: usize, buf: &mut [u8]) -> ZxResult; + + /// Write memory from `buf` to VMO at `offset`. + fn write(&self, offset: usize, buf: &[u8]) -> ZxResult; + + /// Resets the range of bytes in the VMO from `offset` to `offset+len` to 0. + fn zero(&self, offset: usize, len: usize) -> ZxResult; + + /// Get the length of VMO. + fn len(&self) -> usize; + + /// Set the length of VMO. + fn set_len(&self, len: usize) -> ZxResult; + + /// Commit a page. + fn commit_page(&self, page_idx: usize, flags: MMUFlags) -> ZxResult; + + /// Commit pages with an external function f. + /// the vmo is internally locked before it calls f, + /// allowing `VmMapping` to avoid deadlock + fn commit_pages_with( + &self, + f: &mut dyn FnMut(&mut dyn FnMut(usize, MMUFlags) -> ZxResult) -> ZxResult, + ) -> ZxResult; + + /// Commit allocating physical memory. + fn commit(&self, offset: usize, len: usize) -> ZxResult; + + /// Decommit allocated physical memory. + fn decommit(&self, offset: usize, len: usize) -> ZxResult; + + /// Create a child VMO. + fn create_child(&self, offset: usize, len: usize) -> ZxResult>; + + /// Append a mapping to the VMO's mapping list. + fn append_mapping(&self, _mapping: Weak) {} + + /// Remove a mapping from the VMO's mapping list. + fn remove_mapping(&self, _mapping: Weak) {} + + /// Complete the VmoInfo. + fn complete_info(&self, info: &mut VmoInfo); + + /// Get the cache policy. + fn cache_policy(&self) -> CachePolicy; + + /// Set the cache policy. + fn set_cache_policy(&self, policy: CachePolicy) -> ZxResult; + + /// Count committed pages of the VMO. + fn committed_pages_in_range(&self, start_idx: usize, end_idx: usize) -> usize; + + /// Pin the given range of the VMO. + fn pin(&self, _offset: usize, _len: usize) -> ZxResult { + Err(ZxError::NOT_SUPPORTED) + } + + /// Unpin the given range of the VMO. + fn unpin(&self, _offset: usize, _len: usize) -> ZxResult { + Err(ZxError::NOT_SUPPORTED) + } + + /// Returns true if the object is backed by a contiguous range of physical memory. + fn is_contiguous(&self) -> bool { + false + } + + /// Returns true if the object is backed by RAM. + fn is_paged(&self) -> bool { + false + } +} +``` +`read()` 和 `write()` 用于读和写,`zero()` 用于清空一段内存。 +比较特别的是:`fn commit_page(&self, page_idx: usize, flags: MMUFlags) -> ZxResult;`,`fn commit(&self, offset: usize, len: usize) -> ZxResult;` 和 `fn commit(&self, offset: usize, len: usize) -> ZxResult;` 主要用于分配物理内存,因为一些内存分配策略,物理内存并不一定是马上分配的,所以需要 commit 来分配一块内存。 +`pin` 和 `unpin` 在这里主要用于增加和减少引用计数。 +VmObject 实现了不同的 new 方法,它们之间的差别在于实现 trait_ 的对象不同。 +```rust +impl VmObject { + /// Create a new VMO backing on physical memory allocated in pages. + pub fn new_paged(pages: usize) -> Arc { + Self::new_paged_with_resizable(false, pages) + } + + /// Create a new VMO, which can be resizable, backing on physical memory allocated in pages. + pub fn new_paged_with_resizable(resizable: bool, pages: usize) -> Arc { + let base = KObjectBase::new(); + Arc::new(VmObject { + resizable, + trait_: VMObjectPaged::new(pages), + inner: Mutex::new(VmObjectInner::default()), + base, + }) + } + + /// Create a new VMO representing a piece of contiguous physical memory. + pub fn new_physical(paddr: PhysAddr, pages: usize) -> Arc { + Arc::new(VmObject { + base: KObjectBase::new(), + resizable: false, + trait_: VMObjectPhysical::new(paddr, pages), + inner: Mutex::new(VmObjectInner::default()), + }) + } + + /// Create a VM object referring to a specific contiguous range of physical frame. + pub fn new_contiguous(pages: usize, align_log2: usize) -> ZxResult> { + let vmo = Arc::new(VmObject { + base: KObjectBase::new(), + resizable: false, + trait_: VMObjectPaged::new_contiguous(pages, align_log2)?, + inner: Mutex::new(VmObjectInner::default()), + }); + Ok(vmo) + } +} +``` +通过 `pub fn create_child(self: &Arc, resizable: bool, offset: usize, len: usize)` 可以创建一个 VMObject 的快照副本。 +```rust +impl VmObject { + /// Create a child VMO. + pub fn create_child( + self: &Arc, + resizable: bool, + offset: usize, + len: usize, + ) -> ZxResult> { + // Create child VmObject + let base = KObjectBase::with_name(&self.base.name()); + let trait_ = self.trait_.create_child(offset, len)?; + let child = Arc::new(VmObject { + base, + resizable, + trait_, + inner: Mutex::new(VmObjectInner { + parent: Arc::downgrade(self), + ..VmObjectInner::default() + }), + }); + // Add child VmObject to this VmObject + self.add_child(&child); + Ok(child) + } + + /// Add child to the list + fn add_child(&self, child: &Arc) { + let mut inner = self.inner.lock(); + // 判断这个 child VmObject 是否还是存在,通过获取子对象的强引用数来判断 + inner.children.retain(|x| x.strong_count() != 0); + // downgrade 将 Arc 转为 Weak + inner.children.push(Arc::downgrade(child)); + } +} +``` ## HAL:用文件模拟物理内存 > 初步介绍 mmap,引出用文件模拟物理内存的思想 @@ -21,11 +203,168 @@ > 创建文件并用 mmap 线性映射到进程地址空间 > > 实现 pmem_read, pmem_write +### mmap +mmap是一种内存映射文件的方法,将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址一一对应的关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。因此,新建一个文件,然后调用 mmap,其实就相当于分配了一块物理内存,因此我们可以用文件来模拟物理内存。 +![mmap.png](img/mmap.png) +### 分配地址空间 +创建一个文件用于 mmap 系统调用。 +```rust +fn create_pmem_file() -> File { + let dir = tempdir().expect("failed to create pmem dir"); + let path = dir.path().join("pmem"); + + // workaround on macOS to avoid permission denied. + // see https://jiege.ch/software/2020/02/07/macos-mmap-exec/ for analysis on this problem. + #[cfg(target_os = "macos")] + std::mem::forget(dir); + + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&path) + .expect("failed to create pmem file"); + file.set_len(PMEM_SIZE as u64) + .expect("failed to resize file"); + trace!("create pmem file: path={:?}, size={:#x}", path, PMEM_SIZE); + let prot = libc::PROT_READ | libc::PROT_WRITE; + // 调用 mmap (这个不是系统调用)进行文件和内存之间的双向映射 + mmap(file.as_raw_fd(), 0, PMEM_SIZE, phys_to_virt(0), prot); + file +} +``` +mmap: +```rust +/// Mmap frame file `fd` to `vaddr`. +fn mmap(fd: libc::c_int, offset: usize, len: usize, vaddr: VirtAddr, prot: libc::c_int) { + // 根据不同的操作系统去修改权限 + // workaround on macOS to write text section. + #[cfg(target_os = "macos")] + let prot = if prot & libc::PROT_EXEC != 0 { + prot | libc::PROT_WRITE + } else { + prot + }; + + // 调用 mmap 系统调用,ret 为返回值 + let ret = unsafe { + let flags = libc::MAP_SHARED | libc::MAP_FIXED; + libc::mmap(vaddr as _, len, prot, flags, fd, offset as _) + } as usize; + trace!( + "mmap file: fd={}, offset={:#x}, len={:#x}, vaddr={:#x}, prot={:#b}", + fd, + offset, + len, + vaddr, + prot, + ); + assert_eq!(ret, vaddr, "failed to mmap: {:?}", Error::last_os_error()); +} +``` +最后创建一个全局变量保存这个分配的内存 +```rust +lazy_static! { + static ref FRAME_FILE: File = create_pmem_file(); +} +``` +### pmem_read 和 pmem_write +```rust +/// Read physical memory from `paddr` to `buf`. +#[export_name = "hal_pmem_read"] +pub fn pmem_read(paddr: PhysAddr, buf: &mut [u8]) { + trace!("pmem read: paddr={:#x}, len={:#x}", paddr, buf.len()); + assert!(paddr + buf.len() <= PMEM_SIZE); + ensure_mmap_pmem(); + unsafe { + (phys_to_virt(paddr) as *const u8).copy_to_nonoverlapping(buf.as_mut_ptr(), buf.len()); + } +} +/// Write physical memory to `paddr` from `buf`. +#[export_name = "hal_pmem_write"] +pub fn pmem_write(paddr: PhysAddr, buf: &[u8]) { + trace!("pmem write: paddr={:#x}, len={:#x}", paddr, buf.len()); + assert!(paddr + buf.len() <= PMEM_SIZE); + ensure_mmap_pmem(); + unsafe { + buf.as_ptr() + .copy_to_nonoverlapping(phys_to_virt(paddr) as _, buf.len()); + } +} + +/// Ensure physical memory are mmapped and accessible. +fn ensure_mmap_pmem() { + FRAME_FILE.as_raw_fd(); +} +``` +`ensure_mmap_pmem()` 确保物理内存已经映射 +`copy_to_nonoverlapping(self, dst *mut T, count: usize)` 将 self 的字节序列拷贝到 dst 中,source 和 destination 是不互相重叠的。`(phys_to_virt(paddr) as *const u8).copy_to_nonoverlapping(buf.as_mut_ptr(), buf.len());` 通过 `phys_to_virt(paddr)` 将 paddr 加上 PMEM_BASE 转为虚拟地址,然后将里面的字节拷贝到 buf 里面。 ## 实现物理内存 VMO > 用 HAL 实现 VmObjectPhysical 的方法,并做单元测试 +物理内存 VMO 结构体: +```rust +pub struct VMObjectPhysical { + paddr: PhysAddr, + pages: usize, + /// Lock this when access physical memory. + data_lock: Mutex<()>, + inner: Mutex, +} +struct VMObjectPhysicalInner { + cache_policy: CachePolicy, +} +``` +这里比较奇怪的是 data_lock 这个字段,这个字段里 Mutex 的泛型类型是一个 unit type,其实相当于它是没有“值”的,它只是起到一个锁的作用。 +```rust +impl VMObjectTrait for VMObjectPhysical { + fn read(&self, offset: usize, buf: &mut [u8]) -> ZxResult { + let _ = self.data_lock.lock(); // 先获取锁 + assert!(offset + buf.len() <= self.len()); + kernel_hal::pmem_read(self.paddr + offset, buf); // 对一块物理内存进行读 + Ok(()) + } +} +``` ## 实现切片 VMO > 实现 VmObjectSlice,并做单元测试 +VMObjectSlice 中的 parent 用于指向一个实际的 VMO 对象,比如:VMObjectPaged,这样通过 VMObjectSlice 就可以实现对 VMObjectPaged 的共享。 +```rust +pub struct VMObjectSlice { + /// Parent node. + parent: Arc, + /// The offset from parent. + offset: usize, + /// The size in bytes. + size: usize, +} + +impl VMObjectSlice { + pub fn new(parent: Arc, offset: usize, size: usize) -> Arc { + Arc::new(VMObjectSlice { + parent, + offset, + size, + }) + } + + fn check_range(&self, offset: usize, len: usize) -> ZxResult { + if offset + len >= self.size { + return Err(ZxError::OUT_OF_RANGE); + } + Ok(()) + } +} +``` +VMObjectSlice 实现的读写,第一步是 `check_range` ,第二步是调用 parent 中的读写方法。 +```rust +impl VMObjectTrait for VMObjectSlice { + fn read(&self, offset: usize, buf: &mut [u8]) -> ZxResult { + self.check_range(offset, buf.len())?; + self.parent.read(offset + self.offset, buf) + } +} +``` \ No newline at end of file diff --git a/docs/src/ch03-03-vmo-paged.md b/docs/src/ch03-03-vmo-paged.md index 58c3a7a..a27cc0b 100644 --- a/docs/src/ch03-03-vmo-paged.md +++ b/docs/src/ch03-03-vmo-paged.md @@ -7,18 +7,399 @@ > > 介绍 commit 操作的意义和作用 +commit_page 和 commit_pages_with 函数的作用:用于检查物理页帧是否已经分配。 + ## HAL:物理内存管理 > 在 HAL 中实现 PhysFrame 和最简单的分配器 +### kernel-hal +```rust +#[repr(C)] +pub struct PhysFrame { + // paddr 物理地址 + paddr: PhysAddr, +} + +impl PhysFrame { + // 分配物理页帧 + #[linkage = "weak"] + #[export_name = "hal_frame_alloc"] + pub fn alloc() -> Option { + unimplemented!() + } + + #[linkage = "weak"] + #[export_name = "hal_frame_alloc_contiguous"] + pub fn alloc_contiguous_base(_size: usize, _align_log2: usize) -> Option { + unimplemented!() + } + + pub fn alloc_contiguous(size: usize, align_log2: usize) -> Vec { + PhysFrame::alloc_contiguous_base(size, align_log2).map_or(Vec::new(), |base| { + (0..size) + .map(|i| PhysFrame { + paddr: base + i * PAGE_SIZE, + }) + .collect() + }) + } + + pub fn alloc_zeroed() -> Option { + Self::alloc().map(|f| { + pmem_zero(f.addr(), PAGE_SIZE); + f + }) + } + + pub fn alloc_contiguous_zeroed(size: usize, align_log2: usize) -> Vec { + PhysFrame::alloc_contiguous_base(size, align_log2).map_or(Vec::new(), |base| { + pmem_zero(base, size * PAGE_SIZE); + (0..size) + .map(|i| PhysFrame { + paddr: base + i * PAGE_SIZE, + }) + .collect() + }) + } + + pub fn addr(&self) -> PhysAddr { + self.paddr + } + + #[linkage = "weak"] + #[export_name = "hal_zero_frame_paddr"] + pub fn zero_frame_addr() -> PhysAddr { + unimplemented!() + } +} + +impl Drop for PhysFrame { + #[linkage = "weak"] + #[export_name = "hal_frame_dealloc"] + fn drop(&mut self) { + unimplemented!() + } +} +``` +### kernel-hal-unix +通过下面的代码可以构造一个页帧号。`(PAGE_SIZE..PMEM_SIZE).step_by(PAGE_SIZE).collect()` 可以每隔 PAGE_SIZE 生成一个页帧的开始位置。 +```rust +lazy_static! { + static ref AVAILABLE_FRAMES: Mutex> = + Mutex::new((PAGE_SIZE..PMEM_SIZE).step_by(PAGE_SIZE).collect()); +} +``` +分配一块物理页帧就是从 AVAILABLE_FRAMES 中通过 pop_front 弹出一个页号 +```rust +impl PhysFrame { + #[export_name = "hal_frame_alloc"] + pub fn alloc() -> Option { + let ret = AVAILABLE_FRAMES + .lock() + .unwrap() + .pop_front() + .map(|paddr| PhysFrame { paddr }); + trace!("frame alloc: {:?}", ret); + ret + } + #[export_name = "hal_zero_frame_paddr"] + pub fn zero_frame_addr() -> PhysAddr { + 0 + } +} + +impl Drop for PhysFrame { + #[export_name = "hal_frame_dealloc"] + fn drop(&mut self) { + trace!("frame dealloc: {:?}", self); + AVAILABLE_FRAMES.lock().unwrap().push_back(self.paddr); + } +} +``` ## 辅助结构:BlockRange 迭代器 > 实现 BlockRange +在按页分配内存的 VMObjectPaged 的读和写的方法中会使用到一个 BlockIter 迭代器。BlockIter 主要用于将一段内存分块,每次返回这一块的信息也就是 BlockRange。 +### BlockIter +```rust +#[derive(Debug, Eq, PartialEq)] +pub struct BlockRange { + pub block: usize, + pub begin: usize, // 块内地址开始位置 + pub end: usize, // 块内地址结束位置 + pub block_size_log2: u8, +} + +/// Given a range and iterate sub-range for each block +pub struct BlockIter { + pub begin: usize, + pub end: usize, + pub block_size_log2: u8, +} +``` +block_size_log2 是 log 以2为底 block size, 比如:block size 大小为4096,则 block_size_log2 为 12。block 是块编号。 +```rust +impl BlockRange { + pub fn len(&self) -> usize { + self.end - self.begin + } + pub fn is_full(&self) -> bool { + self.len() == (1usize << self.block_size_log2) + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn origin_begin(&self) -> usize { + (self.block << self.block_size_log2) + self.begin + } + pub fn origin_end(&self) -> usize { + (self.block << self.block_size_log2) + self.end + } +} + +impl Iterator for BlockIter { + type Item = BlockRange; + + fn next(&mut self) -> Option<::Item> { + if self.begin >= self.end { + return None; + } + let block_size_log2 = self.block_size_log2; + let block_size = 1usize << self.block_size_log2; + let block = self.begin / block_size; + let begin = self.begin % block_size; + // 只有最后一块需要计算块内最后的地址,其他的直接返回块的大小 + let end = if block == self.end / block_size { + self.end % block_size + } else { + block_size + }; + self.begin += end - begin; + Some(BlockRange { + block, + begin, + end, + block_size_log2, + }) + } +} +``` ## 实现按页分配的 VMO > 实现 for_each_page, commit, read, write 函数 +按页分配的 VMO 结构体如下: +```rust +pub struct VMObjectPaged { + inner: Mutex, +} + +/// The mutable part of `VMObjectPaged`. +#[derive(Default)] +struct VMObjectPagedInner { + /// Physical frames of this VMO. + frames: Vec, + /// Cache Policy + cache_policy: CachePolicy, + /// Is contiguous + contiguous: bool, + /// Sum of pin_count + pin_count: usize, + /// All mappings to this VMO. + mappings: Vec>, +} +``` +VMObjectPage 有两个 new 方法 +```rust +impl VMObjectPaged { + /// Create a new VMO backing on physical memory allocated in pages. + pub fn new(pages: usize) -> Arc { + let mut frames = Vec::new(); + frames.resize_with(pages, || PhysFrame::alloc_zeroed().unwrap()); // 分配 pages 个页帧号,并将这些页帧号的内存清零 + Arc::new(VMObjectPaged { + inner: Mutex::new(VMObjectPagedInner { + frames, + ..Default::default() + }), + }) + } + + /// Create a list of contiguous pages + pub fn new_contiguous(pages: usize, align_log2: usize) -> ZxResult> { + let frames = PhysFrame::alloc_contiguous_zeroed(pages, align_log2 - PAGE_SIZE_LOG2); + if frames.is_empty() { + return Err(ZxError::NO_MEMORY); + } + Ok(Arc::new(VMObjectPaged { + inner: Mutex::new(VMObjectPagedInner { + frames, + contiguous: true, + ..Default::default() + }), + })) + } +} +``` +VMObjectPaged 的读和写用到了一个非常重要的函数 for_each_page 。首先它先构造了一个 BlockIter 迭代器,然后调用传入的函数进行读或者写。 +```rust +impl VMObjectPagedInner { + /// Helper function to split range into sub-ranges within pages. + /// + /// ```text + /// VMO range: + /// |----|----|----|----|----| + /// + /// buf: + /// [====len====] + /// |--offset--| + /// + /// sub-ranges: + /// [===] + /// [====] + /// [==] + /// ``` + /// + /// `f` is a function to process in-page ranges. + /// It takes 2 arguments: + /// * `paddr`: the start physical address of the in-page range. + /// * `buf_range`: the range in view of the input buffer. + fn for_each_page( + &mut self, + offset: usize, + buf_len: usize, + mut f: impl FnMut(PhysAddr, Range), + ) { + let iter = BlockIter { + begin: offset, + end: offset + buf_len, + block_size_log2: 12, + }; + for block in iter { + // 获取这一块开始的物理地址 + let paddr = self.frames[block.block].addr(); + // 这块物理地址的范围 + let buf_range = block.origin_begin() - offset..block.origin_end() - offset; + f(paddr + block.begin, buf_range); + } + } +} +``` +read 和 write 函数,一个传入的是 `kernel_hal::pmem_read` ,另外一个是 `kernel_hal::pmem_write` +```rust +impl VMObjectTrait for VMObjectPaged { + fn read(&self, offset: usize, buf: &mut [u8]) -> ZxResult { + let mut inner = self.inner.lock(); + if inner.cache_policy != CachePolicy::Cached { + return Err(ZxError::BAD_STATE); + } + inner.for_each_page(offset, buf.len(), |paddr, buf_range| { + kernel_hal::pmem_read(paddr, &mut buf[buf_range]); + }); + Ok(()) + } + + fn write(&self, offset: usize, buf: &[u8]) -> ZxResult { + let mut inner = self.inner.lock(); + if inner.cache_policy != CachePolicy::Cached { + return Err(ZxError::BAD_STATE); + } + inner.for_each_page(offset, buf.len(), |paddr, buf_range| { + kernel_hal::pmem_write(paddr, &buf[buf_range]); + }); + Ok(()) + } +} +``` +commit 函数 +```rust +impl VMObjectTrait for VMObjectPaged { + fn commit_page(&self, page_idx: usize, _flags: MMUFlags) -> ZxResult { + let inner = self.inner.lock(); + Ok(inner.frames[page_idx].addr()) + } + + fn commit_pages_with( + &self, + f: &mut dyn FnMut(&mut dyn FnMut(usize, MMUFlags) -> ZxResult) -> ZxResult, + ) -> ZxResult { + let inner = self.inner.lock(); + f(&mut |page_idx, _| Ok(inner.frames[page_idx].addr())) + } +} +``` ## VMO 复制 > 实现 create_child 函数 + +create_child 是将原 VMObjectPaged 的内容拷贝一份 +```rust +// object/vm/vmo/paged.rs + +impl VMObjectTrait for VMObjectPaged { + fn create_child(&self, offset: usize, len: usize) -> ZxResult> { + assert!(page_aligned(offset)); + assert!(page_aligned(len)); + let mut inner = self.inner.lock(); + let child = inner.create_child(offset, len)?; + Ok(child) + } + + /// Create a snapshot child VMO. + fn create_child(&mut self, offset: usize, len: usize) -> ZxResult> { + // clone contiguous vmo is no longer permitted + // https://fuchsia.googlesource.com/fuchsia/+/e6b4c6751bbdc9ed2795e81b8211ea294f139a45 + if self.contiguous { + return Err(ZxError::INVALID_ARGS); + } + if self.cache_policy != CachePolicy::Cached || self.pin_count != 0 { + return Err(ZxError::BAD_STATE); + } + let mut frames = Vec::with_capacity(pages(len)); + for _ in 0..pages(len) { + frames.push(PhysFrame::alloc().ok_or(ZxError::NO_MEMORY)?); + } + for (i, frame) in frames.iter().enumerate() { + if let Some(src_frame) = self.frames.get(pages(offset) + i) { + kernel_hal::frame_copy(src_frame.addr(), frame.addr()) + } else { + kernel_hal::pmem_zero(frame.addr(), PAGE_SIZE); + } + } + // create child VMO + let child = Arc::new(VMObjectPaged { + inner: Mutex::new(VMObjectPagedInner { + frames, + ..Default::default() + }), + }); + Ok(child) + } +} + +// kernel-hal-unix/sr/lib.rs + +/// Copy content of `src` frame to `target` frame +#[export_name = "hal_frame_copy"] +pub fn frame_copy(src: PhysAddr, target: PhysAddr) { + trace!("frame_copy: {:#x} <- {:#x}", target, src); + assert!(src + PAGE_SIZE <= PMEM_SIZE && target + PAGE_SIZE <= PMEM_SIZE); + ensure_mmap_pmem(); + unsafe { + let buf = phys_to_virt(src) as *const u8; + buf.copy_to_nonoverlapping(phys_to_virt(target) as _, PAGE_SIZE); + } +} + +/// Zero physical memory at `[paddr, paddr + len)` +#[export_name = "hal_pmem_zero"] +pub fn pmem_zero(paddr: PhysAddr, len: usize) { + trace!("pmem_zero: addr={:#x}, len={:#x}", paddr, len); + assert!(paddr + len <= PMEM_SIZE); + ensure_mmap_pmem(); + unsafe { + core::ptr::write_bytes(phys_to_virt(paddr) as *mut u8, 0, len); + } +} +``` diff --git a/docs/src/ch03-04-vmar.md b/docs/src/ch03-04-vmar.md index 5cf4539..8520fd6 100644 --- a/docs/src/ch03-04-vmar.md +++ b/docs/src/ch03-04-vmar.md @@ -10,10 +10,262 @@ > > 实现 create_child, map, unmap, destroy 函数,并做单元测试验证地址空间分配 +### VmAddressRegion +```rust +pub struct VmAddressRegion { + flags: VmarFlags, + base: KObjectBase, + addr: VirtAddr, + size: usize, + parent: Option>, + page_table: Arc>, + /// If inner is None, this region is destroyed, all operations are invalid. + inner: Mutex>, +} + +#[derive(Default)] +struct VmarInner { + children: Vec>, + mappings: Vec>, +} +``` +构造一个根节点 VMAR,这个 VMAR 是每个进程都拥有的。 +```rust +impl VmAddressRegion { + /// Create a new root VMAR. + pub fn new_root() -> Arc { + let (addr, size) = { + use core::sync::atomic::*; + static VMAR_ID: AtomicUsize = AtomicUsize::new(0); + let i = VMAR_ID.fetch_add(1, Ordering::SeqCst); + (0x2_0000_0000 + 0x100_0000_0000 * i, 0x100_0000_0000) + }; + Arc::new(VmAddressRegion { + flags: VmarFlags::ROOT_FLAGS, + base: KObjectBase::new(), + addr, + size, + parent: None, + page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())), //hal PageTable + inner: Mutex::new(Some(VmarInner::default())), + }) + } +} +``` +我们的内核同样需要一个根 VMAR +```rust +/// The base of kernel address space +/// In x86 fuchsia this is 0xffff_ff80_0000_0000 instead +pub const KERNEL_ASPACE_BASE: u64 = 0xffff_ff02_0000_0000; +/// The size of kernel address space +pub const KERNEL_ASPACE_SIZE: u64 = 0x0000_0080_0000_0000; +/// The base of user address space +pub const USER_ASPACE_BASE: u64 = 0; +// pub const USER_ASPACE_BASE: u64 = 0x0000_0000_0100_0000; +/// The size of user address space +pub const USER_ASPACE_SIZE: u64 = (1u64 << 47) - 4096 - USER_ASPACE_BASE; + +impl VmAddressRegion { + /// Create a kernel root VMAR. + pub fn new_kernel() -> Arc { + let kernel_vmar_base = KERNEL_ASPACE_BASE as usize; + let kernel_vmar_size = KERNEL_ASPACE_SIZE as usize; + Arc::new(VmAddressRegion { + flags: VmarFlags::ROOT_FLAGS, + base: KObjectBase::new(), + addr: kernel_vmar_base, + size: kernel_vmar_size, + parent: None, + page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())), + inner: Mutex::new(Some(VmarInner::default())), + }) + } +} +``` +### VmAddressMapping +VmAddressMapping 用于建立 VMO 和 VMAR 之间的映射。 +```rust +/// Virtual Memory Mapping +pub struct VmMapping { + /// The permission limitation of the vmar + permissions: MMUFlags, + vmo: Arc, + page_table: Arc>, + inner: Mutex, +} + +#[derive(Debug, Clone)] +struct VmMappingInner { + /// The actual flags used in the mapping of each page + flags: Vec, + addr: VirtAddr, + size: usize, + vmo_offset: usize, +} +``` +map 和 unmap 实现内存映射和解映射 +```rust +impl VmMapping { + /// Map range and commit. + /// Commit pages to vmo, and map those to frames in page_table. + /// Temporarily used for development. A standard procedure for + /// vmo is: create_vmo, op_range(commit), map + fn map(self: &Arc) -> ZxResult { + self.vmo.commit_pages_with(&mut |commit| { + let inner = self.inner.lock(); + let mut page_table = self.page_table.lock(); + let page_num = inner.size / PAGE_SIZE; + let vmo_offset = inner.vmo_offset / PAGE_SIZE; + for i in 0..page_num { + let paddr = commit(vmo_offset + i, inner.flags[i])?; + //通过 PageTableTrait 的 hal_pt_map 进行页表映射 + //调用 kernel-hal的方法进行映射 + } + Ok(()) + }) + } + + fn unmap(&self) { + let inner = self.inner.lock(); + let pages = inner.size / PAGE_SIZE; + // TODO inner.vmo_offset unused? + // 调用 kernel-hal的方法进行解映射 + } +} +``` ## HAL:用 mmap 模拟页表 > 实现页表接口 map, unmap, protect +在 kernel-hal 中定义了一个页表和这个页表具有的方法。 +```rust +/// Page Table +#[repr(C)] +pub struct PageTable { + table_phys: PhysAddr, +} + +impl PageTable { + /// Get current page table + #[linkage = "weak"] + #[export_name = "hal_pt_current"] + pub fn current() -> Self { + unimplemented!() + } + + /// Create a new `PageTable`. + #[allow(clippy::new_without_default)] + #[linkage = "weak"] + #[export_name = "hal_pt_new"] + pub fn new() -> Self { + unimplemented!() + } +} + +impl PageTableTrait for PageTable { + /// Map the page of `vaddr` to the frame of `paddr` with `flags`. + #[linkage = "weak"] + #[export_name = "hal_pt_map"] + fn map(&mut self, _vaddr: VirtAddr, _paddr: PhysAddr, _flags: MMUFlags) -> Result<()> { + unimplemented!() + } + /// Unmap the page of `vaddr`. + #[linkage = "weak"] + #[export_name = "hal_pt_unmap"] + fn unmap(&mut self, _vaddr: VirtAddr) -> Result<()> { + unimplemented!() + } + /// Change the `flags` of the page of `vaddr`. + #[linkage = "weak"] + #[export_name = "hal_pt_protect"] + fn protect(&mut self, _vaddr: VirtAddr, _flags: MMUFlags) -> Result<()> { + unimplemented!() + } + /// Query the physical address which the page of `vaddr` maps to. + #[linkage = "weak"] + #[export_name = "hal_pt_query"] + fn query(&mut self, _vaddr: VirtAddr) -> Result { + unimplemented!() + } + /// Get the physical address of root page table. + #[linkage = "weak"] + #[export_name = "hal_pt_table_phys"] + fn table_phys(&self) -> PhysAddr { + self.table_phys + } + + /// Activate this page table + #[cfg(target_arch = "riscv64")] + #[linkage = "weak"] + #[export_name = "hal_pt_activate"] + fn activate(&self) { + unimplemented!() + } + + #[linkage = "weak"] + #[export_name = "hal_pt_unmap_cont"] + fn unmap_cont(&mut self, vaddr: VirtAddr, pages: usize) -> Result<()> { + for i in 0..pages { + self.unmap(vaddr + i * PAGE_SIZE)?; + } + Ok(()) + } +} +``` +在 kernel-hal-unix 中实现了 PageTableTrait,在 map 中调用了 mmap。 +```rust +impl PageTableTrait for PageTable { + /// Map the page of `vaddr` to the frame of `paddr` with `flags`. + #[export_name = "hal_pt_map"] + fn map(&mut self, vaddr: VirtAddr, paddr: PhysAddr, flags: MMUFlags) -> Result<()> { + debug_assert!(page_aligned(vaddr)); + debug_assert!(page_aligned(paddr)); + let prot = flags.to_mmap_prot(); + mmap(FRAME_FILE.as_raw_fd(), paddr, PAGE_SIZE, vaddr, prot); + Ok(()) + } + + /// Unmap the page of `vaddr`. + #[export_name = "hal_pt_unmap"] + fn unmap(&mut self, vaddr: VirtAddr) -> Result<()> { + self.unmap_cont(vaddr, 1) + } +} +``` ## 实现内存映射 > 用 HAL 实现上面 VMAR 留空的部分,并做单元测试验证内存映射 +```rust +impl VmMapping { + /// Map range and commit. + /// Commit pages to vmo, and map those to frames in page_table. + /// Temporarily used for development. A standard procedure for + /// vmo is: create_vmo, op_range(commit), map + fn map(self: &Arc) -> ZxResult { + self.vmo.commit_pages_with(&mut |commit| { + let inner = self.inner.lock(); + let mut page_table = self.page_table.lock(); + let page_num = inner.size / PAGE_SIZE; + let vmo_offset = inner.vmo_offset / PAGE_SIZE; + for i in 0..page_num { + let paddr = commit(vmo_offset + i, inner.flags[i])?; + //通过 PageTableTrait 的 hal_pt_map 进行页表映射 + page_table + .map(inner.addr + i * PAGE_SIZE, paddr, inner.flags[i]) + .expect("failed to map"); + } + Ok(()) + }) + } + + fn unmap(&self) { + let inner = self.inner.lock(); + let pages = inner.size / PAGE_SIZE; + // TODO inner.vmo_offset unused? + self.page_table + .lock() + .unmap_cont(inner.addr, pages) + .expect("failed to unmap") + } +} +``` \ No newline at end of file diff --git a/docs/src/img/mmap.png b/docs/src/img/mmap.png new file mode 100644 index 0000000000000000000000000000000000000000..852e576aa79885c898684fcfd6e72bc9f325454d GIT binary patch literal 37142 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