rCore第四章练习
mmap 和 munmap 匿名映射
mmap 在 Linux 中主要用于在内存中映射文件,本次实验简化它的功能,仅用于申请内存。
请实现 mmap 和 munmap 系统调用,mmap 定义如下:
fn sys_mmap(start: usize, len: usize, prot: usize) -> isize
-
syscall ID:222
-
申请长度为
len
字节的物理内存(不要求实际物理内存位置,可以随便找一块),将其映射到start
开始的虚存,内存页属性为prot
-
参数:
start
需要映射的虚存起始地址,要求按页对齐len 映射字节长度,可以为 0。prot
:第 0 位表示是否可读,第 1 位表示是否可写,第 2 位表示是否可执行。其他位无效且必须为 0 -
返回值:执行成功则返回 0,错误返回 -1
-
说明:
为了简单,目标虚存区间要求按页对齐,
len
可直接按页向上取整,不考虑分配失败时的页回收。 -
可能的错误:
start
没有按页大小对齐prot
& !0x7 != 0 (prot
其余位必须为0)prot
& 0x7 = 0 (这样的内存无意义)[start, start + len)
中存在已经被映射的页物理内存不足
munmap
定义如下:
fn sys_munmap(start: usize, len: usize) -> isize
-
syscall ID
:215 -
取消到 [
start
,start + len
) 虚存的映射 -
参数和返回值请参考
mmap
-
说明:
为了简单,参数错误时不考虑内存的恢复和回收。
-
可能的错误:
[start, start + len)
中存在未被映射的虚存。
TIPS
:注意 prot
参数的语义,它与内核定义的 MapPermission
有明显不同!
我的实现
简述
这里需要实现两个系统调用mmap
和munmap
,其中munmap
传入的参数不用考虑页表访问限制,直接将相关的映射删除即可。这里我在os
的syscall
下添加了memory
模块,模块中会直接调用task
模块中的接口函数,接口中使用TaskManager
的方法,这些方法主要的原理是通过调用当前执行的控制块中的memory_set
进行映射和拆除映射。映射的实现较为简单,因为已经检查了这个连续区间内没有已经被映射的虚拟页号,直接调用memory_set
中的insert_framed_area
即可。但是拆除映射会比较复杂,因为拆除映射需要考虑要移除几个area
,是否需要将原本的area
分为两个等问题。这里我记录最左area
和最右area
,将其中的所有区域释放,如果最边上的area
并不是[start, start+len)
中的区域,则再insert
刚才删除过多的部分。
具体代码
impl TaskManager {
//...
fn map(&self, start: usize, len: usize, prot: usize) -> isize {
let mut inner = self.inner.exclusive_access();
let cur = inner.current_task;
let mut map_perm = MapPermission::U;
if (prot & 1) != 0 {
map_perm |= MapPermission::R;
}
if (prot & 2) != 0 {
map_perm |= MapPermission::W;
}
if (prot & 4) != 0 {
map_perm |= MapPermission::X;
}
inner.tasks[cur].memory_set.insert_framed_area(start.into(), (start+len).into(), map_perm);
0
}
}
这里直接根据prot
生成MapPermission
,调用insert_framed_area
方法就完成了map
,当然之前需要进行是否已经map
过的检查。
impl for memory_set {
//...
pub fn unmap(&mut self, start: usize, len: usize) -> isize {
let length = (len + PAGE_SIZE - 1) / PAGE_SIZE;
if length == 0 {
return 0;
}
let start_vaddr: VirtAddr = start.into();
let end_vaddr: VirtAddr = (start+len).into();
let mut left_area_index = 0;
let mut right_area_index = 0;
//获取左右边界
for i in 0..self.areas.len() {
if self.areas[i].vpn_range.get_start() <= end_vaddr.into() &&
self.areas[i].vpn_range.get_end() > end_vaddr.into() {
right_area_index = i;
}
if self.areas[i].vpn_range.get_start() <= start_vaddr.into() &&
self.areas[i].vpn_range.get_end() > start_vaddr.into() {
left_area_index = i;
}
}
let del_region_left = self.areas[left_area_index].vpn_range.get_start();
let del_region_right = self.areas[right_area_index].vpn_range.get_end();
let del_region_left_permission = self.areas[left_area_index].map_perm;
let del_region_right_permission = self.areas[right_area_index].map_perm;
//删除区域判断
for j in 0..self.areas.len() {
if (self.areas[j].vpn_range.get_start() <= del_region_left && del_region_left < self.areas[j].vpn_range.get_end()) ||
(self.areas[j].vpn_range.get_start() <= del_region_right && del_region_right < self.areas[j].vpn_range.get_end()) ||
(self.areas[j].vpn_range.get_start() >= del_region_left && self.areas[j].vpn_range.get_end() <= del_region_right) {
self.areas[j].unmap(&mut self.page_table)
}
}
//多删部分补回
if del_region_left < start.into() {
self.insert_framed_area(del_region_left.into(), start_vaddr.into(), del_region_left_permission);
}
if del_region_right > (start+len).into() {
self.insert_framed_area(end_vaddr.into(), del_region_right.into(), del_region_right_permission);
}
return 0;
}
}
这里要考虑unmap
的区域是否会包括多个area
,是否会删除一个完整的一个area
的问题,我的方法是考虑最左能到哪个area
, 最右到哪个area
,删除之间所有area
,最后判断左右的area
是否应该完全取出,否的话则进行补回。