天天看點

作業系統ucore lab8實驗報告作業系統lab8實驗報告

作業系統lab8實驗報告

本次實驗涉及的是檔案系統,通過分析了解 ucore 檔案系統的總體架構設計,完善讀寫檔案操作,從新實作基于檔案系統的執行程式機制(即改寫

do_execve

),進而可以完成執行存儲在磁盤上的檔案和實作檔案讀寫等功能。可以看到,在

kern_init

函數中,多了一個

fs_init

函數的調用。

fs_init

函數就是檔案系統初始化的總控函數,它進一步調用了虛拟檔案系統初始化函數

vfs_init

,與檔案相關的裝置初始化函數

dev_init

Simple FS

檔案系統的初始化函數

sfs_init

。這三個初始化函數聯合在一起,協同完成了整個虛拟檔案系統、SFS檔案系統和檔案系統對應的裝置(鍵盤、序列槽、磁盤)的初始化工作。

練習0:填寫已有實驗

同樣運用一款名為

meld

的軟體,将已完成的lab7和lab8進行對比,大緻截圖如下:

作業系統ucore lab8實驗報告作業系統lab8實驗報告

需要修改的檔案羅列如下:

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

同樣隻需要依次将實驗1-7的代碼填入本實驗中代碼即可,無需進行其他的修改。

練習1 完成讀檔案操作的實作

要求是首先了解打開檔案的處理流程,然後參考本實驗後續的檔案讀寫操作的過程分析,編寫在

sfs_inode.c

sfs_io_nolock

讀檔案中資料的實作代碼。

根據實驗指導書,我們可以了解到,ucore的檔案系統架構主要由四部分組成:

  • 通用檔案系統通路接口層

    :該層提供了一個從使用者空間到檔案系統的标準通路接口。這一層通路接口讓應用程式能夠通過一個簡單的接口獲得ucore核心的檔案系統服務。
  • 檔案系統抽象層

    :向上提供一個一緻的接口給核心其他部分(檔案系統相關的系統調用實作子產品和其他核心功能子產品)通路。向下提供一個抽象函數指針清單和資料結構來屏蔽不同檔案系統的實作細節。
  • Simple FS檔案系統層

    :一個基于索引方式的簡單檔案系統執行個體。向上通過各種具體函數實作以對應檔案系統抽象層提出的抽象函數。向下通路外設接口
  • 外設接口層

    :向上提供device通路接口屏蔽不同硬體細節。向下實作通路各種具體裝置驅動的接口,比如disk裝置接口/序列槽裝置接口/鍵盤裝置接口等。

這裡借助實驗指導書中的一張圖可以比較好的了解這四個部分的關系:

作業系統ucore lab8實驗報告作業系統lab8實驗報告

接下來分析下打開一個檔案的詳細處理的流程。

例如某一個應用程式需要操作檔案(增删讀寫等),首先需要通過檔案系統的通用檔案系統通路接口層給使用者空間提供的通路接口進入檔案系統内部,接着由檔案系統抽象層把通路請求轉發給某一具體檔案系統(比如

Simple FS檔案系統

),然後再由具體檔案系統把應用程式的通路請求轉化為對磁盤上的block的處理請求,并通過外設接口層交給磁盤驅動例程來完成具體的磁盤操作。

在介紹ucore中打開檔案的具體流程之前,先簡單分析下一些重要的資料結構,如下:

首先是

file

資料結構:

struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status;       //通路檔案的執行狀态
bool readable; //檔案是否可讀
bool writable; //檔案是否可寫
int fd;        //檔案在filemap中的索引值
off_t pos;    //通路檔案的目前位置
struct inode *node;//該檔案對應的記憶體inode指針
atomic_t open_count;//打開此檔案的次數
};
           

接下來

inode

資料結構,它是位于記憶體的索引節點,把不同檔案系統的特定索引節點資訊(甚至不能算是一個索引節點)統一封裝起來,避免了程序直接通路具體檔案系統

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 = ,
inode_type_sfs_inode_info,
} in_type;  //此inode所屬檔案系統類型
atomic_t ref_count;   //此inode的引用計數
atomic_t open_count;  //打開此inode對應檔案的個數
struct fs *in_fs;     //抽象的檔案系統,包含通路檔案系統的函數指針
const struct inode_ops *in_ops;   //抽象的inode操作,包含通路inode的函數指針
};
           

對應到我們的

ucore

