0%

Lab Mmap

Lab Mmap

前置知识

lab lazy, lab file

Task: mmap

这个task难度较大,要求我们将文件映射到用户地址,让用户程序可以直接读写操作文件。

  1. 首先定义结构体mvma,将它添加到proc结构体中并初始化。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // proc.h
    struct mvma {
    uint64 addr; // 起始地址
    int len; // 文件长度
    int offset; // 偏移量(在这个lab中始终为0)
    int flags; // 标志位
    int protection; // 权限
    int fd; // 文件描述符
    struct file *fl; // 文件
    int used; // 是否正在被使用
    };
    ...
    char name[16]; // Process name (debugging)
    struct mvma vma[NVMA]; //记得去宏定义
    ···
    // allocproc()
    found:
    ...
    memset(&p->vma, 0, sizeof (p->vma));
  2. 添加syscall mmap, munmap(在sysfile中),注意在user.h中的函数声明形式
    1
    2
    3
    // user.h
    void* mmap(void*, int, int, int, int, int);
    int munmap(void*, int);
  3. 为sys_mmap添加内容。具体做法类似lazy,每次寻找一个可用的vma,储存参数,增加文件引用次数,增加page计数但不分配物理页。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    uint64
    sys_mmap(void)
    {
    struct proc*p = myproc();
    int len, offset, prot, flags, fd;
    uint64 addr;
    struct file *f;

    if (argaddr(0, &addr) < 0 || argint(1, &len) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argfd(4, &fd, &f) < 0 || argint(5, &offset) < 0)
    goto bad;

    len = PGROUNDUP(len);
    if(addr != 0 || offset != 0 || len < 0) // 根据实验手册要求进行检查
    goto bad;
    if (!f->writable && (prot & PROT_WRITE) && (flags == MAP_SHARED))
    goto bad;
    if (MAXVA - len < p->sz) // 越界
    goto bad;
    if (!f->readable && (prot & PROT_READ)) // 可读性
    goto bad;


    for (int i = 0; i < NVMA; i++)
    {
    if (p->vma[i].used == 0) {

    struct mvma* crvma = &p->vma[i];
    crvma->used = 1;
    crvma->addr = p->sz; // 注意顺序!!!
    p->sz += len; // 注意顺序!!!
    crvma->len = len;
    crvma->flags = flags;
    crvma->protection = prot;
    crvma->fd = fd;
    crvma->offset = offset;
    crvma->fl = f;
    filedup(f); // 增加引用计数
    return crvma->addr;
    }
    }
    goto bad;

    bad: return 0xffffffffffffffff;
    }
  4. 类似lazy,因为我们没有分配物理页,因此当进程访问这个文件时会发生pagefault。此时进入usertrap,我们需要在里面进行物理页分配。
    注意:在usertrap中非常容易出现错误。比如,任意环节出现需要kill进程的时候设置完p->killed = 1后不能向后执行,否则sbrkfail不过。再比如,小心使用goto,因为在这里面出现问题后并不是直接返回,而是设置killed位后统一调用exit。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    // trap.c
    else if (r_scause() == 13 || r_scause() == 15) {

    uint64 va = r_stval();
    struct proc *p = myproc();
    if (va > MAXVA || va > p->sz) {
    p->killed = 1;
    }
    else {
    struct mvma* crvma = 0;
    int found = 0;
    for (int i = 0; i < NVMA; i++) //通过比对地址与长度来寻找特定的mvma
    {
    crvma = &p->vma[i];
    if (crvma->used && va >= crvma->addr && va < crvma->addr + crvma->len) {
    found = 1;
    break;
    }
    }
    if (!found){
    p->killed = 1;
    goto bad;
    }

    va = PGROUNDDOWN(va); // 地址对齐
    uint64 pa = (uint64)kalloc();
    if (pa == 0) {
    p->killed = 1;
    goto bad;
    }

    memset((void*)pa, 0, PGSIZE);

    ilock(crvma->fl->ip);
    if (readi(crvma->fl->ip, 0, pa, crvma->offset + va - crvma->addr, PGSIZE) < 0) {
    iunlock(crvma->fl->ip);
    p->killed = 1;
    goto bad;
    } // 读取文件内容

    iunlock(crvma->fl->ip); // 对文件操作需要锁

    int flag = PTE_U; // 设立标志位
    if (crvma->protection & PROT_READ)
    flag |= PTE_R;
    if (crvma->protection & PROT_WRITE)
    flag |= PTE_W;
    if (crvma->protection & PROT_EXEC)
    flag |= PTE_X;
    if (mappages(p->pagetable, va, PGSIZE, pa, flag) < 0) { // 添加映射
    kfree((void*)pa);
    p->killed = 1;
    goto bad;
    }
    }
    bad:;
    //printf("mmap trap failed");
    }
  5. 完成munmap。这里面除了取消映射,写回文件以外,还需要根据munmap操作的文件位置分类操作。当操作的是整个文件的时候,记得减少引用计数,当操作的是部分文件的时候(头部或尾部,中间部分不在lab范围内),需要对mvma的地址,长度等进行相应修改。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    uint64
    sys_munmap(void)
    {
    struct proc *p = myproc();
    uint64 addr;
    int len;
    struct mvma *crvma;
    if (argaddr(0, &addr) < 0 || argint(1, &len) < 0)
    return -1;

    for (int i = 0; i <= NVMA; i++)
    {
    if (i == NVMA){
    return -1;
    }

    crvma = &p->vma[i];
    if (crvma->used && addr >= crvma->addr && addr <= crvma->addr + crvma->len) {
    break;
    }
    }

    addr = PGROUNDDOWN(addr);
    len = PGROUNDUP(len); // 长度取整
    if (crvma->flags & MAP_SHARED) {
    if (filewrite(crvma->fl, addr, len) < 0){
    //printf("filewrite failed");
    //return -1; // why can't go error?
    }

    }

    uvmunmap(p->pagetable, addr, len / PGSIZE, 1);

    if (addr == crvma->addr && len == crvma->len) { // all pages
    fileclose(crvma->fl);
    crvma->used = 0;
    }
    else if(addr == crvma->addr) { // start pages
    crvma->addr += len;
    crvma->len -= len;
    crvma->offset += len;

    }
    else if((addr + len) == (crvma->addr + crvma->len)) { // end pages
    crvma->len -= len;
    }
    else {
    printf("Not allowed in this lab");
    return -1;
    }
    return 0;
    }
  6. 回忆lazy lab,需要将uvmunmap和uvmcopy中的panic continue掉。
    1
    2
    3
    4
    // uvmunmap(), uvmcopy
    if((*pte & PTE_V) == 0)
    continue;
    // panic("uvmunmap: not mapped");
  7. 修改exit,取消对应映射并写回文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // proc.c
    // unmap all the mmap pages
    for (int i = 0; i < NVMA; i++)
    {
    if (p->vma[i].used)
    {
    if (p->vma[i].flags & MAP_SHARED)
    filewrite(p->vma[i].fl, p->vma[i].addr, p->vma[i].len);
    fileclose(p->vma[i].fl);
    uvmunmap(p->pagetable, p->vma[i].addr, p->vma[i].len / PGSIZE, 1);
    p->vma[i].used = 0;
    }
    }

    begin_op();
  8. 修改fork,使子进程copy父进程的mvma。
    1
    2
    3
    4
    5
    6
    7
    8
    for (int i = 0; i < NVMA; i++)
    {
    if (p->vma[i].used)
    {
    memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
    filedup(p->vma[i].fl);
    }
    }