全部的實驗報告將會在 Github 同步更新,更多內容請移步至Github:https://github.com/AngelKitty/review_the_national_post-graduate_entrance_examination/blob/master/books_and_notes/professional_courses/operating_system/sources/ucore_os_lab/docs/lab_report/node
lab8
會依賴 lab1~lab7
,咱們須要把作的 lab1~lab7
的代碼填到 lab8
中缺失的位置上面。練習 0 就是一個工具的利用。這裏我使用的是 Linux
下的系統已預裝好的 Meld Diff Viewer
工具。和 lab6
操做流程同樣,咱們只須要將已經完成的 lab1~lab7
與待完成的 lab7
(因爲 lab8
是基於 lab1~lab7
基礎上完成的,因此這裏只須要導入 lab7
)分別導入進來,而後點擊 compare
就好了。git
而後軟件就會自動分析兩份代碼的不一樣,而後就一個個比較比較複製過去就好了,在軟件裏面是能夠支持打開對比複製了,點擊 Copy Right
便可。固然 bin
目錄和 obj
目錄下都是 make
生成的,就不用複製了,其餘須要修改的地方主要有如下七個文件,經過對比複製完成便可:github
proc.c default_pmm.c pmm.c swap_fifo.c vmm.c trap.c sche.c
根據試驗要求,咱們須要對部分代碼進行改進,進一步比對發現,無需改進代碼實現,直接使用便可。數組
要求是首先了解打開文件的處理流程,而後參考本實驗後續的文件讀寫操做的過程分析,編寫在 sfs_inode.c 中 sfs_io_nolock 讀文件中數據的實現代碼。markdown
ucore 的文件系統模型源於 Havard 的 OS161 的文件系統和 Linux 文件系統。但其實這兩者都是源於傳統的 UNIX 文件系統設計。UNIX 提出了四個文件系統抽象概念:文件(file)、目錄項(dentry)、索引節點(inode)和安裝點(mount point)。數據結構
其中,文件和目錄是給應用程序看到的一個抽象。架構
從 ucore 操做系統不一樣的角度來看,ucore 中的文件系統架構包含四類主要的數據結構, 它們分別是:ide
文件系統,會將磁盤上的文件(程序)讀取到內存裏面來,在用戶空間裏面變成進程去進一步執行或其餘操做。經過一系列系統調用完成這個過程。函數
根據實驗指導書,咱們能夠了解到,ucore 的文件系統架構主要由四部分組成:工具
這裏咱們能夠經過下圖能夠比較好的理解這四個部分的關係:
接下來分析下打開一個文件的詳細處理的流程。
例如某一個應用程序須要操做文件(增刪讀寫等),首先須要經過文件系統的通用文件系統訪問接口層給用戶空間提供的訪問接口進入文件系統內部,接着由文件系統抽象層把訪問請求轉發給某一具體文件系統(好比 Simple FS 文件系統),而後再由具體文件系統把應用程序的訪問請求轉化爲對磁盤上的 block 的處理請求,並經過外設接口層交給磁盤驅動例程來完成具體的磁盤操做。
對應到咱們的ucore上,具體的過程以下:
以下圖所示,ucore 文件系統中,是這樣處理讀寫硬盤操做的:
那麼,硬盤中的文件佈局又是怎樣的呢?硬盤中的佈局信息存在SFS中,以下圖所示:
上圖所示的是一個 SFS 的文件系統,其定義在(kern/fs/sfs/sfs.h,83——94行):
struct sfs_fs { struct sfs_super super; /* on-disk superblock */ struct device *dev; /* device mounted on */ struct bitmap *freemap; /* blocks in use are mared 0 */ bool super_dirty; /* true if super/freemap modified */ void *sfs_buffer; /* buffer for non-block aligned io */ semaphore_t fs_sem; /* semaphore for fs */ semaphore_t io_sem; /* semaphore for io */ semaphore_t mutex_sem; /* semaphore for link/unlink and rename */ list_entry_t inode_list; /* inode linked-list */ list_entry_t *hash_list; /* inode hash linked-list */ };
其中,SFS 的前 3 項對應的就是硬盤文件佈局的全局信息。
那麼,接下來分析這些文件佈局的數據結構:
(1)超級塊 super_block(kern/fs/sfs/sfs.h,40——45行)
struct sfs_super { uint32_t magic; /* magic number, should be SFS_MAGIC */ uint32_t blocks; /* # of blocks in fs */ uint32_t unused_blocks; /* # of unused blocks in fs */ char info[SFS_MAX_INFO_LEN + 1]; /* infomation for sfs */ };
超級塊,剛剛說過是一個文件系統的全局角度描述特定文件系統的全局信息。這裏面定義了標識符 magic、總塊數 blocks、空閒塊數 unused_blocks 和一些關於 SFS 的信息,一般是字符串。
(2)根目錄結構 root_dir(kern/fs/sfs/sfs.h,48——57行)
struct sfs_disk_inode { uint32_t size; /* size of the file (in bytes) */ uint16_t type; /* one of SYS_TYPE_* above */ uint16_t nlinks; /* # of hard links to this file */ uint32_t blocks; /* # of blocks */ uint32_t direct[SFS_NDIRECT]; /* direct blocks */ uint32_t indirect; /* indirect blocks */ };
咱們剛剛講過,iNode 是從文件系統的單個文件的角度它描述了文件的各類屬性和數據所在位置,至關於一個索引,而 root_dir 是一個根目錄索引,根目錄表示,咱們一開始訪問這個文件系統能夠看到的目錄信息。主要關注 direct 和 indirect,表明根目錄下的直接索引和間接索引。
(3)目錄項 entry(kern/fs/sfs/sfs.h,60——63行)
struct sfs_disk_entry { uint32_t ino; /* inode number */ char name[SFS_MAX_FNAME_LEN + 1]; /* file name */ };
數組中存放的是文件的名字,ino 是該文件的 iNode 值。
僅有硬盤文件佈局還不夠,SFS 畢竟是一個在硬盤之上的抽象,它還須要傳遞上一層過來的索引值 INODE。這個 INODE 是 SFS 層面的,咱們剛剛討論的 iNode 是硬盤上實際的索引。
sfs_inode(kern/fs/sfs/sfs.h,69——77行)
struct sfs_inode { struct sfs_disk_inode *din; /* on-disk inode */ uint32_t ino; /* inode number */ bool dirty; /* true if inode modified */ int reclaim_count; /* kill inode if it hits zero */ semaphore_t sem; /* semaphore for din */ list_entry_t inode_link; /* entry for linked-list in sfs_fs */ list_entry_t hash_link; /* entry for hash linked-list in sfs_fs */ };
咱們看到,sfs_disk_inode 是 SFS 層面上的 iNode 的一個成員,表明了這兩個結構之間的上下級關係。
接下來,咱們來分析更高層的數據結構 VFS(虛擬文件系統)。
在 VFS 層中,咱們須要對於虛擬的 iNode,和下一層的 SFS 的 iNode 進行對接。
文件系統抽象層是把不一樣文件系統的對外共性接口提取出來,造成一個函數指針數組,這樣,通用文件系統訪問接口層只需訪問文件系統抽象層,而不需關心具體文件系統的實現細節和接口。
(1)VFS的抽象定義(kern/fs/vfs/vfs.h,35——46行)
struct fs { union { struct sfs_fs __sfs_info; } fs_info; // filesystem-specific data enum { fs_type_sfs_info, } fs_type; // filesystem type int (*fs_sync)(struct fs *fs); // Flush all dirty buffers to disk struct inode *(*fs_get_root)(struct fs *fs); // Return root inode of filesystem. int (*fs_unmount)(struct fs *fs); // Attempt unmount of filesystem. void (*fs_cleanup)(struct fs *fs); // Cleanup of filesystem.??? };
主要是一些函數指針用於處理 VFS 的操做。
(2)文件結構(kern/fs/file.c,14——24行)
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; //打開此文件的次數 };
在 file 基礎之上還有一個管理全部 file 的數據結構 file_struct(kern/fs/fs.h,25——30行)
struct files_struct { struct inode *pwd; //當前工做目錄 struct file *fd_array; //已經打開的文件對應的數組 int files_count; //打開的文件個數 };
(3)VFS 的索引 iNode(kern/fs/vfs/inode.h,29——42行)
/* 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 = 0x1234, 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 的函數指針 };
咱們看到在 VFS 層面的 iNode 值,包含了 SFS 和硬件設備 device 的狀況。
(4)inode 的操做函數指針列表(kern/fs/vfs/inode.h,169——186行)
struct inode_ops { unsigned long vop_magic; int (*vop_open)(struct inode *node, uint32_t open_flags); int (*vop_close)(struct inode *node); int (*vop_read)(struct inode *node, struct iobuf *iob); int (*vop_write)(struct inode *node, struct iobuf *iob); int (*vop_fstat)(struct inode *node, struct stat *stat); int (*vop_fsync)(struct inode *node); int (*vop_namefile)(struct inode *node, struct iobuf *iob); int (*vop_getdirentry)(struct inode *node, struct iobuf *iob); int (*vop_reclaim)(struct inode *node); int (*vop_gettype)(struct inode *node, uint32_t *type_store); int (*vop_tryseek)(struct inode *node, off_t pos); int (*vop_truncate)(struct inode *node, off_t len); int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **node_store); int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store); int (*vop_ioctl)(struct inode *node, int op, void *data); };
inode_ops 是對常規文件、目錄、設備文件全部操做的一個抽象函數表示。對於某一具體的文件系統中的文件或目錄,只需實現相關的函數,就能夠被用戶進程訪問具體的文件了,且用戶進程無需瞭解具體文件系統的實現細節。
有了上述分析後,咱們能夠看看若是一個用戶進程打開文件會作哪些事情?
首先假定用戶進程須要打開的文件已經存在在硬盤上。以 user/sfs_filetest1.c 爲例,首先用戶進程會調用在 main 函數中的以下語句:
int fd1 = safe_open("/test/testfile", O_RDWR | O_TRUNC);
若是 ucore 可以正常查找到這個文件,就會返回一個表明文件的文件描述符 fd1,這樣在接下來的讀寫文件過程當中,就直接用這樣 fd1 來表明就能夠了。
接下來實現須要編碼的函數:
通用文件訪問接口層的處理流程:
首先進入通用文件訪問接口層的處理流程,即進一步調用以下用戶態函數:open->sys_open->syscall,從而引發系統調用進入到內核態。到了內核態後,經過中斷處理例程,會調用到 sys_open 內核函數,並進一步調用 sysfile_open 內核函數。到了這裏,須要把位於用戶空間的字符串 」/test/testfile」 拷貝到內核空間中的字符串 path 中,並進入到文件系統抽象層的處理流程完成進一步的打開文件操做中。
文件系統抽象層(VFS)的處理流程:
一、分配一個空閒的 file 數據結構變量 file 在文件系統抽象層的處理中,首先調用的是 file_open 函數,它要給這個即將打開的文件分配一個 file 數據結構的變量,這個變量實際上是當前進程的打開文件數組 current->fs_struct->filemap[] 中的一個空閒元素(即還沒用於一個打開的文件),而這個元素的索引值就是最終要返回到用戶進程並賦值給變量 fd1。到了這一步還僅僅是給當前用戶進程分配了一個 file 數據結構的變量,尚未找到對應的文件索引節點。
爲此須要進一步調用 vfs_open 函數來找到 path 指出的文件所對應的基於 inode 數據結構的 VFS 索引節點 node。 vfs_open 函數須要完成兩件事情:經過 vfs_lookup 找到 path 對應文件的 inode;調用 vop_open 函數打開文件。
二、找到文件設備的根目錄/的索引節點須要注意,這裏的 vfs_lookup 函數是一個針對目錄的操做函數,它會調用 vop_lookup 函數來找到 SFS 文件系統中的 /test 目錄下的 testfile 文件。爲此,vfs_lookup 函數首先調用 get_device 函數,並進一步調用 vfs_get_bootfs 函數(其實調用了)來找到根目錄/對應的 inode。這個 inode 就是位於 vfs.c 中的 inode 變量 bootfs_node。這個變量在 init_main 函數(位於kern/process/proc.c)執行時得到了賦值。
找到根目錄/下的test子目錄對應的索引節點,在找到根目錄對應的inode後,經過調用vop_lookup函數來查找/和test這兩層目錄下的文件testfile所對應的索引節點,若是找到就返回此索引節點。
三、把 file 和 node 創建聯繫。完成第3步後,將返回到 file_open 函數中,經過執行語句 file->node=node,就把當前進程的current->fs_struct->filemap[fd](即file所指變量)的成員變量 node 指針指向了表明 /test/testfile 文件的索引節點 node。這時返回 fd。通過重重回退,經過系統調用返回,用戶態的 syscall->sys_open->open->safe_open 等用戶函數的層層函數返回,最終把把fd賦值給fd1。自此完成了打開文件操做。但這裏咱們尚未分析第2和第3步是如何進一步調用 SFS 文件系統提供的函數找位於 SFS 文件系統上的 /test/testfile 所對應的 sfs 磁盤 inode 的過程。下面須要進一步對此進行分析。
sfs_lookup(kern/fs/sfs/sfs_inode.c,975——993行)
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 != 0) { return ret; } *node_store = subnode; //當沒法分解path後,就意味着找到了須要對應的inode節點,就可順利返回了。 return 0; }
看到函數傳入的三個參數,其中 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。
sfs_lookup_once(kern/fs/sfs/sfs_inode.c,498——512行)
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; }
最後是須要實現的函數,這裏只註釋了讀文件的部分:
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) { //找到內存文件索引對應的 block 的編號 ino 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; } ......
每次經過 sfs_bmap_load_nolock 函數獲取文件索引編號,而後調用 sfs_buf_op 完成實際的文件讀寫操做。
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
blkno 就是文件開始塊的位置,nblks 是文件的大小。
請在實驗報告中給出設計實現」UNIX的PIPE機制「的概要設方案,鼓勵給出詳細設計方案。
爲了實現 UNIX 的 PIPE 機制,能夠考慮在磁盤上保留一部分空間或者是一個特定的文件來做爲 pipe 機制的緩衝區,接下來將說明如何完成對 pipe 機制的支持:
事實上,因爲在真實的文件系統和用戶之間還由一層虛擬文件系統,所以咱們也能夠不把數據緩衝在磁盤上,而是直接保存在內存中,而後完成一個根據虛擬文件系統的規範完成一個虛擬的 pipe 文件,而後進行輸入輸出的時候只要對這個文件進行操做便可;
改寫proc.c中的load_icode函數和其餘相關函數,實現基於文件系統的執行程序機制。執行:make qemu。若是能看看到sh用戶程序的執行界面,則基本成功了。若是在sh用戶界面上能夠執行」ls」,」hello」等其餘放置在sfs文件系統中的其餘執行程序,則能夠認爲本實驗基本成功。
能夠在 Lab 7 的基礎上進行修改,讀 elf 文件變成從磁盤上讀,而不是直接在內存中讀。
在 proc.c 中,根據註釋咱們須要先初始化 fs 中的進程控制結構,即在 alloc_proc 函數中咱們須要作一下修改,加上一句 proc->filesp = NULL; 從而完成初始化。
爲何要這樣作的呢,由於咱們以前講過,一個文件須要在 VFS 中變爲一個進程才能被執行。
修改以後 alloc_proc 函數以下:(增長一行,kern/process/proc.c,136行)
proc->filesp = NULL; //初始化fs中的進程控制結構
因此完整的 alloc_proc 函數的實現以下:
//LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ... 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 = -1; //進程ID爲-1 proc->runs = 0; //進程運行時間爲0 proc->kstack = 0; //內核棧爲0 proc->need_resched = 0; //進程不須要調度 proc->parent = NULL; //父進程爲空 proc->mm = NULL; //內存管理爲空 memset(&(proc->context), 0, sizeof(struct context)); proc->tf = NULL; //中斷幀爲空 proc->cr3 = boot_cr3; //cr3寄存器 proc->flags = 0; //標記 memset(proc->name, 0, PROC_NAME_LEN); proc->wait_state = 0; //等待狀態 proc->cptr = proc->optr = proc->yptr = NULL; //相關指針初始化 proc->rq = NULL; //運行隊列 list_init(&(proc->run_link)); //運行隊列鏈表 proc->time_slice = 0; //進程運行的時間片 proc->lab6_run_pool.left = proc->lab6_run_pool.right = proc->lab6_run_pool.parent = NULL; //進程池 proc->lab6_stride = 0; proc->lab6_priority = 0; //優先級 proc->filesp = NULL; //初始化fs中的進程控制結構 } return proc; }
此外 參數在棧中的佈局以下所示:
| High Address | ---------------- | Argument | | n | ---------------- | ... | ---------------- | Argument | | 1 | ---------------- | padding | ---------------- | null ptr | ---------------- | Ptr Arg n | ---------------- | ... | ---------------- | Ptr Arg 1 | ---------------- | Arg Count | <-- user esp ---------------- | Low Address |
而後就是要實現 load_icode
函數,具體的實現及註釋以下所示:
static int load_icode(int fd, int argc, char **kargv) { /* (1) create a new mm for current process * (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT * (3) copy TEXT/DATA/BSS parts in binary to memory space of process * (3.1) read raw data content in file and resolve elfhdr * (3.2) read raw data content in file and resolve proghdr based on info in elfhdr * (3.3) call mm_map to build vma related to TEXT/DATA * (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file * and copy them into the new allocated pages * (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages * (4) call mm_map to setup user stack, and put parameters into user stack * (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO) * (6) setup uargc and uargv in user stacks * (7) setup trapframe for user environment * (8) if up steps failed, you should cleanup the env. */ assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM); //(1)創建內存管理器 // 判斷當前進程的 mm 是否已經被釋放掉了 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) {// 爲進程建立一個新的 mm goto bad_mm; } //(2)創建頁目錄 if (setup_pgdir(mm) != 0) {// 進行頁表項的設置 goto bad_pgdir_cleanup_mm; } struct Page *page;//創建頁表 //(3)從文件加載程序到內存 struct elfhdr __elf, *elf = &__elf; // 從磁盤上讀取出 ELF 可執行文件的 elf-header if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {//讀取 elf 文件頭 goto bad_elf_cleanup_pgdir; } if (elf->e_magic != ELF_MAGIC) {// 判斷該 ELF 文件是否合法 ret = -E_INVAL_ELF; goto bad_elf_cleanup_pgdir; } struct proghdr __ph, *ph = &__ph; uint32_t vm_flags, perm, phnum; // 根據 elf-header 中的信息,找到每個 program header for (phnum = 0; 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)) != 0) {// 讀取program header 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;// 根據 ELF 文件中的信息,對各個段的權限進行設置 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) {// 爲 TEXT/DATA 段逐頁分配物理內存空間 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)) != 0) { //load_icode_read 經過 sysfile_read 函數實現文件讀取,將磁盤上的 TEXT/DATA 段讀入到分配好的內存空間中去 goto bad_cleanup_mmap; } start += size, offset += size; } //創建BSS段 end = ph->p_va + ph->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); 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 段分配新的物理內存頁 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, 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);// 切換到用戶的內存空間,這樣的話後文中在棧上設置參數部分的操做將大大簡化,由於具體由於空間不足而致使的分配物理頁的操做已經交由page fault處理了,是徹底透明的 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 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->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 = elf->e_entry;// 將返回地址設置爲用戶程序的入口 tf->tf_eflags = FL_IF;// 容許中斷,根據 IA32 的規範,eflags 的第 1 位須要恆爲 1 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; }
load_icode 主要是將文件加載到內存中執行,從上面的註釋可知分爲了一共七個步驟:
固然一旦發生錯誤還須要進行錯誤處理。
請在實驗報告中給出設計實現基於」UNIX的硬連接和軟連接機制「的概要設方案,鼓勵給出詳細設計方案;
觀察到保存在磁盤上的 inode 信息均存在一個 nlinks 變量用於表示當前文件的被連接的計數,於是支持實現硬連接和軟連接機制;
最終的實驗結果以下圖所示:
直接運行答案文件夾可能會出錯,須要在 Makefile 最後加上:
tags: @echo TAGS ALL $(V)rm -f cscope.files cscope.in.out cscope.out cscope.po.out tags $(V)find . -type f -name "*.[chS]" >cscope.files $(V)cscope -bq $(V)ctags -L cscope.files
或者將 lab8 中的 Makefile,複製到 lab8_result 中(本項目已作過更正),覆蓋掉原有的 Makefile,待 make qemu 信息輸出完畢後,點進 qemu 界面,輸入 ls,回車,能夠看到文件信息: