清华OS程序员

清华大学操作系统课程 ucore Lab8 文件系统 实验报告

2019-03-21  本文已影响0人  AmadeusChan

操作系统 Lab8 文件系统 实验报告

课程信息所在网址:https://github.com/chyyuu/os_course_info

实验目的

实验内容

基本练习

练习0:填写已有实验

在本练习中将LAB1/2/3/4/5/6/7的实验内容移植到了LAB8的实验框架内,由于手动进行内容移植比较烦杂,因此考虑使用diff和patch工具进行自动化的移植,具体使用的命令如下所示:(对于patch工具进行合并的时候产生冲突的少部分内容,则使用*.rej, *.orig文件来手动解决冲突问题)

diff -r -u -P lab7_origin lab7 > lab7.patch
cd lab8
patch -p1 -u < ../lab7.patch

练习1:完成读文件操作的实现(需要编码)

首先了解打开文件的处理流程,然后参考本实验后续的文件读写操作的过程分析,编写在sfs_inode.c中sfs_io_nolock读文件中数据的实现代码。

设计实现

问题回答

练习2: 完成基于文件系统的执行程序机制的实现(需要编码)

改写proc.c中的load_icode函数和其他相关函数,实现基于文件系统的执行程序机制。执行: make qemu。如果能看看到sh用户程序的执行界面,则基本成功了。如果在sh用户界面上可 以执行”ls”,”hello”等其他放置在sfs文件系统中的其他执行程序,则可以认为本实验基本成功。

设计实现

static int
load_icode(int fd, int argc, char **kargv) {
        if (current->mm != NULL) { // 判断当前进程的mm是否已经被释放掉了
            panic("load_icode: current->mm must be empty.\n");
        }

    int ret = -E_NO_MEM;
    struct mm_struct *mm;

    // (1) create a new mm for current process
    if ((mm = mm_create()) == NULL) { // 为进程创建一个新的mm
        goto bad_mm;
    }
    // (2) create a new PDT
    if ((ret = setup_pgdir(mm)) != 0) { // 进行页表项的设置
        goto bad_pgdir_cleanup_mm;
    }
    // (3) copy TEXT/DATA/BSS section 
    // (3.1) resolve elf header
    struct elfhdr elf, *elfp = &elf;
    off_t offset = 0;
    load_icode_read(fd, (void *) elfp, sizeof(struct elfhdr), offset); // 从磁盘上读取出ELF可执行文件的elf-header
    offset += sizeof(struct elfhdr);
    if (elfp->e_magic != ELF_MAGIC) { // 判断该ELF文件是否合法
        ret = -E_INVAL_ELF;
        goto bad_elf_cleanup_pgdir;
    }

    struct proghdr ph, *php = &ph;
    uint32_t vm_flags, perm;
    struct Page *page;
    for (int i = 0; i < elfp->e_phnum; ++ i) { // 根据elf-header中的信息,找到每一个program header
        // (3.2) resolve prog header
        load_icode_read(fd, (void *) php, sizeof(struct proghdr), elfp->e_phoff + i * sizeof(struct proghdr)); // 读取program header
        if (php->p_type != ELF_PT_LOAD) {
            continue;
        }
        if (php->p_filesz > php->p_memsz) { 
            ret = -E_INVAL_ELF;
            goto bad_cleanup_mmap;
        }
        if (php->p_filesz == 0) {
            continue;
        }
        // (3.3) build vma
        vm_flags = 0, perm = PTE_U;
        if (php->p_flags & ELF_PF_X) vm_flags |= VM_EXEC; // 根据ELF文件中的信息,对各个段的权限进行设置
        if (php->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
        if (php->p_flags & ELF_PF_R) vm_flags |= VM_READ;
        if (vm_flags & VM_WRITE) perm |= PTE_W;
        if ((ret = mm_map(mm, php->p_va, php->p_memsz, vm_flags, NULL)) != 0) { // 将这些段的虚拟内存地址设置为合法的
            goto bad_cleanup_mmap;
        }
        // (3.4) allocate pages for TEXT/DATA sections
        offset = php->p_offset;
        size_t off, size;
        uintptr_t start = php->p_va, end = php->p_va + php->p_filesz, la = ROUNDDOWN(start, PGSIZE);
        ret = -E_NO_MEM;
        while (start < end) {
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { // 为TEXT/DATA段逐页分配物理内存空间
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            load_icode_read(fd, page2kva(page) + off, size, offset);  // 将磁盘上的TEXT/DATA段读入到分配好的内存空间中去
            //memcpy(page2kva(page) + off, page2kva(buff_page), size);
            start += size, offset += size;
        }

        // (3.5) allocate pages for BSS
        end = php->p_va + php->p_memsz;
        if (start < la) { // 如果存在BSS段,并且先前的TEXT/DATA段分配的最后一页没有被完全占用,则剩余的部分被BSS段占用,因此进行清零初始化
            if (start == end) {
                continue;
            }
            off = start + PGSIZE - la, size = PGSIZE - off;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page) + off, 0, size); // init all BSS data with 0
            start += size;
            assert((end < la && start == end) || (end >= la && start == la));
        }
        while (start < end) {  // 如果BSS段还需要更多的内存空间的话,进一步进行分配
            if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { // 为BSS段分配新的物理内存页
                goto bad_cleanup_mmap;
            }
            off = start - la, size = PGSIZE - off, la += PGSIZE;
            if (end < la) {
                size -= la - end;
            }
            memset(page2kva(page), 0, size); // 将分配到的空间清零初始化
            start += size;
        }

    }
    sysfile_close(fd); // 关闭传入的文件,因为在之后的操作中已经不需要读文件了

    // (4) setup user stack
    vm_flags = VM_READ | VM_WRITE | VM_STACK; // 设置用户栈的权限
    if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) { // 将用户栈所在的虚拟内存区域设置为合法的
        goto bad_cleanup_mmap;
    }
    // setup args
    uint32_t stacktop = USTACKTOP;
    uint32_t argsize = 0;
    for (int j = 0; j < argc; ++ j) {  // 确定传入给应用程序的参数具体应当占用多少空间
        argsize += (1 + strlen(kargv[j])); // includinng the ending '\0'
    }
    argsize = (argsize / sizeof(long) + 1) * sizeof(long); //alignment
    argsize += (2 + argc) * sizeof(long);  
    stacktop = USTACKTOP - argsize; // 根据参数需要在栈上占用的空间来推算出,传递了参数之后栈顶的位置
    uint32_t pagen = argsize / PGSIZE + 4;
    for (int j = 1; j <= 4; ++ j) { // 首先给栈顶分配四个物理页
        assert(pgdir_alloc_page(mm->pgdir, USTACKTOP - PGSIZE * j, PTE_USER) != NULL);
    } 

    // for convinience, setup mm (5)
    mm_count_inc(mm); // 切换到用户的内存空间,这样的话后文中在栈上设置参数部分的操作将大大简化,因为具体因为空间不足而导致的分配物理页的操作已经交由page fault处理了,是完全透明的
    current->mm = mm;
    current->cr3 = PADDR(mm->pgdir);
    lcr3(PADDR(mm->pgdir));

    // (6) setup args in user stack
    uint32_t now_pos = stacktop, argvp;
    *((uint32_t *) now_pos) = argc; // 设置好argc参数(压入栈)
    now_pos += 4;
    *((uint32_t *) now_pos) = argvp = now_pos + 4; // 设置argv数组的位置
    now_pos += 4;
    now_pos += argc * 4;
    for (int j = 0; j < argc; ++ j) {
        argsize = strlen(kargv[j]) + 1;  // 将argv[j]指向的数据拷贝到用户栈中
        memcpy((void *) now_pos, kargv[j], argsize);
        *((uint32_t *) (argvp + j * 4)) = now_pos; // 设置好用户栈中argv[j]的数值
        now_pos += argsize;
    }

    // (7) setup tf
    struct trapframe *tf = current->tf; // 设置中断帧
    memset(tf, 0, sizeof(struct trapframe));
    tf->tf_cs = USER_CS; // 需要返回到用户态,因此使用用户态的数据段和代码段的选择子
    tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
    tf->tf_esp = stacktop; // 栈顶位置为先前计算过的栈顶位置,注意在C语言的函数调用规范中,栈顶指针指向的位置应该是返回地址而不是第一个参数,这里让栈顶指针指向了第一个参数的原因在于,在中断返回之后,会跳转到ELF可执行程序的入口处,在该入口处会进一步使用call命令调用主函数,这时候也就完成了将Return address入栈的功能,因此这里无需画蛇添足压入返回地址
    tf->tf_eip = elfp->e_entry; // 将返回地址设置为用户程序的入口
    tf->tf_eflags = 0x2 | FL_IF; // 允许中断,根据IA32的规范,eflags的第1位需要恒为1
    ret = 0;
    
