操作系统实验八实验报告

实验八:文件系统


练习0:填写已有实验

使用meld可以简单地将前几个lab的代码填入lab8中,其他实验完成的部分基本直接合并即可,部分代码需要做出修改但要结合练习1和练习2的内容,在练习1和练习2中指出


练习1:完成读文件操作的实现

1、ucore文件系统架构

以任一文件作为测试用例,ucore的执行顺序如下,其文件系统架构主要由四部分组成

  • 通用文件系统访问接口层:该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务
  • 文件系统抽象层:向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问,向下提供一个同样的抽象函数指针列表和数据结构屏蔽不同文件系统的实现细节
  • Simple FS文件系统层:一个基于索引方式的简单文件系统实例,向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数,向下访问外设接口
  • 外设接口层:向上提供device访问接口屏蔽不同硬件细节。向下实现访问各种具体设备驱动的接口,比如disk设备接口/串口设备接口/键盘设备接口等
文件系统测试用例
user/*.c

1.通用文件系统访问接口层
write              user/libs/file.c     
sys_write          user/libs/syscall.c         
syscall            user/libs/syscall.c             
sys_write          kern/syscall/syscall.c 

2.文件系统抽象层Virtual File System
sysfile_write      kern/fs/sysfile.c
file_write         kern/fs/file.c
vop_write          kern/fs/vfs/inode.h

3.文件系统Simple File System
sfs_write          kern/fs/sfs/sfs_inode.c
sfs_wbuf           kern/fs/sfs/sfs_io.c

4.外部I/O设备接口层
dop_io             kern/fs/devs/dev.h
disk0_io           kern/fs/devs/dev_disk0.c

硬件驱动
ide_write_secs     kern/driver/ide.c

在ucore系统中文件系统架构包含四类主要的数据结构,分别是:

  • 超级块(SuperBlock):它主要从文件系统的全局角度描述特定文件系统的全局信息,它的作用范围是整个OS空间
  • 索引节点(inode):它主要从文件系统的单个文件的角度它描述了文件的各种属性和数据所在位置,它的作用范围是整个OS空间
  • 目录项(dentry):它主要从文件系统的文件路径的角度描述了文件路径中的特定目录,它的作用范围是整个OS空间
  • 文件(file),它主要从进程的角度描述了一个进程在访问文件时需要了解的文件标识,文件读写的位置,文件引用情况等信息,它的作用范围是某一具体进程

2、Virtual File System分析

文件系统抽象层是把不同文件系统的对外共性接口提取出来,形成一个函数指针数组,这样,通用文件系统访问接口层只需访问文件系统抽象层,而不需关心具体文件系统的实现细节和接口。

进程在内核中直接访问的文件接口数据结构及文件相关信息数据结构定义在kern/fs/fs.hkern/fs/file.h

struct proc_struct{                 //进程控制块
    ...
    struct files_struct *filesp;    //文件接口
};

struct files_struct {             
    struct inode *pwd;              //进程当前执行目录的内存inode指针
    struct file *fd_array;          //进程打开文件的集合fd_array[]即filemap
    int files_count;                //打开文件的个数
    semaphore_t files_sem;          //确保互斥访问
};

struct file {
    enum {
        FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
    } status;                       //文件的当前状态
    bool readable;                  //是否可读
    bool writable;                  //是否可写
    int fd;                         //文件在fd_array[]即filemap中的索引
    off_t pos;                      //访问文件的当前位置
    struct inode *node;             //该文件对应的inode指针
    int open_count;                 //打开此文件的次数
};

当创建一个进程后,子进程将会初始化或复制父进程的files_struct,因此在练习0中需要修改do_fork的部分代码,加上如下代码用以复制文件信息

int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
    ...
    if(copy_files(clone_flags, proc) !=0){             //复制父进程的file_struct,若出错进入错误处理
        goto bad_fork_cleanup_fs;
    }
    ...
    bad_fork_cleanup_fs: 
    put_files(proc);
    ...
}

VFS中的另一类重要数据结构是inode,位于内存的索引节点,负责把不同文件系统的特定索引节点信息统一封装,并定义了能够操作此节点的函数,inode定义在kern/fs/vfs/inode.h

struct inode {
    union {                                   //包含不同文件系统特定inode信息的union成员变量
        struct device __device_info;          //设备文件系统内存inode信息
        struct sfs_inode __sfs_inode_info;    //SFS文件系统内存inode信息
    } in_info;
    enum {                                  
        inode_type_device_info = 0x1234,
        inode_type_sfs_inode_info,
    } in_type;                                //此inode所属文件系统类型
    int ref_count;                            //此inode的引用计数
    int open_count;                           //打开此inode对应文件的个数
    struct fs *in_fs;                         //inode所在的抽象的文件系统,包含访问文件系统的函数指针
    const struct inode_ops *in_ops;           //抽象的inode操作,包含访问inode的函数指针
};

struct fs {
    union {
        struct sfs_fs __sfs_info;                   
    } fs_info;                                     //具体文件系统信息,这里只有SFS
    enum {
        fs_type_sfs_info,
    } fs_type;                                     //文件系统类型
    int (*fs_sync)(struct fs *fs);                 //写回修改 
    struct inode *(*fs_get_root)(struct fs *fs);   //返回文件系统的root inode
    int (*fs_unmount)(struct fs *fs);              //解除挂载的文件系统
    void (*fs_cleanup)(struct fs *fs);             //清除文件系统
};

struct inode_ops {
    unsigned long vop_magic;
    int (*vop_open)(struct inode *node, uint32_t open_flags);
    int (*vop_close)(struct inode *node);
    ...
};

3、Simple File System分析

(1)SFS文件系统的基本布局为

| superblock | root-dir inode | freemap | inode/file data/dir data blocks |

struct sfs_fs {
    struct sfs_super super;                         //超级块superblock
    struct device *dev;                             //挂载的设备
    struct bitmap *freemap;                         //空闲区域freemap
    bool super_dirty;                               //修改标记
    void *sfs_buffer;                            
    semaphore_t fs_sem;                       
    semaphore_t io_sem;                      
    semaphore_t mutex_sem;                          
    list_entry_t inode_list;                    
    list_entry_t *hash_list;                  
};
  • 超级块(superblock):包含了关于文件系统的所有关键参数,当计算机被启动或文件系统被首次接触时,超级块的内容就会被装入内存
struct sfs_super {
    uint32_t magic;                                 //魔数magic = SFS_MAGIC = 0x2F8DBE2A,检测是否合法的SFS img
    uint32_t blocks;                                //SFS中所有block的数量
    uint32_t unused_blocks;                         //SFS中所有未被使用的block的数量
    char info[SFS_MAX_INFO_LEN + 1];                //info = "simple file system"
};
  • 根目录(root-dir inode):SFS文件系统的根结点,通过这个root-dir的inode信息就可以定位并查找到根目录下的所有文件信息
  • 空闲区域(freemap):根据SFS中所有块的数量,用1个位来表示一个块的占用和未被占用的情况,相关操作定义在kern/fs/sfs/bitmap.[ch]
  • 其他(inode/file data/dir data blocks):所有其他目录和文件的inode信息和内容数据信息,为了便于实现,此处每个inode占用一个完整的block(4KB)

(2)磁盘索引节点
SFS中的磁盘索引节点代表了一个实际位于磁盘上的文件。
如果inode表示的是文件,则成员变量direct[]直接指向了保存文件内容数据的数据块索引值。indirect间接指向了保存文件内容数据的数据块,indirect指向的是间接数据块(indirect block),此数据块实际存放的全部是数据块索引,这些数据块索引指向的数据块才被用来存放文件内容数据

struct sfs_disk_inode {
    uint32_t size;                                  //文件大小
    uint16_t type;                                  //文件类型
    uint16_t nlinks;                                //硬链接数
    uint32_t blocks;                                //block个数
    uint32_t direct[SFS_NDIRECT];                   //直接数据块索引
    uint32_t indirect;                              //一级间接数据块索引,为0时表示不使用一级间接数据块
};

struct sfs_disk_entry {                             //若inode是目录,索引指向的数据块为sfs_disk_entry
    uint32_t ino;                                   //索引节点所占数据块的索引值
    char name[SFS_MAX_FNAME_LEN + 1];               //文件名
};

(3)内存索引节点
内存inode是在打开一个文件后才创建的,包含有磁盘索引节点,如果关机则相关信息都会消失。而硬盘inode的内容是保存在硬盘中的,只是在进程需要时才被读入到内存中,用于访问文件或目录的具体内容数据。

struct sfs_inode {
    struct sfs_disk_inode *din;                     //对应的磁盘索引节点
    uint32_t ino;                                   //inode编号
    bool dirty;                                     //修改标记
    int reclaim_count;                              
    semaphore_t sem;                                //对磁盘索引节点的互斥访问
    list_entry_t inode_link;                        
    list_entry_t hash_link;                         
};

4、读文件操作的实现

整体流程即ucore文件系统架构中描述的过程,详细函数调用处理如下

打开文件

(1)通用文件访问接口层的处理
调用如下用户态函数: open->sys_open->syscall,从而引起系统调用进入到内核态。
进入内核态后,通过中断处理例程,会调用sys_open->sysfile_open内核函数。到了这里,需要把位于用户空间的字符串/test/testfile拷贝到内核空间中的字符串path中,并进入到文件系统抽象层的处理流程完成进一步的打开文件操作中。

(2)文件系统抽象层VFS的处理
调用file_open给打开的文件分配一个struct file,这个元素在struct file *fd_array中的索引值就是要返回给用户进程的文件描述符fd
调用vfs_open,进一步调用vfs_lookup找到path对应的基于inode数据结构的VFS索引节点,再调用vop_open打开文件。

(3)SFS文件系统层的处理
在(2)中调用vfs_lookup时会调用vop_lookup来找到具体的文件。
vop_lookup在ucore中实际指的是sfs_lookup(定义在sfs_inode.c中),sfs_lookup/为分割符,从左至右逐一分解path获得各个子目录和最终文件对应的inode节点。在本例中是分解出test子目录,并调用sfs_lookup_once函数获得子目录对应的inode节点subnode,然后循环进一步调用sfs_lookup_once查找以test子目录下的文件testfile1所对应的inode节点。
sfs_lookup_once将调用sfs_dirent_search_nolock函数来查找与路径名匹配的目录项,如果找到目录项,则根据目录项中记录的inode所处的数据块索引值找到路径名对应的SFS磁盘inode,并读入SFS磁盘inode对的内容,创建SFS内存inode。

/*
 * sfs_lookup - Parse path relative to the passed directory
 *              DIR, and hand back the inode for the file it
 *              refers to.
 */
static int
sfs_lookup(struct inode *node, char *path, struct inode **node_store) {
    struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs);
    assert(*path != '\0' && *path != '/');
    vop_ref_inc(node);
    struct sfs_inode *sin = vop_info(node, sfs_inode);
    if (sin->din->type != SFS_TYPE_DIR) {
        vop_ref_dec(node);
        return -E_NOTDIR;
    }
    struct inode *subnode;
    int ret = sfs_lookup_once(sfs, sin, path, &subnode, NULL);

    vop_ref_dec(node);
    if (ret != 0) {
        return ret;
    }
    *node_store = subnode;
    return 0;
}

/*
 * sfs_lookup_once - find inode corresponding the file name in DIR's sin inode 
 * @sfs:        sfs file system
 * @sin:        DIR sfs inode in memory
 * @name:       the file name in DIR
 * @node_store: the inode corresponding the file name in DIR
 * @slot:       the logical index of file entry
 */
static int
sfs_lookup_once(struct sfs_fs *sfs, struct sfs_inode *sin, const char *name, struct inode **node_store, int *slot) {
    int ret;
    uint32_t ino;
    lock_sin(sin);
    {   // find the NO. of disk block and logical index of file entry
        ret = sfs_dirent_search_nolock(sfs, sin, name, &ino, slot, NULL);
    }
    unlock_sin(sin);
    if (ret == 0) {
        // load the content of inode with the the NO. of disk block
        ret = sfs_load_inode(sfs, node_store, ino);
    }
    return ret;
}

