tags: mit-6.828 osnode
本lab將實現JOS的文件系統,只要包括以下四部分:linux
咱們將要實現的文件系統會比真正的文件系統要簡單,可是能知足基本的建立,讀,寫,刪除文件的功能。可是不支持連接,符號連接,時間戳等特性。git
JOS的文件系統不使用inodes,全部文件的元數據都被存儲在directory entry中。
文件和目錄邏輯上都是由一系列數據blocks組成,這些blocks分散在磁盤中,文件系統屏蔽blocks分佈的細節,提供一個能夠順序讀寫文件的接口。JOS文件系統容許用戶讀目錄元數據,這就意味着用戶能夠掃描目錄來像實現ls這種程序,UNIX沒有采用這種方式的緣由是,這種方式使得應用程序過分依賴目錄元數據格式。github
大部分磁盤都是以Sector爲粒度進行讀寫,JOS中Sectors爲512字節。文件系統以block爲單位分配和使用磁盤。注意區別,sector size是磁盤的屬性,block size是操做系統使用磁盤的粒度。JOS的文件系統的block size被定爲4096字節。shell
文件系統使用一些特殊的block保存文件系統屬性元數據,好比block size, disk size, 根目錄位置等。這些特殊的block叫作superblocks。
咱們的文件系統使用一個superblock,位於磁盤的block 1。block 0被用來保存boot loader和分區表。不少文件系統維護多個superblock,這樣當一個損壞時,依然能夠正常運行。
磁盤結構以下:
Super結構以下:vim
struct Super { uint32_t s_magic; // Magic number: FS_MAGIC uint32_t s_nblocks; // Total number of blocks on disk struct File s_root; // Root directory node };
咱們的文件系統使用struct File結構描述文件,該結構包含文件名,大小,類型,保存文件內容的block號。struct File結構的f_direct數組保存前NDIRECT(10)個block號,這樣對於10*4096=40KB的文件不須要額外的空間來記錄內容block號。對於更大的文件咱們分配一個額外的block來保存4096/4=1024 block號。因此咱們的文件系統容許文件最多擁有1034個block。File結構以下:
File結構定義在inc/fs.h中:windows
struct File { char f_name[MAXNAMELEN]; // filename off_t f_size; // file size in bytes uint32_t f_type; // file type // Block pointers. // A block is allocated iff its value is != 0. uint32_t f_direct[NDIRECT]; // direct blocks uint32_t f_indirect; // indirect block // Pad out to 256 bytes; must do arithmetic in case we're compiling // fsformat on a 64-bit machine. uint8_t f_pad[256 - MAXNAMELEN - 8 - 4*NDIRECT - 4]; } __attribute__((packed)); // required only on some 64-bit machines
File結構既能表明文件也能表明目錄,由type字段區分,文件系統以相同的方式管理文件和目錄,只是目錄文件的內容是一系列File結構,這些File結構描述了在該目錄下的文件或者子目錄。
超級塊中包含一個File結構,表明文件系統的根目錄。數組
到目前爲止內核尚未訪問磁盤的能力。JOS不像其餘操做系統同樣在內核添加磁盤驅動,而後提供系統調用。咱們實現一個文件系統進程來做爲磁盤驅動。
x86處理器使用EFLAGS寄存器的IOPL爲來控制保護模式下代碼是否能執行設備IO指令,好比in和out。咱們但願文件系統進程能訪問IO空間,其餘進程不能。緩存
文件系統進程的type爲ENV_TYPE_FS,須要修改env_create(),若是type是ENV_TYPE_FS,須要給該進程IO權限。
在env_create()中添加以下代碼:數據結構
if (type == ENV_TYPE_FS) { e->env_tf.tf_eflags |= FL_IOPL_MASK; }
咱們的文件系統最大支持3GB,文件系統進程保留從0x10000000 (DISKMAP)到0xD0000000 (DISKMAP+DISKMAX)固定3GB的內存空間做爲磁盤的緩存。好比block 0被映射到虛擬地址0x10000000,block 1被映射到虛擬地址0x10001000以此類推。
若是將整個磁盤所有讀到內存將很是耗時,因此咱們將實現按需加載,只有當訪問某個bolck對應的內存地址時出現頁錯誤,纔將該block從磁盤加載到對應的內存區域,而後從新執行內存訪問指令。
實現bc_pgfault()和flush_block()。
bc_pgfault()是FS進程缺頁處理函數,負責將數據從磁盤讀取到對應的內存。能夠回顧下lab4。
bc_pgfault(struct UTrapframe *utf) { void *addr = (void *) utf->utf_fault_va; uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE; int r; // Check that the fault was within the block cache region if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE)) panic("page fault in FS: eip %08x, va %08x, err %04x", utf->utf_eip, addr, utf->utf_err); // Sanity check the block number. if (super && blockno >= super->s_nblocks) panic("reading non-existent block %08x\n", blockno); // Allocate a page in the disk map region, read the contents // of the block from the disk into that page. // Hint: first round addr to page boundary. fs/ide.c has code to read // the disk. // // LAB 5: you code here: addr = ROUNDDOWN(addr, PGSIZE); sys_page_alloc(0, addr, PTE_W|PTE_U|PTE_P); if ((r = ide_read(blockno * BLKSECTS, addr, BLKSECTS)) < 0) panic("ide_read: %e", r); // Clear the dirty bit for the disk block page since we just read the // block from disk if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0) panic("in bc_pgfault, sys_page_map: %e", r); // Check that the block we read was allocated. (exercise for // the reader: why do we do this *after* reading the block // in?) if (bitmap && block_is_free(blockno)) panic("reading free block %08x\n", blockno); }
flush_block()將一個block寫入磁盤。flush_block()不須要作任何操做,若是block沒有在內存或者block沒有被寫過。能夠經過PTE的PTE_D位判斷該block有沒有被寫過。
void flush_block(void *addr) { uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE; int r; if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE)) panic("flush_block of bad va %08x", addr); // LAB 5: Your code here. addr = ROUNDDOWN(addr, PGSIZE); if (!va_is_mapped(addr) || !va_is_dirty(addr)) { //若是addr尚未映射過或者該頁載入到內存後尚未被寫過,不用作任何事 return; } if ((r = ide_write(blockno * BLKSECTS, addr, BLKSECTS)) < 0) { //寫回到磁盤 panic("in flush_block, ide_write(): %e", r); } if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0) //清空PTE_D位 panic("in bc_pgfault, sys_page_map: %e", r); }
fs/fs.c中的fs_init()將會初始化super和bitmap全局指針變量。至此對於文件系統進程只要訪問虛擬內存[DISKMAP, DISKMAP+DISKMAX]範圍中的地址addr,就會訪問到磁盤((uint32_t)addr - DISKMAP) / BLKSIZE block中的數據。若是block數據還沒複製到內存物理頁,bc_pgfault()缺頁處理函數會將數據從磁盤拷貝到某個物理頁,而且將addr映射到該物理頁。這樣FS進程只須要訪問虛擬地址空間[DISKMAP, DISKMAP+DISKMAX]就能訪問磁盤了。
fs_init()中已經初始化了bitmap,咱們能經過bitmap訪問磁盤的block 1,也就是位數組,每一位表明一個block,1表示該block未被使用,0表示已被使用。咱們實現一系列管理函數來管理這個位數組。
實現fs/fs.c中的alloc_block(),該函數搜索bitmap位數組,返回一個未使用的block,並將其標記爲已使用。
alloc_block(void) { // The bitmap consists of one or more blocks. A single bitmap block // contains the in-use bits for BLKBITSIZE blocks. There are // super->s_nblocks blocks in the disk altogether. // LAB 5: Your code here. uint32_t bmpblock_start = 2; for (uint32_t blockno = 0; blockno < super->s_nblocks; blockno++) { if (block_is_free(blockno)) { //搜索free的block bitmap[blockno / 32] &= ~(1 << (blockno % 32)); //標記爲已使用 flush_block(diskaddr(bmpblock_start + (blockno / 32) / NINDIRECT)); //將剛剛修改的bitmap block寫到磁盤中 return blockno; } } return -E_NO_DISK; }
fs/fs.c文件提供了一系列函數用於管理File結構,掃描和管理目錄文件,解析絕對路徑。
基本的文件系統操做:
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
:查找f指向文件結構的第filebno個block的存儲地址,保存到ppdiskbno中。若是f->f_indirect尚未分配,且alloc爲真,那麼將分配要給新的block做爲該文件的f->f_indirect。類比頁表管理的pgdir_walk()。file_get_block(struct File *f, uint32_t filebno, char **blk)
:該函數查找文件第filebno個block對應的虛擬地址addr,將其保存到blk地址處。walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)
:解析路徑path,填充pdir和pf地址處的File結構。好比/aa/bb/cc.c那麼pdir指向表明bb目錄的File結構,pf指向表明cc.c文件的File結構。又好比/aa/bb/cc.c,可是cc.c此時還不存在,那麼pdir依舊指向表明bb目錄的File結構,可是pf地址處應該爲0,lastelem指向的字符串應該是cc.c。dir_lookup(struct File *dir, const char *name, struct File **file)
:該函數查找dir指向的文件內容,尋找File.name爲name的File結構,並保存到file地址處。dir_alloc_file(struct File *dir, struct File **file)
:在dir目錄文件的內容中尋找一個未被使用的File結構,將其地址保存到file的地址處。文件操做:
file_create(const char *path, struct File **pf)
:建立path,若是建立成功pf指向新建立的File指針。file_open(const char *path, struct File **pf)
:尋找path對應的File結構地址,保存到pf地址處。file_read(struct File *f, void *buf, size_t count, off_t offset)
:從文件f中的offset字節處讀取count字節到buf處。file_write(struct File *f, const void *buf, size_t count, off_t offset)
:將buf處的count字節寫到文件f的offset開始的位置。實現file_block_walk()和file_get_block()。
file_block_walk():
static int file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc) { // LAB 5: Your code here. int bn; uint32_t *indirects; if (filebno >= NDIRECT + NINDIRECT) return -E_INVAL; if (filebno < NDIRECT) { *ppdiskbno = &(f->f_direct[filebno]); } else { if (f->f_indirect) { indirects = diskaddr(f->f_indirect); *ppdiskbno = &(indirects[filebno - NDIRECT]); } else { if (!alloc) return -E_NOT_FOUND; if ((bn = alloc_block()) < 0) return bn; f->f_indirect = bn; flush_block(diskaddr(bn)); indirects = diskaddr(bn); *ppdiskbno = &(indirects[filebno - NDIRECT]); } } return 0; }
file_get_block():
int file_get_block(struct File *f, uint32_t filebno, char **blk) { // LAB 5: Your code here. int r; uint32_t *pdiskbno; if ((r = file_block_walk(f, filebno, &pdiskbno, true)) < 0) { return r; } int bn; if (*pdiskbno == 0) { //此時*pdiskbno保存着文件f第filebno塊block的索引 if ((bn = alloc_block()) < 0) { return bn; } *pdiskbno = bn; flush_block(diskaddr(bn)); } *blk = diskaddr(*pdiskbno); return 0; }
包括後面的Exercise 10都遇到相同的問題。
寫完Exercise4後執行make grade,沒法經過測試,提示"file_get_block returned wrong data"。在實驗目錄下搜索該字符串,發現是在fs/test.c文件中,
if ((r = file_open("/newmotd", &f)) < 0) panic("file_open /newmotd: %e", r); if ((r = file_get_block(f, 0, &blk)) < 0) panic("file_get_block: %e", r); if (strcmp(blk, msg) != 0) panic("file_get_block returned wrong data");
也就是說只有當blk和msg指向的字符串不同時纔會報這個錯,msg定義在fs/test.c中static char *msg = "This is the NEW message of the day!\n\n"
。blk指向/newmotd文件的開頭。/newmotd文件在fs/newmotd中,打開後發現內容也是"This is the NEW message of the day!"。照理來講應該沒有問題啊。可是經過xxd fs/newmotd指令查看文件二進制發現以下:
1. 00000000: 5468 6973 2069 7320 7468 6520 4e45 5720 This is the NEW 2. 00000010: 6d65 7373 6167 6520 6f66 2074 6865 2064 message of the d 3. 00000020: 6179 210d 0a0d 0a ay!....
最後的兩個換行符是0d0a 0d0a,也就是\r\n\r\n。可是msg中末尾倒是\n\n。\r\n應該是windows上的換行符,不知道爲何fs/newmotd中的換行符竟然是windows上的換行符。找到問題了所在,咱們用vim打開fs/newmotd,而後使用命令set ff=unix,保存退出。如今再用xxd fs/newmotd指令查看文件二進制發現,換行符已經變成了\n(0x0a)。這樣就能夠經過該實驗了。在Exercise 10中一樣須要將fs文件夾下的lorem,script,testshell.sh文件中的換行符轉成UNIX下的。
到目前爲止,文件系統進程已經能提供各類操做文件的功能了,可是其餘用戶進程不能直接調用這些函數。咱們經過進程間函數調用(RPC)對其它進程提供文件系統服務。RPC機制原理以下:
Regular env FS env +---------------+ +---------------+ | read | | file_read | | (lib/fd.c) | | (fs/fs.c) | ...|.......|.......|...|.......^.......|............... | v | | | | RPC mechanism | devfile_read | | serve_read | | (lib/file.c) | | (fs/serv.c) | | | | | ^ | | v | | | | | fsipc | | serve | | (lib/file.c) | | (fs/serv.c) | | | | | ^ | | v | | | | | ipc_send | | ipc_recv | | | | | ^ | +-------|-------+ +-------|-------+ | | +-------------------+
本質上RPC仍是藉助IPC機制實現的,普通進程經過IPC向FS進程間發送具體操做和操做數據,而後FS進程執行文件操做,最後又將結果經過IPC返回給普通進程。從上圖中能夠看到客戶端的代碼在lib/fd.c和lib/file.c兩個文件中。服務端的代碼在fs/fs.c和fs/serv.c兩個文件中。
相關數據結構之間的關係可用下圖來表示:
文件系統服務端代碼在fs/serv.c中,serve()中有一個無限循環,接收IPC請求,將對應的請求分配到對應的處理函數,而後將結果經過IPC發送回去。
對於客戶端來講:發送一個32位的值做爲請求類型,發送一個Fsipc結構做爲請求參數,該數據結構經過IPC的頁共享發給FS進程,在FS進程能夠經過訪問fsreq(0x0ffff000)來訪問客戶進程發來的Fsipc結構。
對於服務端來講:FS進程返回一個32位的值做爲返回碼,對於FSREQ_READ和FSREQ_STAT這兩種請求類型,還額外經過IPC返回一些數據。
實現fs/serv.c中的serve_read()。這是服務端也就是FS進程中的函數。直接調用更底層的fs/fs.c中的函數來實現。
int serve_read(envid_t envid, union Fsipc *ipc) { struct Fsreq_read *req = &ipc->read; struct Fsret_read *ret = &ipc->readRet; if (debug) cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n); // Lab 5: Your code here: struct OpenFile *o; int r; r = openfile_lookup(envid, req->req_fileid, &o); if (r < 0) //經過fileid找到Openfile結構 return r; if ((r = file_read(o->o_file, ret->ret_buf, req->req_n, o->o_fd->fd_offset)) < 0) //調用fs.c中函數進行真正的讀操做 return r; o->o_fd->fd_offset += r; return r; }
實現fs/serv.c中的serve_write()和lib/file.c中的devfile_write()。
serve_write():
int serve_write(envid_t envid, struct Fsreq_write *req) { if (debug) cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n); // LAB 5: Your code here. struct OpenFile *o; int r; if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) { return r; } int total = 0; while (1) { r = file_write(o->o_file, req->req_buf, req->req_n, o->o_fd->fd_offset); if (r < 0) return r; total += r; o->o_fd->fd_offset += r; if (req->req_n <= total) break; } return total; }
devfile_write():客戶端進程函數,包裝一下參數,直接調用fsipc()將參數發送給FS進程處理。
static ssize_t devfile_write(struct Fd *fd, const void *buf, size_t n) { // Make an FSREQ_WRITE request to the file system server. Be // careful: fsipcbuf.write.req_buf is only so large, but // remember that write is always allowed to write *fewer* // bytes than requested. // LAB 5: Your code here int r; fsipcbuf.write.req_fileid = fd->fd_file.id; fsipcbuf.write.req_n = n; memmove(fsipcbuf.write.req_buf, buf, n); return fsipc(FSREQ_WRITE, NULL); }
以打開一個文件爲例,看下總體過程,read(), write()相似。open()在linux中也要實現定義在頭文件<fcntl.h>中,原型以下:
int open(const char *pathname, int flags);
在JOS中open()實如今lib/file.c中,
int open(const char *path, int mode) { // Find an unused file descriptor page using fd_alloc. // Then send a file-open request to the file server. // Include 'path' and 'omode' in request, // and map the returned file descriptor page // at the appropriate fd address. // FSREQ_OPEN returns 0 on success, < 0 on failure. // // (fd_alloc does not allocate a page, it just returns an // unused fd address. Do you need to allocate a page?) // // Return the file descriptor index. // If any step after fd_alloc fails, use fd_close to free the // file descriptor. int r; struct Fd *fd; if (strlen(path) >= MAXPATHLEN) //文件名不能超過指定長度 return -E_BAD_PATH; if ((r = fd_alloc(&fd)) < 0) //搜索當前進程未被分配的文件描述符 return r; strcpy(fsipcbuf.open.req_path, path); fsipcbuf.open.req_omode = mode; if ((r = fsipc(FSREQ_OPEN, fd)) < 0) { //經過fsipc()向FS進程發起RPC調用 fd_close(fd, 0); return r; } return fd2num(fd); } static int fsipc(unsigned type, void *dstva) //type, fsipcbuf是發送給fs進程的數據。dstava和fsipc()的返回值是從fs進程接收的值 { static envid_t fsenv; if (fsenv == 0) fsenv = ipc_find_env(ENV_TYPE_FS); static_assert(sizeof(fsipcbuf) == PGSIZE); ipc_send(fsenv, type, &fsipcbuf, PTE_P | PTE_W | PTE_U); //向FS進程發送數據 return ipc_recv(NULL, dstva, NULL); //接收FS進程發送回來的數據 }
其中fd_alloc()定義在lib/fd.c中,
int fd_alloc(struct Fd **fd_store) { int i; struct Fd *fd; for (i = 0; i < MAXFD; i++) { //從當前最小的未分配描述符開始 fd = INDEX2FD(i); if ((uvpd[PDX(fd)] & PTE_P) == 0 || (uvpt[PGNUM(fd)] & PTE_P) == 0) { *fd_store = fd; return 0; } } *fd_store = 0; return -E_MAX_OPEN; }
每一個進程從虛擬地址0xD0000000開始,每一頁對應一個Fd結構,也就是說文件描述符0對應的Fd結構地址爲0xD0000000,文件描述符1對應的Fd描述符結構地址爲0xD0000000+PGSIZE(被定義爲4096),以此類推,。能夠經過檢查某個Fd結構的虛擬地址是否已經分配,來判斷這個文件描述符是否被分配。若是一個文件描述符被分配了,那麼該文件描述符對應的Fd結構開始的一頁將被映射到和FS進程相同的物理地址處。
FS進程收到FSREQ_OPEN請求後,將調用serve_open(),該函數定義在fs/serv.c中。
int serve_open(envid_t envid, struct Fsreq_open *req, void **pg_store, int *perm_store) { char path[MAXPATHLEN]; struct File *f; int fileid; int r; struct OpenFile *o; if (debug) cprintf("serve_open %08x %s 0x%x\n", envid, req->req_path, req->req_omode); // Copy in the path, making sure it's null-terminated memmove(path, req->req_path, MAXPATHLEN); path[MAXPATHLEN-1] = 0; // Find an open file ID if ((r = openfile_alloc(&o)) < 0) { //從opentab數組中分配一個OpenFile結構 if (debug) cprintf("openfile_alloc failed: %e", r); return r; } fileid = r; // Open the file if (req->req_omode & O_CREAT) { if ((r = file_create(path, &f)) < 0) { //根據path分配一個File結構 if (!(req->req_omode & O_EXCL) && r == -E_FILE_EXISTS) goto try_open; if (debug) cprintf("file_create failed: %e", r); return r; } } else { try_open: if ((r = file_open(path, &f)) < 0) { if (debug) cprintf("file_open failed: %e", r); return r; } } // Truncate if (req->req_omode & O_TRUNC) { if ((r = file_set_size(f, 0)) < 0) { if (debug) cprintf("file_set_size failed: %e", r); return r; } } if ((r = file_open(path, &f)) < 0) { if (debug) cprintf("file_open failed: %e", r); return r; } // Save the file pointer o->o_file = f; //保存File結構到OpenFile結構 // Fill out the Fd structure o->o_fd->fd_file.id = o->o_fileid; o->o_fd->fd_omode = req->req_omode & O_ACCMODE; o->o_fd->fd_dev_id = devfile.dev_id; o->o_mode = req->req_omode; if (debug) cprintf("sending success, page %08x\n", (uintptr_t) o->o_fd); // Share the FD page with the caller by setting *pg_store, // store its permission in *perm_store *pg_store = o->o_fd; *perm_store = PTE_P|PTE_U|PTE_W|PTE_SHARE; return 0; }
該函數首先從opentab這個OpenFile數組中尋找一個未被使用的OpenFile結構,上圖中假設找到數據第一個OpenFile結構就是未使用的。若是open()中參數mode設置了O_CREAT選項,那麼會調用fs/fs.c中的file_create()根據路徑建立一個新的File結構,並保存到OpenFile結構的o_file字段中。
結束後,serve()會將OpenFile結構對應的Fd起始地址發送個客戶端進程,因此客戶進程從open()返回後,新分配的Fd和FS進程Fd共享相同的物理頁。
lib/spawn.c中的spawn()建立一個新的進程,從文件系統加載用戶程序,而後啓動該進程來運行這個程序。spawn()就像UNIX中的fork()後面立刻跟着exec()。
spawn(const char *prog, const char **argv)
作以下一系列動做:
實現sys_env_set_trapframe()系統調用。
static int sys_env_set_trapframe(envid_t envid, struct Trapframe *tf) { // LAB 5: Your code here. // Remember to check whether the user has supplied us with a good // address! int r; struct Env *e; if ((r = envid2env(envid, &e, 1)) < 0) { return r; } tf->tf_eflags = FL_IF; tf->tf_eflags &= ~FL_IOPL_MASK; //普通進程不能有IO權限 tf->tf_cs = GD_UT | 3; e->env_tf = *tf; return 0; }
UNIX文件描述符是一個大的概念,包含pipe,控制檯I/O。在JOS中每種設備對應一個struct Dev結構,該結構包含函數指針,指向真正實現讀寫操做的函數。
lib/fd.c文件實現了UNIX文件描述符接口,但大部分函數都是簡單對struct Dev結構指向的函數的包裝。
咱們但願共享文件描述符,JOS中定義PTE新的標誌位PTE_SHARE,若是有個頁表條目的PTE_SHARE標誌位爲1,那麼這個PTE在fork()和spawn()中將被直接拷貝到子進程頁表,從而讓父進程和子進程共享相同的頁映射關係,從而達到父子進程共享文件描述符的目的。
修改lib/fork.c中的duppage(),使之正確處理有PTE_SHARE標誌的頁表條目。同時實現lib/spawn.c中的copy_shared_pages()。
static int duppage(envid_t envid, unsigned pn) { int r; // LAB 4: Your code here. void *addr = (void*) (pn * PGSIZE); if (uvpt[pn] & PTE_SHARE) { sys_page_map(0, addr, envid, addr, PTE_SYSCALL); //對於標識爲PTE_SHARE的頁,拷貝映射關係,而且兩個進程都有讀寫權限 } else if ((uvpt[pn] & PTE_W) || (uvpt[pn] & PTE_COW)) { //對於UTOP如下的可寫的或者寫時拷貝的頁,拷貝映射關係的同時,須要同時標記當前進程和子進程的頁表項爲PTE_COW if ((r = sys_page_map(0, addr, envid, addr, PTE_COW|PTE_U|PTE_P)) < 0) panic("sys_page_map:%e", r); if ((r = sys_page_map(0, addr, 0, addr, PTE_COW|PTE_U|PTE_P)) < 0) panic("sys_page_map:%e", r); } else { sys_page_map(0, addr, envid, addr, PTE_U|PTE_P); //對於只讀的頁,只須要拷貝映射關係便可 } return 0; }
copy_shared_pages()
static int copy_shared_pages(envid_t child) { // LAB 5: Your code here. uintptr_t addr; for (addr = 0; addr < UTOP; addr += PGSIZE) { if ((uvpd[PDX(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_U) && (uvpt[PGNUM(addr)] & PTE_SHARE)) { sys_page_map(0, (void*)addr, child, (void*)addr, (uvpt[PGNUM(addr)] & PTE_SYSCALL)); } } return 0; }
運行make run-icode,將會執行user/icode,user/icode又會執行inti,而後會spawn sh。而後就能運行以下指令:
echo hello world | cat cat lorem |cat cat lorem |num cat lorem |num |num |num |num |num lsfd
目前shell還不支持IO重定向,修改user/sh.c,增長IO該功能。
runcmd(char* s) { ... if ((fd = open(t, O_RDONLY)) < 0) { cprintf("open %s for write: %e", t, fd); exit(); } if (fd != 0) { dup(fd, 0); close(fd); } ... }
具體代碼在:https://github.com/gatsbyd/mit_6.828_jos
若有錯誤,歡迎指正(*^_^*): 15313676365