上,具體的過程如下:

  • 1、 以打開檔案為例,首先使用者會在程序中調用

    safe_open()

    函數,然後依次調用如下函數

    open->sys_open->syscall

    ,進而引發系統調用然後進入核心态,然後會由

    sys_open

    核心函數處理系統調用,進一步調用到核心函數

    sysfile_open

    ,然後将字元串

    "/test/testfile"

    拷貝到核心空間中的字元串path中,并進入到檔案系統抽象層的處理流程完成進一步的打開檔案操作中。
  • 2、 在檔案系統抽象層,系統會配置設定一個file資料結構的變量,這個變量其實是current->fs_struct->filemap[]中的一個空元素,即還沒有被用來打開過檔案,但是配置設定完了之後還不能找到對應對應的檔案結點。是以系統在該層調用了

    vfs_open

    函數通過調用

    vfs_lookup

    找到path對應檔案的inode,然後調用

    vop_open

    函數打開檔案。然後層層傳回,通過執行語句

    file->node=node;

    ,就把目前程序的

    current->fs_struct->filemap[fd]

    (即file所指變量)的成員變量node指針指向了代表檔案的索引節點node。這時傳回fd。最後完成打開檔案的操作。
  • 3、 在第2步中,調用了

    SFS檔案系統層

    vfs_lookup

    函數去尋找node,這裡在

    sfs_inode.c

    中我們能夠知道

    .vop_lookup = sfs_lookup

    ,是以講繼續跟進看

    sfs_lookup

    函數,如下:
    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 != '/');    //以“/”為分割符,從左至右逐一分解path獲得各個子目錄和最終檔案對應的inode節點。
    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);  //循環進一步調用sfs_lookup_once查找以“test”子目錄下的檔案“testfile1”所對應的inode節點。
    
    vop_ref_dec(node);
    if (ret != ) {  
        return ret;
    }
    *node_store = subnode;  //當無法分解path後,就意味着找到了需要對應的inode節點,就可順利傳回了。
    return ;
    }
               

    看到函數傳入的三個參數,其中node是根目錄“/”所對應的inode節點;path是檔案的絕對路徑(例如“/test/file”),而node_store是經過查找獲得的file所對應的inode節點。

    函數以“/”為分割符,從左至右逐一分解path獲得各個子目錄和最終檔案對應的inode節點。在本例中是分解出“test”子目錄,并調用sfs_lookup_once函數獲得“test”子目錄對應的inode節點subnode,然後循環進一步調用sfs_lookup_once查找以“test”子目錄下的檔案“testfile1”所對應的inode節點。當無法分解path後,就意味着找到了testfile1對應的inode節點,就可順利傳回了。

    而我們再進一步觀察

    sfs_lookup_once

    函數,它調用

    sfs_dirent_search_nolock

    函數來查找與路徑名比對的目錄項,如果找到目錄項,則根據目錄項中記錄的inode所處的資料塊索引值找到路徑名對應的SFS磁盤inode,并讀入SFS磁盤inode對的内容,建立SFS記憶體inode。

    如下:

    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 == ) {
        // load the content of inode with the the NO. of disk block
        ret = sfs_load_inode(sfs, node_store, ino);
    }
    return ret;
    }
               

這樣我們就大概了解一個檔案操作的具體流程,接下來我們需要完成

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) != ) {                   //讀取第一部分的資料
    size = (nblks != ) ? (SFS_BLKSIZE - blkoff) : (endpos - offset); //計算第一個資料塊的大小
    if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != ) {   //找到記憶體檔案索引對應的block的編号ino
        goto out;
    }

    if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != ) {   
        goto out;
    }
    //完成實際的讀寫操作
    alen += size;
    if (nblks == ) {
        goto out;
    }
    buf += size, blkno ++, nblks --;
}

//讀取中間部分的資料,将其分為size大學的塊,然後一次讀一塊直至讀完
size = SFS_BLKSIZE;
while (nblks != ) {
    if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != ) {
        goto out;
    }
    if ((ret = sfs_block_op(sfs, buf, ino, )) != ) {
        goto out;
    }
    alen += size, buf += size, blkno ++, nblks --;
}
//讀取第三部分的資料
if ((size = endpos % SFS_BLKSIZE) != ) {
    if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != ) {
        goto out;
    }
    if ((ret = sfs_buf_op(sfs, buf, size, ino, )) != ) {
        goto out;
    }
    alen += size;
}
......
......
           

這裡分為三部分來讀取檔案,每次通過

sfs_bmap_load_nolock

函數擷取檔案索引編号,然後調用

sfs_buf_op

完成實際的檔案讀寫操作。

練習2 完成基于檔案系統的執行程式機制的實作

實驗要求改寫

proc.c

中的 load_icode 函數和其他相關函數,實作基于檔案系統的執行程式機制。

proc.c

中,根據注釋我們需要先初始化fs中的程序控制結構,即在

alloc_proc

函數中我們需要做一下修改,加上一句

proc->filesp = NULL;

進而完成初始化。

修改之後

alloc_proc

函數如下:

static struct proc_struct * alloc_proc(void) {  
    struct proc_struct *proc = kmalloc(sizeof(struct proc_struct));  
    if (proc != NULL) {  
        proc->state = PROC_UNINIT;  
        proc->pid = -;  
        proc->runs = ;  
        proc->kstack = ;  
        proc->need_resched = ;  
        proc->parent = NULL;  
        proc->mm = NULL;  
        memset(&(proc->context), , sizeof(struct context));  
        proc->tf = NULL;  
        proc->cr3 = boot_cr3;  
        proc->flags = ;  
        memset(proc->name, , PROC_NAME_LEN);  
        proc->wait_state = ;  
        proc->cptr = proc->optr = proc->yptr = NULL;  
        proc->rq = NULL;  
        proc->run_link.prev = proc->run_link.next = NULL;  
        proc->time_slice = ;  
        proc->lab6_run_pool.left = proc->lab6_run_pool.right = proc->lab6_run_pool.parent = NULL;  
        proc->lab6_stride = ;  
        proc->lab6_priority = ;  

        proc->filesp = NULL;     //初始化fs中的程序控制結構
    }  
    return proc;  
}  
           

