当前位置: 首页 > news >正文

MIT 6.S081 Lab10 mmap

mmap

任务

mmap和munmap系统调用允许UNIX程序对其地址空间进行详细控制。它们可用于在进程之间共享内存,将文件映射到进程地址空间,以及作为用户级页面错误方案的一部分,如讲座中讨论的垃圾收集算法。在本实验中,您将把mmap和munmap添加到xv6中,重点放在内存映射文件上。

mmap和munmap系统调用允许UNIX程序对其地址空间进行详细控制。它们可用于在进程之间共享内存,将文件映射到进程地址空间,以及作为用户级页面错误方案的一部分,如讲座中讨论的垃圾收集算法。在本实验中,您将把mmap和munmap添加到xv6中,重点放在内存映射文件上。

mmap can be called in many ways, but this lab requires only a subset of its features relevant to memory-mapping a file. You can assume that addr will always be zero, meaning that the kernel should decide the virtual address at which to map the file. mmap returns that address, or 0xffffffffffffffff if it fails. length is the number of bytes to map; it might not be the same as the file’s length. prot indicates whether the memory should be mapped readable, writeable, and/or executable; you can assume that prot is PROT_READ or PROT_WRITE or both. flags will be either MAP_SHARED, meaning that modifications to the mapped memory should be written back to the file, or MAP_PRIVATE, meaning that they should not. You don’t have to implement any other bits in flags. fd is the open file descriptor of the file to map. You can assume offset is zero (it’s the starting point in the file at which to map).

mmap可以通过多种方式调用,但本实验室只需要其与文件内存映射相关的功能的一个子集。您可以假设addr始终为零,这意味着内核应该决定映射文件的虚拟地址。mmap返回该地址,如果失败,则返回0xFFFFFFFFFFF。length是要映射的字节数;它可能与文件的长度不同。prot指示内存是否应该被映射为可读、可写和/或可执行;你可以假设prot是prot_READ或prot_WRITE,或者两者兼而有之。标志将是MAP_SHARED,这意味着对映射内存的修改应该写回文件,或者是MAP_PRIVATE,这意味著它们不应该写回。您不必在标志中实现任何其他位。fd是要映射的文件的打开文件描述符。您可以假设偏移量为零(这是文件中映射的起点)。

t’s OK if processes that map the same MAP_SHARED file do not share physical pages.
munmap(addr, length) should remove mmap mappings in the indicated address range. If the process has modified the memory and has it mapped MAP_SHARED, the modifications should first be written to the file. An munmap call might cover only a portion of an mmap-ed region, but you can assume that it will either unmap at the start, or at the end, or the whole region (but not punch a hole in the middle of a region).

如果映射同一map_SHARED文件的进程不共享物理页面,则没关系。
munmap(addr,length)应删除指定地址范围内的mmap映射。如果进程修改了内存并将其映射为MAP_SHARED,则应首先将修改写入文件。munmap调用可能只覆盖mmap-ed区域的一部分,但您可以假设它将在开始、结束或整个区域取消映射(但不能在区域中间打孔)。

You should implement enough mmap and munmap functionality to make the mmaptest test program work. If mmaptest doesn’t use a mmap feature, you don’t need to implement that feature.

您应该实现足够的mmap和munmap功能,以使mmaptest测试程序正常工作。如果mmaptest不使用mmap功能,则不需要实现该功能。