读文件

(1)通用文件访问接口层的处理
调用如下用户态函数: read->sys_read->syscall,从而引起系统调用进入到内核态。
进入内核态后,通过中断处理例程,会调用sys_read->sysfile_read内核函数,并进入VFS进一步操作。

(2)文件系统抽象层VFS的处理
首先检查错误并分配缓冲空间,没有问题就进行读文件操作,调用file_read函数将文件内容读取到缓冲区中,然后再将内容拷贝到用户内存空间,循环直到读取完成。
file_read调用fd2file函数找到对应的file结构,并检查是否可读。调用fd_array_acquire函数使打开这个文件的计数加1。调用vop_read函数将文件内容读到iob中。调整文件指针偏移量的值,使其向后移动实际读到的字节数iobuf_used(iob)。最后调用fd_array_release函数使打开这个文件的计数减1,若打开计数为0,则释放file。

(3)SFS文件系统层的处理
在(2)中调用file_read时会调用vop_read来读文件。
vop_read在ucore中实际指的是sfs_read(定义在sfs_inode.c中),sfs_read调用sfs_io先确定文件inode对应的struct sfs_fsstruct sfs_inode,再进一步调用sfs_io_nolock完成读取文件的操作,具体实现为练习1的内容,如下

/*  
 * 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);
    off_t endpos = offset + *alenp, blkoff;
    *alenp = 0;
    // calculate the Rd/Wr end position
    //对边界的预先计算
    if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos) {
        return -E_INVAL;
    }
    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
     *  sfs_bmap_load_nolock:将对应 sfs_inode 的第 index 个索引指向的 block 的索引值取出存到相应的指针指向的单元(ino_store),具体实现定义在sfs_inode.c中
    */
    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){   //调用sfs_bmap_load_nolock获得内存索引节点第index个索引指向的block索引值存到ino中
            goto out;
        }
        if((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0){       //实际调用sfs_rbuf完成读取
            goto out;
        }
        alen += size;                              //alen记录实际读取长度
        if(nblks == 0){
            goto out;
        }
        blkno ++;                                  //blkno记录读取的块编号
        nblks --;                                  //nblks记录读取的块数量
        buf += size;                               //更新缓冲区指针
    }
    size = SFS_BLKSIZE;
    while(nblks){                                  //第二部分,循环读取对齐的整块数据
        if((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0){
            goto out;
        }
        if((ret = sfs_block_op(sfs, buf, ino, 1)) != 0){     //实际调用sfs_rblock一次性读取1块block长度
            goto out;
        }
        alen += size;                              //第二部分连续整块读取,故size=SFS_BLKSIZE
        blkno ++;
        nblks --;
        buf += size;
    }
    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;                                 //alenp指向实际读取的长度alen
    if (offset + alen > sin->din->size) {          //若操作的长度超过了原节点记录的长度(写操作会发生,读不会),更新节点信息并标记需要写回
        sin->din->size = offset + alen;
        sin->dirty = 1;
    }
    return ret;
}