然後就是要實作

load_icode

函數,具體的實作及注釋如下所示:

static int
load_icode(int fd, int argc, char **kargv) {

    /* () create a new mm for current process
     * () create a new PDT, and mm->pgdir= kernel virtual addr of PDT
     * () copy TEXT/DATA/BSS parts in binary to memory space of process
     *    () read raw data content in file and resolve elfhdr
     *    () read raw data content in file and resolve proghdr based on info in elfhdr
     *    () call mm_map to build vma related to TEXT/DATA
     *    () callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file
     *          and copy them into the new allocated pages
     *    () callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
     * () call mm_map to setup user stack, and put parameters into user stack
     * () setup current process's mm, cr3, reset pgidr (using lcr3 MARCO)
     * () setup uargc and uargv in user stacks
     * () setup trapframe for user environment
     * () if up steps failed, you should cleanup the env.
     */
    assert(argc >=  && argc <= EXEC_MAX_ARG_NUM);
    //(1)建立記憶體管理器
    if (current->mm != NULL) {    //要求目前記憶體管理器為空
        panic("load_icode: current->mm must be empty.\n");
    }

    int ret = -E_NO_MEM;    // E_NO_MEM代表因為儲存設備産生的請求錯誤
    struct mm_struct *mm;  //建立記憶體管理器
    if ((mm = mm_create()) == NULL) {
        goto bad_mm;
    }

    //(2)建立頁目錄
    if (setup_pgdir(mm) != ) {
        goto bad_pgdir_cleanup_mm;
    }
    struct Page *page;//建立頁表

    //(3)從檔案加載程式到記憶體
    struct elfhdr __elf, *elf = &__elf;
    if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), )) != ) {//讀取elf檔案頭
        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 = ; phnum < elf->e_phnum; phnum ++) {  //e_phnum代表程式段入口位址數目,即多少各段
        off_t phoff = elf->e_phoff + sizeof(struct proghdr) * phnum;  //循環讀取程式的每個段的頭部   
        if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), phoff)) != ) {
            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 == ) {
            continue ;
        }
        vm_flags = , 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)) != ) {
            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;
            }
            //每次讀取size大小的塊,直至全部讀完
            if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != ) {       //load_icode_read通過sysfile_read函數實作檔案讀取
                goto bad_cleanup_mmap;
            }
            start += size, offset += size;
        }
        //建立BSS段
        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, , 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;
            }
            //每次操作size大小的塊
            memset(page2kva(page) + off, , 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)) != ) {
        goto bad_cleanup_mmap;
    }
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-*PGSIZE , PTE_USER) != NULL);
    assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-*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=, i;
    for (i = ; i < argc; i ++) {
        argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + )+;
    }

    uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long)+)*sizeof(long);
    char** uargv=(char **)(stacktop  - argc * sizeof(char *));

    argv_size = ;
    for (i = ; i < argc; i ++) {         //将所有參數取出來放置uargv
        uargv[i] = strcpy((char *)(stacktop + argv_size ), kargv[i]);
        argv_size +=  strnlen(kargv[i],EXEC_MAX_ARG_LEN + )+;
    }

    stacktop = (uintptr_t)uargv - sizeof(int);   //計算目前使用者棧頂
    *(int *)stacktop = argc;              
    //(7)設定程序的中斷幀   
    struct trapframe *tf = current->tf;     
    memset(tf, , sizeof(struct trapframe));//初始化tf,設定中斷幀
    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 = ;
    //(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;
}
           

詳細的注釋已經在代碼中标注出來了。

load_icode

主要是将檔案加載到記憶體中執行,根據注釋的提示分為了一共七個步驟:

  • 1、建立記憶體管理器
  • 2、建立頁目錄
  • 3、将檔案逐個段加載到記憶體中,這裡要注意設定虛拟位址與實體位址之間的映射
  • 4、建立相應的虛拟記憶體映射表
  • 5、建立并初始化使用者堆棧
  • 6、處理使用者棧中傳入的參數
  • 7、最後很關鍵的一步是設定使用者程序的中斷幀

當然一旦發生錯誤還需要進行錯誤處理。

實驗結果

運作

make qemu

,之後我們先執行一下

ls

指令如下圖:

作業系統ucore lab8實驗報告作業系統lab8實驗報告

然後執行目錄下的

hello

程式,如下圖:

作業系統ucore lab8實驗報告作業系統lab8實驗報告

說明實驗基本成功!

繼續閱讀