提示

  • 首先,在 UPROGS 中添加 _mmaptest,并添加 mmap 和 munmap 系统调用,以便使 user/mmaptest.c 能够编译。目前,只需在 mmap 和 munmap 中返回错误。我们在 kernel/fcntl.h 中为您定义了 PROT_READ 等。运行 mmaptest,它会在第一次调用 mmap 时失败。
  • 在发生缺页异常时,按需延迟填充页表。也就是说,mmap 不应分配物理内存或读取文件。相反,应在 usertrap 中(或由其调用)的缺页异常处理代码中执行这些操作,就像在延迟页分配实验中那样。延迟处理的原因是为了确保对大文件的 mmap 操作快速,并且能够对大于物理内存的文件进行 mmap 操作。
  • 为每个进程跟踪 mmap 映射的内容。定义一个与第 15 讲中描述的 VMA(虚拟内存区域)相对应的结构,记录由 mmap 创建的虚拟内存范围的地址、长度、权限、文件等信息。由于 xv6 内核在内核中没有内存分配器,所以可以声明一个固定大小的 VMA 数组,并根据需要从中分配。大小为 16 应该足够。实现 mmap:在进程的地址空间中找到一个未使用的区域来映射文件,并向进程的已映射区域表中添加一个虚拟内存区域(VMA)。该 VMA 应包含一个指向被映射文件的 struct file 结构体的指针;mmap 应增加文件的引用计数,以防止在文件关闭时结构体消失(提示:参见 filedup)。运行 mmaptest:第一个 mmap 应该成功,但对映射内存的首次访问将导致页面错误并终止 mmaptest。
  • 添加代码,以便在映射区域中发生页面错误时分配一页物理内存,从相关文件中读取 4096 字节到该页,并将其映射到用户地址空间。使用 readi 读取文件,该函数接受一个在文件中读取的偏移量参数(但您必须锁定/解锁传递给 readi 的 inode)。别忘了正确设置页面的权限。运行 mmaptest;它应该到达第一个 munmap。
  • 实现 munmap:找到指定地址范围的 VMA 并取消映射指定的页面(提示:使用 uvmunmap)。如果 munmap 移除了先前 mmap 的所有页面,应减少相应 struct file 的引用计数。如果未映射的页面已被修改且文件是以 MAP_SHARED 方式映射的,则将该页面写回文件。可参考 filewrite 获取灵感。
  • 理想情况下,您的实现应仅将程序实际修改的 MAP_SHARED 页面写回。RISC-V PTE 中的脏位(D)指示页面是否已被写入。但是,mmaptest 不会检查未设置脏位的页面是否未被写回;因此,您无需查看 D 位即可将页面写回。
  • 修改 exit 以解除进程的映射区域,就像调用了 munmap 一样。运行 mmaptest;mmap_test 应该通过,但 fork_test 可能不会通过。
  • 修改 fork 以确保子进程具有与父进程相同的映射区域。别忘了增加 VMA 的 struct file 的引用计数。在子进程的页面错误处理程序中,分配新的物理页面而不是与父进程共享页面是可以的。后者会更酷,但需要更多的实现工作。运行 mmaptest;它应该通过 mmap_test 和 fork_test。

修改