练习2:完成基于文件系统执行程序机制的实现

在lab5用户进程管理中,我们通过load_icode实现了运行用户程序,但是当时是直接将执行文件附在ucore的末端并定义全局变量记录程序入口,具体参考lab5实验报告

http://blog.csdn.net/jasonyuchen/article/details/77018104

在lab8实现了文件系统后,执行程序的机制需要略加修改,主要体现了load_icode函数中,对比当时实现的load_icode,文件系统更新后,函数的参数变为了文件标识符fd,执行程序的入口参数个数argc及具体参数内容kargv,具体变化及注释如下

static int
load_icode(int fd, int argc, char **kargv) {
    /* LAB8:EXERCISE2 YOUR CODE  HINT:how to load the file with handler fd  in to process's memory? how to setup argc/argv?
     * MACROs or Functions:
     *  mm_create        - create a mm
     *  setup_pgdir      - setup pgdir in mm
     *  load_icode_read  - read raw data content of program file
     *  mm_map           - build new vma
     *  pgdir_alloc_page - allocate new memory for  TEXT/DATA/BSS/stack parts
     *  lcr3             - update Page Directory Addr Register -- CR3
     */
    assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);

    if(current->mm != NULL){
        panic("load_icode: current->mm must be empty.\n");
    }
    int ret = -E_NO_MEM;

    //(1) create a new mm for current process
    struct mm_struct *mm;
    if((mm = mm_create()) == NULL){
        goto bad_mm;
    }

    //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
    if(setup_pgdir(mm) != 0){
        goto bad_pgdir_cleanup_mm;
    }

    //(3) copy TEXT/DATA/BSS parts in binary to memory space of process
    struct Page *page;
    struct elfhdr __elf, *elf = &__elf;
    //(3.1) read raw data content in file and resolve elfhdr
    //与lab5不同,由于不是直接获得binary文件,故这里调用load_icode_read来获得ELF header
    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 ++){
        //(3.2) read raw data content in file and resolve proghdr based on info in elfhdr
        //与lab5不同,这里调用load_icode_read来获得program header
        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;
        }
        //(3.3) call mm_map to build vma related to TEXT/DATA
        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){
            //(3.4) call pgdir_alloc_page to allocate page for TEXT/DATA, read contents in file and copy them into the new allocated pages
            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;
            }
            //与lab5直接memcpy不同,这里调用load_icode_read来复制数据
            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;
        if(start < la){
            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){
            //(3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
            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) call mm_map to setup user stack, and put parameters into 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;
    }
    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) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO)
    mm_count_inc(mm);
    current->mm = mm;
    current->cr3 = PADDR(mm->pgdir);
    lcr3(PADDR(mm->pgdir));

    //(6) setup uargc and uargv in user stacks
    //   ----------
    //   |kargv[i]|   <--uargv[i]
    //   ----------
    //   | ...... |
    //   ----------
    //   |kargv[0]|   <--uargv[0]
    //   ----------
    //   |  argc  |   <--stacktop
    //   ----------
    uint32_t argv_size = 0, i;
    //循环累计每个具体参数的长度,记录在argv_size中,注意/0因此要加1
    for(i = 0; i < argc; i++){    
        argv_size += strnlen(kargv[i], EXEC_MAX_ARG_LEN + 1) + 1;
    }
    //更新栈顶为原栈顶减去argv_size值,argv_size需要8字节对齐并向上取整故要做如下处理
    uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long) + 1) * sizeof(long);
    //设置uargv指向第1个指向具体参数指针的指针
    char ** uargv = (char **)(stacktop - argc * sizeof(char *));
    argv_size = 0;
    //循环使得uargv[i]指向第i个具体的参数
    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;
    }
    //最后栈顶再往下一个空间存储参数个数argc
    stacktop = (uintptr_t)uargv - sizeof(int);
    *(int *)stacktop = argc;

    //(7) setup trapframe for user environment
    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;