out:
    return ret;
bad_cleanup_mmap: // 进行加载失败的一系列清理操作
    exit_mmap(mm);
bad_elf_cleanup_pgdir:
    put_pgdir(mm);
bad_pgdir_cleanup_mm:
    mm_destroy(mm);
bad_mm:
    goto out;

}

问题回答

实验结果

最终的实验结果符合预期,并且能够通过make grade脚本的检查,如下图所示:

result1.png result2.png

参考答案分析

练习1

比较练习1的实现与参考答案的实现的区别在于一些细节方面的实现,主要体现在对完全被读写区域覆盖的数据块进行读写的时候,提供的函数事实上是可以完成连续若干块数据块的读写的,但是参考答案没有利用这个特点,而是额外添加了一个循环,然后在循环中对每一个数据块逐次进行读取操作,这有可能会造成时间效率的降低;

练习2

本实验在练习2中的实现与参考答案的实现大致一致,但是经过仔细的比较,观察到一个细节,参考答案在确定参数的长度的时候使用的函数时strnlen,而本实验中的实现使用了strlen,而后者是不安全的,有可能遭到栈溢出攻击的,因此在这个区别上,参考答案的实现明显优于本实验的实验,这也其实我在完成实际的编程任务的时候需要充分考虑到鲁棒性、安全性等细节,这也是我自己觉得在本次操作系统实验中做得有所欠缺的地方;

实验中涉及的知识点列举

实验中未涉及的知识点列举

在本次实验中未涉及到的知识点列举如下:

实验代码

https://github.com/AmadeusChan/ucore_os_lab/tree/master/lab8

参考文献

上一篇 下一篇

猜你喜欢

热点阅读