首页 > 其他分享 >xv6 mmap

xv6 mmap

时间:2023-12-06 17:35:51浏览次数:32  
标签:uint64 addr 映射 int mmap xv6 vmamems file

in linux

调用mmap,会申请一段内存空间(文件的内存映射部分),并且自动映射到指定的文件内存映射部分。

mmap

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr为用户指定的内存起始地址,为0时由系统分配。
length制定映射的长度,单位是字节。
prot制定映射页面的权限

  • PROT_READ 映射内存可读
  • PROT_WRITE 映射内存可写
  • PROT_EXEC 映射内存可被执行
  • PROT_NONE 映射内存不可访问

flags控制映射内存修改后的行为

  • MAP_SHARED 进程共享映射内存,修改后会反映到文件中。精准控制修改时间需要用msyn。
  • MAP_PRIVATE 进程私有映射内存,修改后不会反映到文件中。Copy on write
  • MAP_FIXED 必须使用指定的映射内存的起始地址addr,页对齐。慎用,如果和其他映射内存重叠,会覆盖其他映射内存。
  • MAP_ANONYMOUS 映射匿名,初始化为0,不与文件关联即fd和offset无效,有些实现要求强制fd为-1,及时不强制程序中最好也用-1

fd映射文件的文件描述符
offset映射文件的偏移量,即从文件的什么位置开始映射,必须是页对齐的

成功返回0;失败返回-1即MAP_FAILED,并设置errno。

open系统调用的权限必须和mmap的prot权限符合,具体为

  • open必须可读
#define O_RDONLY  0x000
#define O_WRONLY  0x001
#define O_RDWR    0x002
  • mmap 指定了PROT_WRITE和MAP_SHARED,open必须RDWR
    进程终止内存映射需要单独调用munmap回收,映射完成后文件即可使用close,但实际并没有关闭,内存映射区域存在对文件的引用,导致并不会实际关闭。

使用场景

  • 共享文件映射,使用MAP_SHARED
  • 私有文件映射,使用MAP_PRIVATE,常用于加载动态共享库等程序
  • 共享匿名映射,使用MAP_ANONYMOUS|MAP_SHREAD,常用于进程间通信
  • 私有内存映射,用于分配较大的内存空间,供进程自身使用

munmap

int munmap(void *addr, size_t length);

指定范围内的所有页面都会被unmaped,成功返回0,失败返回-1并设置errno。除了程序内调用,进程终止时也会自动unmap。
不要求区域内页面是映射的,即使不包含任何映射页面,也不会出错。
一个例子

#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
    int *p;
    p=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED,-1,0);
    if(fork()==0){
        *p=100;
        printf("child: %d\n",*p);
        exit(0);
    }else{
        wait(NULL);
        printf("parent: %d\n",*p);
    }
    int err=munmap(p,4);
    printf("err: %d\n",err);
    err = munmap(p-4096,4);
    printf("err: %d\n",err);
}
// child: 100
// parent: 100
// err: 0
// err: 0

in xv6

不得不说,这个实验还是有难度的,做的时候有点面向测试用例编程的感觉了,但思路整体上应该没问题。

mmap

mmap的lab实现时,总是假设addr为0,offset为0,并且MAP_SHARED实际也可以不共享,只需要写回修改即可。
实现共享确实有点麻烦,需要考虑多个进程同时修改以及什么时候释放的问题,而且考虑到munmap可以释放部分区域,单个vma的引用计数也不足以解决问题,因此暂且不实现。一个简单的想法是在vma内同时维护原区域和实际区域,只有一个进程全部ummap时才递减引用计数,引用计数为0时才释放区域。
xv6里没有memory allocator,所以具体实现的时候简便即可,提供的方法是在进程内保存VMA数组。
具体vma里需要包含什么,其实文档说的很清楚了。

struct vma{
  uint64 start;
  uint64 len;
  uint64 offset;
  int prot;
  int flags;
  struct file *file;
};

mmap完成一下操作

  • 检查权限是否匹配,这点文档没说,对着测试代码看的
  • 找到合适的内存空间,为了避免和p->sz混淆带来的麻烦(需要修改很多函数,改变函数语义),选择从高地址TRAPFRAME下开始。并且维护一个vmastart作为内存中mmap分配的最低位置,uvmunmap时简单更新为现存映射空间最低位置。
  • 找到进程中空闲的vma项,将信息存入。

sys_mmap的helper函数,用于检查权限是否匹配。

uint64 mmap(uint64 addr, uint64 len, int prot, int flags, int fd, uint64 offset){
  int i;
  struct vma* vmamems;
  struct proc* p=myproc();
  struct file* file = p->ofile[fd];
  
  if(file == 0){
    return -1;
  }
  if((prot & PROT_READ) && file->readable == 0){
    return -1;
  }
  if((prot & PROT_WRITE) && p->ofile[fd]->writable == 0 && !(flags & MAP_PRIVATE)){
    return -1;
  }
  len = PGROUNDUP(len);
  if(addr == 0){
    addr = p->vmastart - len;
  }
  vmamems=p->vmamems;

  for(i=0;i<NVMA;i++){
    if(vmamems[i].file == 0){
      break;
    }
  }

  if(i >= NVMA){
    return -1;
  }

  vmamems[i].start=addr;
  vmamems[i].len=len;
  vmamems[i].offset=offset;
  vmamems[i].prot=prot;
  vmamems[i].flags=flags;
  vmamems[i].file=p->ofile[fd];
  p->vmastart = addr;
  filedup(vmamems[i].file);
  return addr;
}

munmap

在进程vma数组中找到对应项,低配版直接对比addr(实验保证了不会从中间进行ummap)。
完成写回文件和删除分配的物理内存,最后更改vma记录。
由于xv6日志一次最多修改MAXOPBLOCKS个块,得到的每次系统调用写入最多为3*BSIZE(为了省事我直接用PGSIZE了),因此需要循环写回(总感觉在本次lab下有些多余,但要写出能用的代码确实要这样,然而代码建立在不正确的假设上QAQ)。
sys_munmap的helper函数。

int munmap(uint64 addr, uint64 len){
  int i;
  struct proc * p = myproc();
  struct vma* vmamems = p->vmamems;
  for(i=0;i<NVMA;i++){
    if(vmamems[i].file && vmamems[i].start == addr){
      if(len > vmamems[i].len){
        return -1;
      }
      uint64 offset = vmamems[i].offset;
      uint64 finished = 0;
      pte_t *pte;
      while(finished < len){
        if((pte = walk(p->pagetable, addr + finished, 0)) == 0){
          return -1;
        }
        if(*pte & PTE_V)
        {
          uint64 r; 
          if((vmamems[i].flags & MAP_SHARED) &&*pte & PTE_D){
            // 以下过程参考的file.c,虽然不知道这样判断的意义是什么
            begin_op();
            ilock(vmamems[i].file->ip);
            if((r=writei(vmamems[i].file->ip, 1, addr + finished, offset, PGSIZE))>0){
              offset += r;
            }
            iunlock(vmamems[i].file->ip);
            end_op();
            if(r != PGSIZE){
              break;
            }
          }
          uvmunmap(p->pagetable, addr + finished, 1, 1);
        }
        finished+=PGSIZE;
      }
      if(finished != len){
        return -1;
      }
      vmamems[i].start += len;
      vmamems[i].len -= len;
      vmamems[i].offset = offset;
      if(vmamems[i].len == 0){
        fileclose(vmamems[i].file);
        vmamems[i].file=0;
      }
      break;
    }
  }
  if(i >= NVMA){
    return -1;
  }
  uint64 vmastart = TRAPFRAME;
  for(int i=0;i<NVMA;i++){
    if(vmamems[i].file != 0){
      vmastart = (vmastart > vmamems[i].start) ? vmamems[i].start : vmastart;
    }
  }
  p->vmastart = vmastart;
  return 0;
}

缺页处理程序,有上次写COW的经验这次写起来就很顺手了。

int MapHandler(uint64 addr){
  struct proc *p = myproc();
  struct vma* vmamems = p->vmamems;
  uint64 va;
  
  for(;vmamems < p->vmamems + NVMA; vmamems++){
    if(vmamems->file && addr >= vmamems->start && addr < vmamems->start + vmamems->len){
      break;
    }
  }
  if(vmamems == NVMA + p->vmamems){
    return -1;
  }
  uint64 mem = (uint64)kalloc();
  if(mem == 0){
    return -1;
  }
  memset((void*)mem, 0, PGSIZE);
  va = PGROUNDDOWN(addr);
  ilock(vmamems->file->ip);
  readi(vmamems->file->ip, 0, mem, vmamems->offset + (va - vmamems->start), PGSIZE);
  iunlock(vmamems->file->ip);
  int flags = PTE_U;
  if(vmamems->prot & PROT_WRITE)
    flags |= PTE_W;
  if(vmamems->prot & PROT_READ)
    flags |= PTE_R;
  if(vmamems->prot & PROT_EXEC)
    flags |= PTE_X;
  if(mappages(p->pagetable, va, PGSIZE, mem, flags) != 0){
    kfree((void*)mem);
    return -1;
  }
  return 0;
}

exit

  for(int i=0;i<NVMA;i++){
    if(p->vmamems[i].file){
      munmap(p->vmamems[i].start, p->vmamems[i].len);
    }
  }

fork

  for(int i=0;i<NVMA;i++){
    np->vmamems[i] = p->vmamems[i];
    if(p->vmamems[i].file){
      filedup(p->vmamems[i].file);
    }
  }

标签:uint64,addr,映射,int,mmap,xv6,vmamems,file
From: https://www.cnblogs.com/wangerblog/p/17880021.html

相关文章

  • xv6 file system
    xv6filesystem在我看来文件系统某种程度上是最复杂的一部分(单从页数也足以说明了),而且我对文件系统的了解其实很少,因此这部分仔细看了一下。xv6文件系统提供类似unix的文件、目录和路径名,并将其数据存储在virtio磁盘上以实现持久化。文件系统解决了几个挑战:文件系统需要......
  • xv6 cow
    虚拟内存提供了一定程度的间接性:内核可以通过将PTE标记为无效或只读来拦截内存引用,从而导致页面错误,并可以通过修改PTE来更改地址的含义。xv6中的fork系统调用将父进程的所有用户空间内存复制到子进程中。如果父对象很大,则复制可能需要很长时间。更糟糕的是,这项工作经常被大量浪......
  • xv6 device driver
    Interruptsanddevicedrivers驱动程序是操作系统中管理特定设备的代码:它配置设备硬件,告诉设备执行操作,处理由此产生的中断,并与可能等待设备I/O的进程进行交互。驱动程序需要与它所管理的设备并发执行并且必须理解设备的硬件接口,编写代码可能很棘手。设备通常可以产生中断,内核......
  • xv6 traps
    traps引入三种类型的事件会导致CPU暂时搁置普通指令的执行,并强制将控制转移给处理事件的特殊代码。系统调用。用户程序执行调用指令要求内核为它做一些事情异常。指令(用户或内核)做了一些非法的事情,例如除以零或使用无效的虚拟地址设备中断。当设备发出需要注意的信号时,例......
  • xv6book阅读 chapter1
    xv6book主要研究了xv6如何实现它的类Unix接口,但是其思想和概念不仅仅适用于Unix。任何操作系统都必须将进程多路复用到底层硬件上,相互隔离进程,并提供受控制的进程间通信机制。1了解xv6xv6是一个模仿unix内部设计的操作系统,其提供了unix中对应的部分系统调用。理解xv6对于我们理......
  • asis2016_b00ks(根据报错信息确定mmap拓展偏移)
    这个应该是大部分人学off-by-one的第一个例题,当时笔者也是只在本地去测试,最近重温又发现了一些有趣的东西这里有个off-by-null,可以看到14行如果i=a2就break,再让*a1=0,比如我们的size为10,正常我们被允许输入10个字节的数据,这里的i是从0开始的,所以是0-10,也就是11字节,多出的......
  • mitos - xv6 for riscv
    参考:code:https://github.com/mit-pdos/xv6-riscvbook:https://pdos.csail.mit.edu/6.828/2021/xv6/book-riscv-rev2.pdfnote:https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/......
  • C语言 mmap完成文件读写
    #include<stdio.h>#include<stdlib.h>#include<string.h>#include<sys/mman.h>#include<fcntl.h>#include<unistd.h>intmain(){//打开文件进行读写intfd=open("test.log",O_RDWR|O_CREAT,0600);......
  • xv6 traps
    trap:在xv6操作系统中,"trap"是指程序从用户态切换到内核态的一种机制。这种切换通常会在几种情况下发生,例如:系统调用、出现页错误(pagefault)或者外部设备触发了中断。Trap机制是通过一些特定的硬件指令和硬件状态来实现的,例如修改程序计数器(PC)的值,以便将程序的控制权转移到内核中......
  • mmap:Python内存映射文件操作
    前言内存映射通常可以提高I/O的性能,因为使用内存映射时,不需要对每个访问都建立一个单独的系统调用,也不需要在缓冲区之间复制数据,内核和用户都能很方便的直接访问内存。本篇,将详细介绍Python内存映射库:mmap。mmap(读文件)使用mmap()函数可以创建一个内存映射文件。该函数的第1个......