参考博客1

  1. 添加测试程序和系统调用

    UPROGS=\$U/_mmaptest\
    
    // usys.pl
    entry("mmap");
    entry("munmap");
    // user.h
    void *mmap(void *addr, int length, int prot, int flags, int fd, int off);
    int munmap(void *addr, int length);	
    // syscall.h
    #define SYS_mmap   22
    #define SYS_munmap 23
    // sysycall.c
    extern uint64 sys_mmap(void);
    extern uint64 sys_munmap(void);
    static uint64 (*syscalls[])(void) = {//...[SYS_mmap]    sys_mmap,[SYS_munmap]  sys_munmap,
    };
    

    addroff在本实验中一直都是0,因此可以不在sys_mmap传入这两个参数,length是需要映射的字节数,可能和文件大小不等,port表示映射到内存的权限(PORT_READ或者PORT_WRITE)。flag表示是否需要将内存中修改的相应部分写回文件(MAP_SHARED或者MAP_PRIVATE)。

  2. 定义vma结构体,记录mmap创建的虚拟内存范围的地址、长度权限等信息。

    // proc.h
    #define NVMA 16
    struct vma
    {int vaild;      // 0: invalid, 1: validuint64 addr;    //  start address of vmaint length;     // length of vmaint prot;       //  permission of vmaint flags;      //  flag of vmastruct file *f; // pointer of mapped fileint off;        //  offset of mapped fileint valid_len;  // length of the valid mapped file
    };
    struct vma procvma[NVMA];
    
  3. 添加函数sys_mmap定义

    // sysfile.c
    // void *mmap(void *addr, int length, int prot, int flags, int fd, int off);
    uint64 sys_mmap(void){int addr, length, prot, flags, fd, off;struct file *f;if(argint(0, &addr) < 0 || argint(1, &length) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argfd(4, &fd,&f) < 0 || argint(5, &off) < 0)return -1;if(!f->writable &&(port&PROT_WRITE)&&(flags&MAP_SHARED))return -1;struct proc *p = myproc();struct vma *pvma = p->procvma;// 在这里没有分配物理内存,只是增长p->sz,标记pvma数组。                                                                                                                  for(int i=0;i<NVMA;i++){if(pvma[i].vaild==0){pvma[i].addr = p->sz;pvma[i].f = filedup(f);pvma[i].flags = flags;pvma[i].length = PGROUNDUP(length);pvma[i].off = off;pvma[i].prot = prot;pvma[i].vaild = 1;pvma[i].valid_len = PGROUNDUP(length);p->sz += pvma.length;return pvma[i].addr;}}return -1;
    }
    
  4. 在trap中添加分配物理页面的代码,利用mappages将物理页面映射到进程地址空间,然后用readi打开需要映射的文件,将对应的文件内容放入这一页内存。

    #include "fs.h"
    #include "sleeplock.h"
    #include "file.h"
    void
    usertrap(void)
    {// ...else if(r_scause() == 13||r_scause()==15){uint64 va = r_stval();if (va < p->trapframe->sp || va >= p->sz) {goto exception;}struct vma *pvma = p->procvma;va = PGROUNDDOWN(va);for (int i = 0; i < NVMA; i++) {if (pvma[i].valid && va >= pvma[i].addr + pvma[i].off && va < pvma[i].addr + pvma[i].off + pvma[i].valid_len) {  // allocate one page in the physical memorychar *mem = kalloc();if (mem == 0) goto exception;memset(mem, 0, PGSIZE);int flag = (pvma[i].prot << 1) | PTE_U; // PTE_R == 2 and PROT_READ == 1if (mappages(p->pagetable, va, PGSIZE, (uint64)mem, flag) != 0) {kfree(mem);goto exception;}   int off = va - pvma[i].addr;ilock(pvma[i].f->ip);readi(pvma[i].f->ip, 1, va, off, PGSIZE);iunlock(pvma[i].f->ip);break;}}}else {exception:printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());p->killed = 1;}// ...usertrapret();
    }
    
  5. 实现sys_munmap

