操作系统实验ucore_lab8实验报告

lab8

练习0:填写已有实验

proc.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
trap.c
sche.c
monitor.
check_sync.c

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

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

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

当进行文件读取/写入操作时,最终uCore都会执行到sfs_io_nolock函数。在该函数中,我们要完成对设备上基础块数据的读取与写入。

在进行读取/写入前,我们需要先将数据与基础块对齐,以便于使用sfs_block_op函数来操作基础块,提高读取/写入效率。

但一旦将数据对齐后会存在一个问题:

  • 待操作数据的前一小部分有可能在最前的一个基础块的末尾位置
  • 待操作数据的后一小部分有可能在最后的一个基础块的起始位置

我们需要分别对这第一和最后这两个位置的基础块进行读写/写入,因为这两个位置的基础块所涉及到的数据都是部分的。而中间的数据由于已经对齐好基础块了,所以可以直接调用sfs_block_op来读取/写入数据。以下是相关操作的实现:

/*  
 * sfs_io_nolock - Rd/Wr a file contentfrom offset position to offset+ length  disk blocks<-->buffer (in memroy)
 * @sfs:      sfs file system
 * @sin:      sfs inode in memory
 * @buf:      the buffer Rd/Wr
 * @offset:   the offset of file
 * @alenp:    the length need to read (is a pointer). and will RETURN the really Rd/Wr lenght
 * @write:    BOOL, 0 read, 1 write
 */
static int sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
    struct sfs_disk_inode *din = sin->din;
    assert(din->type != SFS_TYPE_DIR);
  // calculate the Rd/Wr end position
    // 计算缓冲区读取/写入的终止位置
    off_t endpos = offset + *alenp, blkoff;
    *alenp = 0;
    if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos)
        return -E_INVAL;
    // 如果偏移与终止位置相同,及欲读取/写入0字节的数据
    if (offset == endpos) {
        // 直接返回
        return 0;
    }
    if (endpos > SFS_MAX_FILE_SIZE)
        endpos = SFS_MAX_FILE_SIZE;
    if (!write) {
        // 如果是读取数据,并冲区中剩余的数据超出一个硬盘节点的数据大小
        if (offset >= din->size) {
            // 直接返回,读取失败
            return 0;
        }
        if (endpos > din->size)
            endpos = din->size;
    }
    // 根据不同的执行函数,设置对应的函数指针
    int (*sfs_buf_op)(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
    int (*sfs_block_op)(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
    if (write)
        sfs_buf_op = sfs_wbuf, sfs_block_op = sfs_wblock;
    else
        sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock;

    int ret = 0;
    size_t size, alen = 0;
    uint32_t ino;
    uint32_t blkno = offset / SFS_BLKSIZE;          // The NO. of Rd/Wr begin block
    uint32_t nblks = endpos / SFS_BLKSIZE - blkno;  // The size of Rd/Wr blocks

  //LAB8:EXERCISE1 YOUR CODE HINT: call sfs_bmap_load_nolock, sfs_rbuf, sfs_rblock,etc. read different kind of blocks in file
  /*
   * (1) If offset isn't aligned with the first block, Rd/Wr some content from offset to the end of the first block
   *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
   *               Rd/Wr size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset)
   * (2) Rd/Wr aligned blocks
   *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_block_op
     * (3) If end position isn't aligned with the last block, Rd/Wr some content from begin to the (endpos % SFS_BLKSIZE) of the last block
   *       NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
  */
    // 对齐偏移。如果偏移没有对齐第一个基础块,则多读取/写入第一个基础块的末尾数据
    if ((blkoff = offset % SFS_BLKSIZE) != 0) {
        size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
        // 获取第一个基础块所对应的block的编号`ino`
        if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0)
            goto out;
        // 通过上一步取出的`ino`,读取/写入一部分第一个基础块的末尾数据
        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 = SFS_BLKSIZE;
    while (nblks != 0) {
        // 获取inode对应的基础块编号
        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;
    }
out:
    *alenp = alen;
    if (offset + alen > sin->din->size) {
        sin->din->size = offset + alen;
        sin->dirty = 1;
    }
    return ret;
}
  • 给出设计实现”UNIX的PIPE机制“的概要设方案
    • PIPE管道机制是进程间通信的较为重要的一种方式。在VFS中,最简单的做法就是在磁盘上建立一块pipe缓冲文件pipe_tmp。之后,当打开了pipe_tmp文件的某进程fork出子进程后,父子进程就可以通过读写同一文件来实现进程间通信。
    • 但实际上,上述的进程间通信是十分低效的,因为需要调用多个函数来完成文件读写,同时硬盘的读写速率也远远小于内存。由于用户与实际的文件系统间由虚拟文件系统VFS调控,因此我们可以在内存中根据文件系统规范,建立虚拟pipe缓冲区域文件来代替磁盘上的缓冲文件,这样便可大大提高通信速率。

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

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

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

基于文件系统的执行程序机制,有几部分地方需要添加代码,分别是alloc_procdo_forkload_icode三个函数。

1、alloc_proc
  • 这个函数需要添加的内容最少,只需多补充一个struct files_struct *filesp的初始化即可

  • 修改后的源码如下

    COPYstatic struct proc_struct *
    alloc_proc(void) {
        struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));
        if (proc != NULL) {
            // ...
    
            //LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ...
            // LAB8 添加一个filesp指针的初始化
            proc->filesp = NULL;
        }
        return proc;
    }
    
2、do_fork
  • fork机制在原先lab7的基础上,多了file_struct结构的复制操作与执行失败时的重置操作。

    这两部操作分别需要调用copy_filesput_files函数

  • 修改后的源码如下

    COPYint
    do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
        int ret = -E_NO_FREE_PROC;
        struct proc_struct *proc;
        if (nr_process >= MAX_PROCESS) {
            goto fork_out;
        }
        ret = -E_NO_MEM;
        if ((proc = alloc_proc()) == NULL) {
            goto fork_out;
        }
        proc->parent = current;
        assert(current->wait_state == 0);
    
        if (setup_kstack(proc) != 0) {
            goto bad_fork_cleanup_proc;
        }
        //LAB8:EXERCISE2 YOUR CODE  HINT:how to copy the fs in parent's proc_struct?
        // LAB8 将当前进程的fs复制到fork出的进程中
        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);
    
        bool intr_flag;
        local_intr_save(intr_flag);
        {
            proc->pid = get_pid();
            hash_proc(proc);
            set_links(proc);
    
        }
        local_intr_restore(intr_flag);
    
        wakeup_proc(proc);
    
        ret = proc->pid;
    fork_out:
        return ret;
      // LAB8 如果复制失败,则需要重置原先的操作
    bad_fork_cleanup_fs:  //for LAB8
        put_files(proc);
    bad_fork_cleanup_kstack:
        put_kstack(proc);
    bad_fork_cleanup_proc:
        kfree(proc);
        goto fork_out;
    }
    
3、load_icode

