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 有明显不同!

我的实现

简述

​ 这里需要实现两个系统调用mmapmunmap,其中munmap传入的参数不用考虑页表访问限制,直接将相关的映射删除即可。这里我在ossyscall下添加了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是否应该完全取出,否的话则进行补回。