int
_filewrite(struct file *f, uint64 addr, int n, uint off);uint64 sys_munmap(void){int addr;int length;if(argint(0,&addr) < 0 || argint(1,&length) < 0)return -1;struct proc *p = myproc();struct vma *pvma = p->procvma;int close = 0;for(int i=0;i<NVMA;i++){if(pvma[i].valid && addr >= pvma[i].addr && addr < pvma[i].addr + pvma[i].length){if (addr == pvma[i].addr + pvma[i].off) {// starting at begin of the valid address of vmaif (length >= pvma[i].valid_len) {// whole vma is unmmapedpvma[i].valid = 0;length = pvma[i].valid_len;close = 1;p->sz -= pvma[i].length;} else {pvma[i].off += length;pvma[i].valid_len -= length;}} else {// starting at middle, should unmap until the endlength = pvma[i].addr + pvma[i].off + pvma[i].valid_len - addr;pvma[i].valid_len -= length;}if (pvma[i].flags & MAP_SHARED) {// write the page back to the fileif (_filewrite(pvma[i].f, addr, length, addr - pvma[i].addr) == -1) return -1; }uvmunmap(p->pagetable, addr, PGROUNDUP(length)/PGSIZE, 0);if (close) fileclose(pvma[i].f);return 0;}}return -1;
}
int
_filewrite(struct file *f, uint64 addr, int n, uint off) {int r, ret = 0;if(f->writable == 0)return -1;if(f->type == FD_PIPE){ret = pipewrite(f->pipe, addr, n);} else if(f->type == FD_DEVICE){if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)return -1;ret = devsw[f->major].write(1, addr, n);} else if(f->type == FD_INODE){// write a few blocks at a time to avoid exceeding// the maximum log transaction size, including// i-node, indirect block, allocation blocks,// and 2 blocks of slop for non-aligned writes.// this really belongs lower down, since writei()// might be writing a device like the console.int max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE;int i = 0;while(i < n){int n1 = n - i;if(n1 > max)n1 = max;begin_op();ilock(f->ip);if ((r = writei(f->ip, 1, addr + i, off, n1)) > 0)off += r;iunlock(f->ip);end_op();if(r != n1){// error from writeibreak;}i += r;}// ret = (i == n ? n : -1);} else {panic("filewrite");}return ret;
}
  1. 修改uvmunmap uvmcopy fork kfree四个函数
// vm.c
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{uint64 a;pte_t *pte;if((va % PGSIZE) != 0)panic("uvmunmap: not aligned");for(a = va; a < va + npages*PGSIZE; a += PGSIZE){if((pte = walk(pagetable, a, 0)) == 0)panic("uvmunmap: walk");// if((*pte & PTE_V) == 0)//   panic("uvmunmap: not mapped");if(PTE_FLAGS(*pte) == PTE_V)panic("uvmunmap: not a leaf");if(do_free){uint64 pa = PTE2PA(*pte);kfree((void*)pa);}*pte = 0;}
}
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{pte_t *pte;uint64 pa, i;uint flags;char *mem;for(i = 0; i < sz; i += PGSIZE){if((pte = walk(old, i, 0)) == 0)panic("uvmcopy: pte should exist");if((*pte & PTE_V) == 0)continue;// panic("uvmcopy: page not present");pa = PTE2PA(*pte);flags = PTE_FLAGS(*pte);if((mem = kalloc()) == 0)goto err;memmove(mem, (char*)pa, PGSIZE);if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){kfree(mem);goto err;}}return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1;
}
// proc.h
int
fork(void)
{// ...for(i = 0; i < NVMA; i++){if(p->procvma[i].valid){np->procvma[i] = p->procvma[i];filedup(p->procvma[i].f);}}// ...
}
// kalloc.c
void
kfree(void *pa)
{struct run *r;if(pa==0)return;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// Fill with junk to catch dangling refs.memset(pa, 1, PGSIZE);r = (struct run*)pa;acquire(&kmem.lock);r->next = kmem.freelist;kmem.freelist = r;release(&kmem.lock);
}

参考博客

相关文章:

  • 智能手表供应链与采购清单(Aurora Watch S1)
  • 基于Python学习《Head First设计模式》第六章 命令模式
  • 第4章——springboot自动配置
  • 【JavaSE】泛型学习笔记
  • 【知识扫盲】分布式系统架构或分布式服务中的管理面,数据面和业务面
  • 【Zephyr 系列 13】BLE Mesh 入门实战:构建基础节点通信与中继组播系统
  • MVC分层架构模式深入剖析
  • Redis介绍和部署
  • Redis 高频知识点及解析
  • 基于51单片机的空气净化器仿真
  • 【网站建设】网站 SEO 中 meta 信息修改全攻略 ✅
  • 策略模式实战:Spring中动态选择商品处理策略的实现
  • 【工具使用】STM32CubeMX-FreeRTOS操作系统-信号标志、互斥锁、信号量篇
  • 线程安全集合
  • 黑马Sting四道练习题
  • 数据库防丢失技术指南
  • 区块链技术概述
  • 链结构与工作量证明7️⃣:用 Go 实现比特币的核心机制
  • CQF预备知识:三、微分方程 -- 3.3.3 二阶常系数齐次线性微分方程详解
  • 人工智能--AI换脸
  • 企业线上培训平台有哪些/广州seo和网络推广
  • 网站建设维护单位/百度竞价推广效果好吗
  • 怎么做同学录的网站/seo工资服务
  • 谈谈设计和建设网站体会/东营优化路网
  • 真做视频网站/seo关键词分析表
  • 无锡制作网站公司/同城推广引流平台