Lab8

作者: 彳余斤欠丹彡 | 来源:发表于2017-07-01 02:25 被阅读0次

    Lab8 report

    练习0:填写已有实验

    用meld对比修改了以下文件:

    kdebug.c
    trap.c
    default_pmm.c
    pmm.c
    swap_fifo.c
    vmm.c
    proc.c
    default_sched.c
    monitor.c
    check_sync.c
    

    在练习0中暂时无需对以上文件做修改。

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

    实验思路

    打开文件时,调用 safe_open()函数,然后依次调用open->sys_open->syscall,从而引发系统调用然后进入内核态,然后会由sys_open内核函数处理系统调用,进一步调用到内核函数sysfile_open,然后将存有文件路径的字符串拷贝到内核空间中的字符串path中,并进入到vfs的处理流程完成进一步的打开文件操作中。
    系统会分配一个file变量,这个变量其实是current->fs_struct->filemap[]中的一个元素,内容为空,但是分配完了之后还不能找到对应对应的文件结点。所以系统在该层调用了vfs_open函数通过调用vfs_lookup找到path对应文件的inode,然后调用vfs_open函数打开文件。然后层层返回,通过执行语句file->node=node;,就把当前进程的current->fs_struct->filemap[fd](即file所指变量)的成员变量node指针指向了代表文件的索引节点node。这时返回fd。最后完成打开文件的操作。

    实现过程

    sfs_inode.c

    sfs_io_nolock()函数
    static int
    sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
        ······
        //读取第一部分的数据
        if ((blkoff = offset % SFS_BLKSIZE) != 0) {
            size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
            if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
                goto out;
            }
            if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {
                goto out;
            }
            alen += size;
            if (nblks == 0) {
                goto out;
            }
            buf += size, blkno ++, nblks --;
        }
        //读取中间部分的数据,将其分为size大学的块,然后一次读一块直至读完
        size = SFS_BLKSIZE;
        while (nblks != 0) {
            if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
                goto out;
            }
            if ((ret = sfs_block_op(sfs, buf, ino, 1)) != 0) {
                goto out;
            }
            alen += size, buf += size, blkno ++, nblks --;
        }
        //读取第三部分的数据
        if ((size = endpos % SFS_BLKSIZE) != 0) {
            if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
                goto out;
            }
            if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0) {
                goto out;
            }
            alen += size;
        }
        ······
    }
    

    思考题

    在实验报告中给出设计实现”UNIX的PIPE机制“的概要设方案,鼓励给出详细设计方案

    管道可以看作是由内核管理的一个缓冲区,一端连接进程A的输出,另一端连接进程B的输入。进程A会向管道中放入信息,而进程B会取出被放入管道的信息。当管道中没有信息,进程B会等待,直到进程A放入信息。当管道被放满信息的时候,进程A会等待,直到进程B取出信息。当两个进程都结束的时候,管道也自动消失。
    管道基于fork机制建立,从而让两个进程可以连接到同一个PIPE上。
    最开始的时候,管道两端都连接在同一个进程A上,当fork复制进程A得到进程B的时候,会将这两个连接也复制到新的进程B上。随后,每个进程关闭自己不需要的一个连接。例如进程A关闭向PIPE输入的链接,进程B关闭接收PIPE输出的连接。 从而二者即可通过该PIPE进行通信。
    在UNIX中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。具体来说,即有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。
    当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。
    写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:

    1. 内存中有足够的空间可容纳所有要写入的数据;
    2. 内存没有被读程序锁定。

    如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。
    管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。

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

    实验思路

    load_icode主要是将文件加载到内存中执行,根据注释的提示分为了七个步骤:

    1. 建立内存管理器
    2. 建立页目录
    3. 将文件逐个段加载到内存中,这里要注意设置虚拟地址与物理地址之间的映射
    4. 建立相应的虚拟内存映射表
    5. 建立并初始化用户堆栈
    6. 处理用户栈中传入的参数
    7. 最后很关键的一步是设置用户进程的中断帧

    此外,一旦发生错误还需要进行错误处理。

    实现过程

    proc.c

    alloc_proc()函数
    static struct proc_struct *
    alloc_proc(void) {
        ······
            proc->filesp = NULL;//初始化fs中的进程控制结构
        }
        return proc;
    }
    
    do_fork()函数
    int
    do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
        ······    
        if (setup_kstack(proc) != 0) {
            goto bad_fork_cleanup_proc;
        }
        /*if (copy_mm(clone_flags, proc) != 0) {
            goto bad_fork_cleanup_kstack;
        }*/
        if (copy_files(clone_flags, proc) != 0) {
            goto bad_fork_cleanup_kstack;
        }
        if (copy_mm(clone_flags, proc) != 0) {
            goto bad_fork_cleanup_fs;
        }
        copy_thread(proc, stack, tf);
    }
    
    load_icode()函数
    static int
    load_icode(int fd, int argc, char **kargv) {
        assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);
        //(1)建立内存管理器
        if (current->mm != NULL) {
            panic("load_icode: current->mm must be empty.\n");
        }
        //(2)建立页目录
        int ret = -E_NO_MEM;// E_NO_MEM代表因为存储设备产生的请求错误
        struct mm_struct *mm;// 建立内存管理器
        if ((mm = mm_create()) == NULL) {
            goto bad_mm;
        }
        if (setup_pgdir(mm) != 0) {
            goto bad_pgdir_cleanup_mm;
        }
        struct Page *page;
        //(3)从文件加载程序到内存
        struct elfhdr __elf, *elf = &__elf;
        if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {
            goto bad_elf_cleanup_pgdir;
        }
        if (elf->e_magic != ELF_MAGIC) {
            ret = -E_INVAL_ELF;
            goto bad_elf_cleanup_pgdir;
        }
        struct proghdr __ph, *ph = &__ph;
        uint32_t vm_flags, perm, phnum;
        for (phnum = 0; phnum < elf->e_phnum; phnum ++) {
            off_t phoff = elf->e_phoff + sizeof(struct proghdr) * phnum;//循环读取程序的每个段的头部
            if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), phoff)) != 0) {
                goto bad_cleanup_mmap;
            }
            if (ph->p_type != ELF_PT_LOAD) {
                continue ;
            }
            if (ph->p_filesz > ph->p_memsz) {
                ret = -E_INVAL_ELF;
                goto bad_cleanup_mmap;
            }
            if (ph->p_filesz == 0) {
                continue ;
            }
            vm_flags = 0, perm = PTE_U;
            if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
            if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
            if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ;
            if (vm_flags & VM_WRITE) perm |= PTE_W;
            if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) {
                goto bad_cleanup_mmap;
            }
            off_t offset = ph->p_offset;
            size_t off, size;
            uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE);
            ret = -E_NO_MEM;
            //复制数据段和代码段
            end = ph->p_va + ph->p_filesz;
            while (start < end) {
                if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                    ret = -E_NO_MEM;
                    goto bad_cleanup_mmap;
                }
                off = start - la, size = PGSIZE - off, la += PGSIZE;
                if (end < la) {
                    size -= la - end;
                }
                if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0) {
                    goto bad_cleanup_mmap;
                }
                start += size, offset += size;
            }
            //建立BSS段
            end = ph->p_va + ph->p_memsz;
            if (start < la) {
                /* ph->p_memsz == ph->p_filesz */
                if (start == end) {
                    continue ;
                }
                off = start + PGSIZE - la, size = PGSIZE - off;
                if (end < la) {
                    size -= la - end;
                }
                memset(page2kva(page) + off, 0, size);
                start += size;
                assert((end < la && start == end) || (end >= la && start == la));
            }
            while (start < end) {
                if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
                    ret = -E_NO_MEM;
                    goto bad_cleanup_mmap;
                }
                off = start - la, size = PGSIZE - off, la += PGSIZE;
                if (end < la) {
                    size -= la - end;
                }
                memset(page2kva(page) + off, 0, size);
                start += size;
            }
        }
        sysfile_close(fd);
        //(4)建立相应的虚拟内存映射表
        vm_flags = VM_READ | VM_WRITE | VM_STACK;//建立虚拟地址与物理地址之间的映射
        if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
            goto bad_cleanup_mmap;
        }
        assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
        assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
        assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
        assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);
        //(5)设置用户栈
        mm_count_inc(mm);
        current->mm = mm;
        current->cr3 = PADDR(mm->pgdir);
        lcr3(PADDR(mm->pgdir));
        //(6)处理用户栈中传入的参数,其中argc对应参数个数,uargv[]对应参数的具体内容的地址
        uint32_t argv_size=0, i;
        for (i = 0; i < argc; i ++) {
            argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
        }
        uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long)+1)*sizeof(long);
        char** uargv=(char **)(stacktop  - argc * sizeof(char *));
        argv_size = 0;
        for (i = 0; i < argc; i ++) {
            uargv[i] = strcpy((char *)(stacktop + argv_size ), kargv[i]);
            argv_size +=  strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
        }
        
        stacktop = (uintptr_t)uargv - sizeof(int);
        *(int *)stacktop = argc;
        //(7)设置进程的中断帧 
        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;
        tf->tf_eip = elf->e_entry;
        tf->tf_eflags = FL_IF;
        ret = 0;
        //(8)错误处理部分
    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;
    }
    

    实验结果

    ls
     @ is  [directory] 2(hlinks) 23(blocks) 5888(bytes) : @'.'
       [d]   2(h)       23(b)     5888(s)   .
       [d]   2(h)       23(b)     5888(s)   ..
       [-]   1(h)       10(b)    40383(s)   softint
       [-]   1(h)       11(b)    44571(s)   priority
       [-]   1(h)       11(b)    44584(s)   matrix
       [-]   1(h)       10(b)    40391(s)   faultreadkernel
       [-]   1(h)       10(b)    40381(s)   hello
       [-]   1(h)       10(b)    40382(s)   badarg
       [-]   1(h)       10(b)    40404(s)   sleep
       [-]   1(h)       11(b)    44694(s)   sh
       [-]   1(h)       10(b)    40380(s)   spin
       [-]   1(h)       11(b)    44640(s)   ls
       [-]   1(h)       10(b)    40386(s)   badsegment
       [-]   1(h)       10(b)    40435(s)   forktree
       [-]   1(h)       10(b)    40410(s)   forktest
       [-]   1(h)       10(b)    40516(s)   waitkill
       [-]   1(h)       10(b)    40404(s)   divzero
       [-]   1(h)       10(b)    40381(s)   pgdir
       [-]   1(h)       10(b)    40385(s)   sleepkill
       [-]   1(h)       10(b)    40408(s)   testbss
       [-]   1(h)       10(b)    40381(s)   yield
       [-]   1(h)       10(b)    40406(s)   exit
       [-]   1(h)       10(b)    40385(s)   faultread
    lsdir: step 4
    $ hello
    Hello world!!.
    I am process 14.
    hello pass.
    

    思考题

    在实验报告中给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案,鼓励给出详细设计方案

    1. 硬链接机制的设计实现:
      vfs中预留了硬链接的实现接口int vfs_link(char *old_path, char *new_path);。在实现硬链接机制,创建硬链接link时,为new_path创建对应的file,并把其inode指向old_path所对应的inode,inode的引用计数加1。在unlink时将引用计数减去1即可。
    2. 软链接机制的设计实现:
      vfs中预留了软链接的实现接口int vfs_symlink(char *old_path, char *new_path);。在实现软链接机制,创建软连接link时,创建一个新的文件(inode不同),并把old_path的内容存放到文件的内容中去,给该文件保存在磁盘上时disk_inode类型为SFS_TYPE_LINK,再完善对于该类型inode的操作即可。unlink时类似于删除一个普通的文件。

    和实验参考答案的对比分析。

    在lab8_result中make grade之后发现得分是0,sfs_inode.c函数基本相同;而对比proc.c中的函数,发现有以下几处区别

    init_main()函数

    参考答案中在cprintf("init check memory pass.\n");之前多了如下两行:

        assert(nr_free_pages_store == nr_free_pages());
        assert(kernel_allocated_store == kallocated());
    

    经过验证,这两句会导致一些用户进程无法正常输出"init check memory pass."导致result错误。

    函数的命名

    我自己的lab7中的copy_files()和put_files()函数在参考答案中分别被命名为copy_fs()和put_fs()函数。
    另外,我还猜想参考答案没有满分的原因是,前几次实验的代码在“迁移”的过程中可能产生了错误或者没有完全“迁移”过来。

    知识点的分析

    考察了基本的文件系统系统调用的实现方法,和基于索引节点组织方式的Simple FS文件系统的设计与实现,以及文件系统抽象层-VFS的设计与实现。前者的考察是基于分析理解,后两者则是需要具体实现。

    知识点的补充

    对于知识点的考察挺全面的,没有需要补充的地方。

    相关文章

      网友评论

          本文标题:Lab8

          本文链接:https://www.haomeiwen.com/subject/hemxxxtx.html