该函数可以在lab7原先的基础上进行修改,不需要从0开发。

  • 原先lab7源码中,读取可执行文件是直接读取内存的,但在这里需要使用函数load_icode_read来从文件系统中读取ELF header以及各个段的数据。

  • 原先Lab7的load_icode函数中并没有对execve所执行的程序传入参数,而我们需要在lab8中补充这个实现。

  • 补充后的源码如下

    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");
        }
    
        int ret = -E_NO_MEM;
        // 创建proc的内存管理结构
        struct mm_struct *mm;
        if ((mm = mm_create()) == NULL) {
            goto bad_mm;
        }
        if (setup_pgdir(mm) != 0) {
            goto bad_pgdir_cleanup_mm;
        }
    	 //(2)建立页目录
        struct Page *page;
        //############## LAB8 这里要从文件中读取ELF header,而不是Lab7中的内存了#############
        struct elfhdr __elf, *elf = &__elf;
        if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {
            goto bad_elf_cleanup_pgdir;
        }
        // 判断读取入的elf header是否正确
        if (elf->e_magic != ELF_MAGIC) {
            ret = -E_INVAL_ELF;
            goto bad_elf_cleanup_pgdir;
        }
        //(3)从文件加载程序到内存
        // 根据每一段的大小和基地址来分配不同的内存空间
        struct proghdr __ph, *ph = &__ph;
        uint32_t vm_flags, perm, phnum;
        for (phnum = 0; phnum < elf->e_phnum; phnum ++) {
        //########### LAB8 从文件特定偏移处读取每个段的详细信息(包括大小、基地址等等)#############
            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;
                }
      //################## LAB8 读取elf对应段内的数据并写入至该内存中########################################
                if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0) {
                    goto bad_cleanup_mmap;
                }
                start += size, offset += size;
            }
            end = ph->p_va + ph->p_memsz;
            // 对于段中当前页中剩余的空间(复制elf数据后剩下的空间),将其置为0
            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));
            }
            // 对于段中剩余页中的空间(复制elf数据后的多余页面),将其置为0
            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;
            }
        }
        // 关闭读取的ELF
        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);
        // 设置CR3页表相关寄存器
        current->mm = mm;
        current->cr3 = PADDR(mm->pgdir);
        lcr3(PADDR(mm->pgdir));
    //(6)处理用户栈中传入的参数,其中argc对应参数个数,uargv[]对应参数的具体内容的地址
        // #######################LAB8 设置execve所启动的程序参数############################
        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;
    }
    
  • 给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案
    • SFS中已经预留出硬链接/软链接的相关定义(没有实现)
      COPY/*
       * VFS layer high-level operations on pathnames
       *
       *    vfs_link         - Create a hard link to a file.
       *    vfs_symlink      - Create a symlink PATH containing contents CONTENTS.
       *    vfs_unlink       - Delete a file/directory.
       *
       */
      int vfs_link(char *old_path, char *new_path);
      int vfs_symlink(char *old_path, char *new_path);
      int vfs_unlink(char *path);
      
    • 硬链接机制的实现
      • 创建硬链接时,仍然为new_path建立一个sfs_disk_entry结构,但该结构的内部ino成员指向old_path的磁盘索引结点,并使该磁盘索引节点的nlinks引用计数成员加一即可。
      • 删除硬链接时,令对应磁盘结点sfs_disk_inode中的nlinks减一,同时删除硬链接的sfs_disk_entry结构即可。
    • 软链接的实现
      • 与创建硬链接不同,创建软链接时要多建立一个sfs_disk_inode结构(即建立一个全新的文件)。之后,将old_path写入该文件中,并标注sfs_disk_inodetypeSFS_TYPE_LINK即可。
      • 删除软链接与删除文件的操作没有区别,直接将对应的sfs_disk_entrysfs_disk_inode结构删除即可。

重要知识点

1. 文件系统和文件

  • 文件系统是操作系统中管理持久性数据的子系统,提供数据存储和访问功能
    • 组织、检索、读写访问数据
    • 大多数计算机系统都有文件系统
    • Google也是一个文件系统
  • 文件是具有符号名,由字节序列构成的数据项集合
    • 文件系统的基本数据单位
    • 文件名是文件的标识符号
  • 文件系统的功能
    • 分配文件磁盘空间
      • 管理文件块(位置和顺序)
      • 管理空闲空间(位置)
      • 分配算法(策略)
    • 管理文件集合
      • 定位:文件及其内容
      • 命名:通过名字找到文件
      • 文件系统结构:文件组织方式
    • 数据可靠和安全
      • 安全:多层次保护数据
      • 可靠
        • 持久保存文件
        • 避免系统崩溃、媒体错误、攻击等
  • 文件属性
    • 名称、类型、大小、位置、保护、创建者、创建时间、最近修改时间
    • 文件头:文件系统元数据中的文件信息
      • 文件属性
      • 文件存储位置和顺序

2. 文件缓存和打开文件

  • 数据块缓存

    • 数据块按需读入内存
      • 提供read()操作
      • 预读:预先读取后面的数据块
    • 数据块使用后被缓存
      • 假设数据将会再次用到
      • 写操作可能被缓存和延迟写入
    • 两种数据块缓存方式
      • 数据块缓存
      • 页缓存:统一缓存数据块和内存页
  • 页缓存

    • 虚拟页式存储:在虚拟地址空间中虚拟页面可映射到本地外存文件中
    • 文件数据块的页缓存
      • 在虚拟内存中文件数据块被映射成页
      • 文件的读写操作被转换成对内存的访问
      • 可能导致缺页或设置为脏页
      • 存在的问题:页置换算法需要协调虚拟存储和页缓存间的页面数
  • 文件系统中打开文件的数据结构

    • 文件描述符
      • 每个被打开的文件都有一个文件描述符
      • 文件状态信息:目录项、当前文件指针、文件操作设置等
    • 打开文件表
      • 每个进程都有一个进程打开文件表
      • 一个系统级的打开文件表
      • 有文件被打开时,文件卷就不能被卸载
  • 打开文件锁

    一些文件系统提供文件锁,用于协调多进程的文件访问

    • 强制——根据锁保持情况和访问需求确定是否拒绝访问
    • 劝告——进程可以查找锁的状态来决定怎么处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值