out:
    return ret;

//(8) if up steps failed, you should cleanup the env.
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 qemu可以获得输出如下,输入lshelloexit等指令运行程序,可以看出实验成功

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)    40200(s)   badarg
   [-]   1(h)       10(b)    40204(s)   badsegment
   [-]   1(h)       10(b)    40220(s)   divzero
   [-]   1(h)       10(b)    40224(s)   exit
   [-]   1(h)       10(b)    40204(s)   faultread
   [-]   1(h)       10(b)    40208(s)   faultreadkernel
   [-]   1(h)       10(b)    40228(s)   forktest
   [-]   1(h)       10(b)    40252(s)   forktree
   [-]   1(h)       10(b)    40200(s)   hello
   [-]   1(h)       10(b)    40360(s)   ls
   [-]   1(h)       10(b)    40304(s)   matrix
   [-]   1(h)       10(b)    40192(s)   pgdir
   [-]   1(h)       10(b)    40292(s)   priority
   [-]   1(h)       11(b)    44508(s)   sh
   [-]   1(h)       10(b)    40220(s)   sleep
   [-]   1(h)       10(b)    40204(s)   sleepkill
   [-]   1(h)       10(b)    40200(s)   softint
   [-]   1(h)       10(b)    40196(s)   spin
   [-]   1(h)       10(b)    40224(s)   testbss
   [-]   1(h)       10(b)    40332(s)   waitkill
   [-]   1(h)       10(b)    40200(s)   yield
lsdir: step 4
$ hello
Hello world!!.
I am process 14.
hello pass.
$ exit
I am the parent. Forking the child...
I am parent, fork a child pid 16
I am the parent, waiting now..
I am the child.
waitpid 16 ok.
exit pass.
$ sfs: cleanup: 'simple file system' (259/32509/32768)
all user-mode processes have quit.
init check memory pass.
kernel panic at kern/process/proc.c:534:
    initproc exit.

ucore系列实验的总结

至此ucore的八个实验全部完成,每次涉及到的层次多,例如lab1从底层开始启动os的过程或是lab8从顶层向下访问底层的文件,就会容易犯难。对照着参考答案、piazza上同学们和老师们的指导以及其他同学完成的实验报告,磕磕碰碰总算也是做完了全部的实验。收获很多,尤其在如何阅读大量代码、如何利用GDB调试、理解os运行等各个方面上都有很多新的收获,非常感谢两位老师及诸多助教、同学们将这门课程打造的如此精美,让我能过通过在线平台也能完成学习。

与各位踏实学习的同学共勉!

你可能感兴趣的:(OperatingSystem)