MIT6.828 La5 File system, Spawn and Shell

Lab 5: File system, Spawn and Shell

1. File system preliminaries

在lab中咱們要使用的文件系統比大多數「真實」文件系統更簡單,包括XV6 UNIX的文件系統,但它足以提供基本功能:建立,讀取,寫入和刪除在分層目錄結構中組織的文件。node

咱們僅開發一個單用戶操做系統, 所以,咱們的文件系統不支持文件全部權或權限。 咱們的文件系統目前也不支持硬連接,符號連接,時間戳或大多數UNIX文件系統的特殊設備文件。c++

1. On-Disk File System Structure

大多數UNIX文件系統將可用磁盤空間分爲兩種主要類型的區域:inode區域和數據區域。 UNIX文件系統爲文件系統中的每一個文件分配一個inode;文件的inode保存關於文件的關鍵元數據,例如其stat屬性和指向其數據塊的指針。數據區域被劃分紅更大(一般爲8KB或更多)的數據塊,文件系統在其中存儲文件數據和目錄元數據。目錄條目包含文件名和指向inode的指針;若是文件系統中的多個目錄條目引用該文件的inode,則文件被稱爲硬連接。因爲咱們的文件系統不支持硬連接,因此咱們不須要這種級別的重定向,所以能夠方便的簡化:咱們的文件系統根本不會使用inode,而只是在(惟一)的目錄條目中存儲全部的文件(或子目錄)的元數據。shell

文件和目錄邏輯上都是由一系列數據塊組成的,這些數據塊可能散佈在整個磁盤上,就像用戶環境的虛擬地址空間的頁面能夠分散在整個物理內存中同樣。文件系統環境隱藏數據塊佈局的細節,僅呈如今文件任意偏移量處讀/寫字節序列的接口。文件系統環境將對目錄的全部修改做爲文件建立和刪除等操做內部處理的一部分。咱們的文件系統容許用戶環境直接讀取目錄元數據(例如,read),這意味着用戶環境能夠本身執行目錄掃描操做(例如,實現ls程序),而沒必要依賴額外特殊的對文件系統的調用。對目錄掃描方法的缺點,以及大多數現代UNIX變體阻止它的緣由在於它使應用程序依賴於目錄元數據的格式,使得在不更改或至少從新編譯應用程序的狀況下難以更改文件系統的內部佈局。數組

簡單來說,咱們文件系統就只有一個數據結構保存文件,沒有索引。緩存

1.1 Sectors and Blocks

大多數磁盤不能以字節粒度執行讀取和寫入,而是以扇區爲單位執行讀取和寫入操做。在JOS中,扇區爲512字節。文件系統實際上以塊爲單位分配和使用磁盤存儲。請注意兩個術語之間的區別:扇區大小是磁盤硬件的屬性,而塊大小是操做系統使用磁盤的一個方面。文件系統的塊大小必須是底層磁盤扇區大小的倍數。服務器

UNIX xv6文件系統使用512字節的塊大小,與底層磁盤的扇區大小相同。然而,大多數現代文件系統使用更大的塊大小,由於存儲空間已經變得更便宜,而且以更大的粒度來管理存儲效率更高。咱們的文件系統將使用4096字節的塊大小,方便地匹配處理器的頁面大小。數據結構

簡單來說,磁盤默認512字節是一個扇區,咱們系統4096字節一個塊,也就是8個扇區一個塊。app

1.2 Superblocks

文件系統一般將某些磁盤塊保留在磁盤上的「易於查找」位置(例如起始或最後),以保存描述整個文件系統屬性的元數據,例如塊大小,磁盤大小,找到根目錄所需的任何元數據,文件系統上次掛載的時間,文件系統上次檢查錯誤的時間等等。這些特殊塊稱爲超級塊。ide

咱們的文件系統將只有一個超級塊,它將始終位於磁盤上的塊1。它的佈局由struct Super在inc/fs.h中定義。塊0一般保留用於保存引導加載程序和分區表,所以文件系統一般不使用第一個磁盤塊。許多「真正的」文件系統具備多個超級塊,這幾個副本在磁盤的幾個普遍間隔的區域,以便若是其中一個被損壞或磁盤在該區域中產生媒體錯誤,則仍然能夠找到其餘超級塊,並將其用於訪問文件系統。
函數

其中具體塊的數據結構以下

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
};

1.3 File Meta-data

描述文件系統中的文件的元數據的佈局由inc/fs.h中的struct File定義。該元數據包括文件的名稱,大小,類型(常規文件或目錄)以及指向包含該文件的塊的指針。如上所述,咱們沒有inode,因此元數據存儲在磁盤上的目錄條目中。與大多數「真實」文件系統不一樣,爲簡單起見,咱們將使用這個struct File來表示在磁盤和內存中出現的文件元數據。

struct File中的f_direct數組包含存儲文件前10個(NDIRECT)塊的塊號的空間,這前10個塊被稱之爲文件的直接塊。對於大小爲10 * 4096 = 40KB的小文件,這意味着全部文件塊的塊號將直接適用於struct File自己。然而,對於較大的文件,咱們須要一個地方來保存文件的其餘塊號。所以,對於大於40KB的任何文件,咱們分配一個額外的磁盤塊,稱爲文件的間接塊,最多容納4096/4 = 1024個附加塊號。所以,咱們的文件系統容許文件的大小可達1034個塊,或者剛剛超過四兆字節大小。爲了支持更大的文件,「真實」文件系統一般也支持雙重和三重間接塊。

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

1.4 Directories versus Regular Files

咱們的文件系統中的struct File能夠表示常規文件或目錄;這兩種類型的「文件」經過struct File中的類型字段進行區分。文件系統以徹底相同的方式管理常規文件和目錄文件,除了它不解釋與常規文件相關聯的數據塊的內容,而文件系統將目錄文件的內容解釋爲一系列描述目錄中的文件和子目錄的struct File

// File types
#define FTYPE_REG	0	// Regular file 文件
#define FTYPE_DIR	1	// Directory 目錄

咱們的文件系統中的超級塊包含一個struct File(其實struct Super中的根字段),它保存文件系統根目錄的元數據。根目錄文件的內容是描述位於文件系統根目錄下的文件和目錄的struct File序列。根目錄中的任何子目錄能夠依次包含表示子子目錄的更多的struct File,依此類推。

2. The File System

lab的目標不是實現整個文件系統,而是僅實現某些關鍵組件。特別是,須要實現將塊讀入塊高速緩存並將其刷新回磁盤;分配磁盤塊;將文件偏移映射到磁盤塊;並在IPC接口中中實現讀,寫和打開。由於你不會本身實現全部的文件系統,因此你須要熟悉提供的代碼和各類文件系統接口。

2.1 Disk Access

x86處理器使用EFLAGS寄存器中的IOPL位來肯定是否容許保護模式代碼執行特殊的設備I/O指令,如IN和OUT指令。因爲咱們須要訪問的全部IDE磁盤寄存器位於x86的I/O空間中,而不是內存映射,所以爲文件系統環境提供「I/O特權」是咱們惟一須要作的,以便容許文件系統訪問這些寄存器。實際上,EFLAGS寄存器中的IOPL位爲內核提供了一種簡單的「全或無」方法來控制用戶態代碼可否訪問I/O空間。在咱們的實現中,咱們但願文件系統環境可以訪問I/O空間,可是咱們不但願任何其餘環境可以訪問I/O空間。

因此對於第一個Exercise1是很是簡單的,只須要一行代碼。

不過這裏我遇到了一個問題

這個問題我修了一個小時。。fuck

後面發現是make參數的問題。原本的makefile文件設置了Werror參數。這表示會把warning看成error來處理。後面把它刪掉就沒問題了

// If this is the file server (type == ENV_TYPE_FS) give it I/O privileges.
	// LAB 5: Your code here.
	if (type == ENV_TYPE_FS) {
		env->env_tf.tf_eflags |= FL_IOPL_MASK;
	}

3. The Block Cache

在咱們的文件系統中,咱們將在處理器的虛擬內存系統的幫助下實現一個簡單的「緩衝區緩存」(真正只是一個塊緩存)。 塊緩存的代碼在fs/bc.c中。

咱們的文件系統將僅限於處理小於等於3GB的磁盤。 咱們爲文件系統預留了固定的3GB區域。從0x10000000(diskmap)到0xd0000000(diskmap + diskmax),做爲磁盤的「內存映射」版本。 例如,磁盤塊0映射在虛擬地址0x10000000,磁盤塊1映射在虛擬地址0x10001000等等。

Exercise 2

實現bc_pgfault()flush_block()
bc_pgfault()是文件系統中的進程缺頁處理函數,負責將數據從磁盤讀取到對應的內存

參考註釋的提示也是不難實現

bc_pgfault()實現

static void
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_U | PTE_W | PTE_P);
	if ((r = ide_read(blockno * BLKSECTS,addr,BLKSECTS)) < 0) {
		panic("ide_read error : %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("sys_page_map error : %e",r);
	}
	if (bitmap && block_is_free(blockno)) {
		panic("reading free block %08x\n",blockno);
	}
	
}

flush_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.
	// round addr down
	addr = ROUNDDOWN(addr,PGSIZE);
	if (!va_is_mapped(addr) || ! va_is_dirty(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 ) {
		panic("sys_page_map error : %e",r);
	}
}

4. The Block Bitmap

fs_init()中已經初始化了bitmap,咱們能經過bitmap訪問磁盤的block 1,也就是位數組,每一位表明一個block,1表示該block未被使用,0表示已被使用。咱們實現一系列管理函數來管理這個位數組。

Exercise 3

實現fs/fs.c中的alloc_block(),該函數搜索bitmap位數組,返回一個未使用的block,並將其標記爲已使用。

這裏主要仿照free_block.c函數來寫.

根據blockno來清楚bitmap數組。所以當咱們設置bitmap數組的時候,只須要作取反操做便可

// Mark a block free in the bitmap
void
free_block(uint32_t blockno)
{
	// Blockno zero is the null pointer of block numbers.
	if (blockno == 0)
		panic("attempt to free zero block");
	bitmap[blockno/32] |= 1<<(blockno%32);
}

所以alloc_block函數就很是好實現了

int
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.
	for (int i = 3; i < super->s_nblocks; i++) {
		if (block_is_free(i)) {
			bitmap[i/32] &= ~(1<<(i%32));
			return i;
		}
	}

	return -E_NO_DISK;
}

5. File Operations

fs/fs.c文件提供了一系列函數用於管理File結構,掃描和管理目錄文件,解析絕對路徑。
基本的文件系統操做:

1. static int file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc){}
2. int file_get_block(struct File *f, uint32_t filebno, char **blk)

解析路徑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。

3. static int walk_path(const char **path*, struct *File* ***pdir*, struct *File* ***pf*, char **lastelem*)

該函數尋找dir指向的文件內容。並尋找制定name的file結構保存到file指針處

4. static int dir_lookup(struct File *dir, const char *name, struct File **file)

在dir目錄文件的內容中尋找一個未被使用的File結構,將其地址保存到file的地址處

5. static int dir_alloc_file(struct File *dir, struct File **file)

基本的文件操做

在給定path建立file,若是建立成功pf指向新建立的File指針

1. int file_create(const char *path, struct File **pf)

尋找path對應的File結構地址,保存到pf地址處。

2. int file_open(const char *path, struct File **pf)

從文件f的offset字節處讀取count字節到buf處

3. size_t* file_read(struct *File* **f*, void **buf*, *size_t* *count*, *off_t* *offset*)

將buf處的count字節寫到文件f的offset開始的位置。

4. int file_write(struct File *f, const void *buf, size_t count, off_t offset)

Exercise 4

實現file_block_walk()和file_get_block()。
file_block_walk():

該函數查找f指針指向文件結構的第filebnoblock的存儲地址,保存到ppdiskbno中。

  1. 若是filebno > NDIRECT 則須要去checkf_indirect。若是還未建立indirect塊。可是alloc爲真,那麼將分配要給新的block做爲該文件的f->f_indirect。類比頁表管理的pgdir_walk()。
  2. 最簡單的狀況是能夠在NDIRECT中獲取到對應的塊
static int
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
{
    // LAB 5: Your code here.
	// first do sanity check
	if (filebno >= NDIRECT + NINDIRECT) {
		return -E_INVAL;
	}
	uintptr_t *block_addr = NULL;
	if (filebno < NDIRECT) {
		block_addr = &f->f_direct[filebno];
	} else {
		int r;
		if (f->f_indirect == 0) {
			if (alloc) {
				r = alloc_block();
				if (r < 0) {
					return r;
				}
				memset(diskaddr(r),0,BLKSIZE);
				f->f_indirect = r;
			} else {
				return -E_NOT_FOUND;
			}
		}
		uint32_t *indir = (uint32_t *)diskaddr(f->f_indirect);
		block_addr = indir[filebno - NDIRECT];
	}
	*ppdiskbno = block_addr;
	return 0;
}

file_get_block

  1. 這個就是獲取制定的block
  2. 首先調用咱們以前實現的file_block_walk函數。若是沒問題則直接ok
  3. 不然的話會分配一個新的block,注意這裏要把它flush到磁盤裏
int
file_get_block(struct File *f, uint32_t filebno, char **blk)
{
    // LAB 5: Your code here.
	int r;
	uint32_t *ppdiskbno;
	if ((r = file_block_walk(f, filebno, &ppdiskbno, 1)) < 0) {
		return r;
	}

	int blockno;
	if (*ppdiskbno == 0) {
		if ((blockno = alloc_block()) < 0) {
			return blockno;
		}

		*ppdiskbno = blockno;
		flush_block(diskaddr(blockno));
	}

	*blk = diskaddr(*ppdiskbno);
	return 0;
}

6. The file system interface

固然其餘env也有對於文件系統環境的請求。這時候咱們須要有相似下面的機制

Exercise 5

Implement serve_read in fs/serv.c.

serve_read 的繁重工做將由 fs/fs.c 中已經實現的 file_read 完成(反過來,它只是對 file_get_block 的一堆調用)。 serve_read 只須要提供用於文件讀取的 RPC 接口。 查看 serve_set_size 中的註釋和代碼,以大體瞭解服務器功能的結構。

那咱們在寫這個代碼以前首先就要剖析一下整個ipc的發生過程

  1. regular_env中調用read函數

    這個函數首先根據fd_lookup找到對應fdnum的fd結構

    隨後根據dev_lookup找到對應的dev信息

    而後調用dev_read(fd,buf,n)

    ssize_t
    read(int fdnum, void *buf, size_t n)
    {
    	int r;
    	struct Dev *dev;
    	struct Fd *fd;
    
    	if ((r = fd_lookup(fdnum, &fd)) < 0
    	    || (r = dev_lookup(fd->fd_dev_id, &dev)) < 0)
    		return r;
    	if ((fd->fd_omode & O_ACCMODE) == O_WRONLY) {
    		cprintf("[%08x] read %d -- bad mode\n", thisenv->env_id, fdnum);
    		return -E_INVAL;
    	}
    	if (!dev->dev_read)
    		return -E_NOT_SUPP;
    	return (*dev->dev_read)(fd, buf, n);
    }
    // 涉及到的三個不一樣的dev;
    static struct Dev *devtab[] =
    {
    	&devfile,
    	&devpipe,
    	&devcons,
    	0
    };
  2. 隨後是調用lib/file.c

    1. 這裏是把請求參數存儲在fsipcbuf.read中
    2. 而後調用fsipc去向服務器端發送read請求。請求成功後結果也是保存在共享頁面fsipcbuf中,而後讀到指定的buf就行。
    static ssize_t
    devfile_read(struct Fd *fd, void *buf, size_t n)
    {
    	// Make an FSREQ_READ request to the file system server after
    	// filling fsipcbuf.read with the request arguments.  The
    	// bytes read will be written back to fsipcbuf by the file
    	// system server.
    	int r;
    
    	fsipcbuf.read.req_fileid = fd->fd_file.id;
    	fsipcbuf.read.req_n = n;
    	if ((r = fsipc(FSREQ_READ, NULL)) < 0)
    		return r;
    	assert(r <= n);
    	assert(r <= PGSIZE);
    	memmove(buf, fsipcbuf.readRet.ret_buf, r);
    	return r;
    }
  3. lib/file.c/fsipc()函數

    1. 找到第一個fs類型的env
    2. 而後調用ipc_send發送ipc信號
    3. 利用ipc_recv獲得返回結果
    static int
    fsipc(unsigned type, void *dstva)
    {
    	static envid_t fsenv;
    	if (fsenv == 0)
    		fsenv = ipc_find_env(ENV_TYPE_FS);
    
    	static_assert(sizeof(fsipcbuf) == PGSIZE);
    
    	if (debug)
    		cprintf("[%08x] fsipc %d %08x\n", thisenv->env_id, type, *(uint32_t *)&fsipcbuf);
    
    	ipc_send(fsenv, type, &fsipcbuf, PTE_P | PTE_W | PTE_U);
    	return ipc_recv(NULL, dstva, NULL);
    }
  4. 接下來看fs/serv.c/server函數

    這裏有一個while循環等到請求的接受,對於上面發送的ipc請求這裏會被接收到

    這裏的邏輯仍是很是簡單的

    1. 當ipc_recv獲得結果以後,就會往下執行
    2. 而後根據請求的類型作出不一樣的處理。對於read操做而言的話
    3. 就會執行server_read函數來處理這個ipc請求
    void
    serve(void)
    {
    	uint32_t req, whom;
    	int perm, r;
    	void *pg;
    
    	while (1) {
    		perm = 0;
    		req = ipc_recv((int32_t *) &whom, fsreq, &perm);
    		if (debug)
    			cprintf("fs req %d from %08x [page %08x: %s]\n",
    				req, whom, uvpt[PGNUM(fsreq)], fsreq);
    
    		// All requests must contain an argument page
    		if (!(perm & PTE_P)) {
    			cprintf("Invalid request from %08x: no argument page\n",
    				whom);
    			continue; // just leave it hanging...
    		}
    
    		pg = NULL;
    		if (req == FSREQ_OPEN) {
    			r = serve_open(whom, (struct Fsreq_open*)fsreq, &pg, &perm);
    		} else if (req < ARRAY_SIZE(handlers) && handlers[req]) {
    			r = handlers[req](whom, fsreq);
    		} else {
    			cprintf("Invalid request code %d from %08x\n", req, whom);
    			r = -E_INVAL;
    		}
    		ipc_send(whom, r, pg, perm);
    		sys_page_unmap(0, fsreq);
    	}
    }
  5. server_read函數

    這個函數就是咱們要實現的函數。根據提示咱們來實現一下

    1. 首先找到ipc->read->req_fileid對應的OpenFile。
    2. 而後調用file_read去讀內容到ipc->readRet->ret_buf
    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;
    	if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
    		return r;
    	if ((r = file_read(o->o_file, ret->ret_buf, req->req_n, o->o_fd->fd_offset)) < 0)
    		return r;
    	o->o_fd->fd_offset += r;
    	return r;
    }

Exercise6

Implement serve_write in fs/serv.c and devfile_write in lib/file.c.

這個的調用邏輯是和上面同樣的,因此就不分析了。那咱們直接看寫操做是如何實現的

首先實現server_write這個和server_read基本上徹底一致的

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 req_n = req->req_n > PGSIZE ? PGSIZE : req->req_n;
	int r;
	if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
		return r;
	if ((r = file_write(o->o_file, req->req_buf, req_n, o->o_fd->fd_offset)) < 0)
		return r;
	o->o_fd->fd_offset += r;
	return r;
}

而後實現devfile_write函數

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 csode here
	if (n > sizeof(fsipcbuf.write.req_buf))
		n = sizeof(fsipcbuf.write.req_buf);
	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);
}

7. Spawning Processes

咱們已經爲您提供了 spawn 的代碼(參見 lib/spawn.c),它建立一個新環境,將文件系統中的程序映像加載到其中,而後啓動運行該程序的子環境。 而後父進程獨立於子進程繼續運行。 spawn 函數的做用相似於 UNIX 中的 fork,而後是子進程中的 exec。

咱們實現了 spawn 而不是 UNIX 風格的 exec,由於 spawn 更容易以「外內核方式」從用戶空間實現,而無需內核的特殊幫助。 考慮一下爲了在用戶空間中實現 exec 必須作什麼。

Exercise7

spawn 依賴於新的系統調用 sys_env_set_trapframe 來初始化新建立環境的狀態。 在 kern/syscall.c 中實現 sys_env_set_trapframe(不要忘記在 syscall() 中調度新的系統調用)。

這個系統調用其實也不難實現.能夠參考env_alloc()

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!
	struct Env *e;
	int32_t ret;
	if ((ret = envid2env(envid, &e, 1)) < 0) {
		return ret; // -E_BAD_ENV
	}
	if ((ret = user_mem_check(e, tf, sizeof(struct Trapframe), PTE_U)) < 0) {
		return ret;
	}
	
	memmove(&e->env_tf, tf, sizeof(struct Trapframe));
	e->env_tf.tf_ds = GD_UD | 3;
	e->env_tf.tf_es = GD_UD | 3;
	e->env_tf.tf_ss = GD_UD | 3;
	e->env_tf.tf_cs = GD_UT | 3;
	e->env_tf.tf_eflags |= FL_IF;
	e->env_tf.tf_eflags &= ~FL_IOPL_MASK; //普通進程不能有IO權限
	return 0;
}

8. Sharing library state across fork and spawn

咱們但願在 fork 和 spawn 之間共享文件描述符狀態,但文件描述符狀態保存在用戶空間內存中。在 fork 上,內存將被標記爲 copy-on-write,所以狀態將被複制而不是共享。 (這意味着環境將沒法在它們本身沒有打開的文件中查找,而且管道沒法跨分支工做。)在spawn中,內存將被共享,根本不會被複制。 (有效地,spawn的environment不會打開任何文件描述符.)

咱們將更改fork以瞭解某些內存區域已由「操做系統庫」使用,而且應始終共享。 與其在某處硬編碼區域列表,不如在頁表條項設置一個otherwise-unused的位(就像咱們對fork中的PTE_COW位所作的那樣)。

咱們在inc / lib.h中定義了一個新的PTE_SHARE位。 該位是Intel和AMD手冊中標記爲「可用於軟件使用」的三個PTE位之一。 咱們將創建一個約定,若是頁表項設置了該位,則應該在fork和spawn中將PTE直接從父級複製到子級。 請注意,這不一樣於將其標記爲「copy-on-write」:如第一段所述,咱們要確保共享頁面更新。

Exercise8

更改 lib/fork.c 中的 duppage 以遵循新約定。 若是頁表條目設置了 PTE_SHARE 位,則直接複製映射。 (您應該使用 PTE_SYSCALL,而不是 0xfff,來屏蔽頁表條目中的相關位。0xfff 也拾取訪問的位和髒位。)

一樣,在lib/spawn.c 中實現 copy_shared_pages。 它應該遍歷當前進程中的全部頁表條目(就像 fork 所作的那樣),將任何設置了 PTE_SHARE 位的頁映射覆制到子進程中。

1. 修改 lib/fork.c

加入針對於PTE_SHARE的處理

perm = pte & PTE_SYSCALL;
		if ((r = sys_page_map(srcid, (void*)addr, envid, (void*)addr, perm)) < 0) {
			panic("sys_page_map: %e\n", r);
		}

2. 實現copy_shared_pages

static int
copy_shared_pages(envid_t child)
{
	// LAB 5: Your code here.
	int r,i;
	for (i = 0; i < PGNUM(USTACKTOP); i ++){ 
    // Attention! i跟pte一一對應,而i/1024就是該pte所在的頁表
		if((uvpd[i/1024] & PTE_P) && (uvpt[i] & PTE_P) && (uvpt[i] & PTE_SHARE)){ 
			if ((r = sys_page_map(0, PGADDR(i/1024, i%1024, 0), child,PGADDR(i/1024, i%1024, 0), uvpt[i] & PTE_SYSCALL)) < 0)
				return r;
		}
	}
	return 0;
	
}

9. The keyboard interface

要讓shell工做,咱們須要一種方法來鍵入它。QEMU一直在顯示咱們寫入到CGA顯示器和串行端口的輸出,但到目前爲止,咱們只在內核監視器中接受輸入。在QEMU中,在圖形化窗口中鍵入的輸入顯示爲從鍵盤到JOS的輸入,而在控制檯中鍵入的輸入顯示爲串行端口上的字符。kern/console.c已經包含了自lab 1以來內核監視器一直使用的鍵盤和串行驅動程序,可是如今您須要將它們附加到系統的其餘部分。

Exercise 9.

在你的kern/trap.c,調用kbd_intr處理trap`` IRQ_OFFSET+IRQ_KBD,調用serial_intr處理trap IRQ_OFFSET+IRQ_SERIAL。

咱們在lib/console.c中爲您實現了控制檯輸入/輸出文件類型。kbd_intr和serial_intr用最近讀取的輸入填充緩衝區,而控制檯文件類型耗盡緩衝區(控制檯文件類型默認用於stdin/stdout,除非用戶重定向它們)。

其實這裏很是簡單就是添加兩個新的trap處理函數

//kern/trap.c/trap_dispatch()
if (tf->tf_trapno == IRQ_OFFSET + IRQ_KBD){
	kbd_intr();
	return;
} 
else if (tf->tf_trapno == IRQ_OFFSET + IRQ_SERIAL){
	serial_intr();
	return;
}

而後咱們來看一下這兩個陷阱處理函數是怎麼作的

  1. kbd_intr()

    kbd_proc_data()是從鍵盤讀入a character就返回,若是沒輸入就返回-1*

    void kbd_intr(void)
    {
      cons_intr(kbd_proc_data);
    }
  2. cons_intr

    serial_proc_data()很明顯就是從串行端口讀一個

    void serial_intr(void){
    	if (serial_exists)
    		cons_intr(serial_proc_data);
    }

    這兩個函數都調用了cons_intr函數

  3. cons_intr函數

    將從鍵盤讀入的一行填充到cons.buf

    static void cons_intr(int (*proc)(void)) 
    {
    	int c;
    
    	while ((c = (*proc)()) != -1) {
    		if (c == 0)
    			continue;
    		cons.buf[cons.wpos++] = c;
    		if (cons.wpos == CONSBUFSIZE)
    			cons.wpos = 0;
    	}

10. The Shell

Run make run-icode or make run-icode-nox。這將運行內核並啓動user/icode。icode執行init,它將把控制檯設置爲文件描述符0和1(標準輸入和標準輸出)。而後它會spawn sh,也就是shell。你應該可以運行如下命令:

echo hello world | cat

cat lorem |cat

cat lorem |num

cat lorem |num |num

|num |num |num lsfd

注意,用戶庫例程cprintf直接打印到控制檯,而不使用文件描述符代碼。這對於調試很是有用,可是對於piping into other programs卻不是頗有用。要將輸出打印到特定的文件描述符(例如,1,標準輸出),請使用fprintf(1, 「…」, …)。 printf("…", …)是打印到FD 1的捷徑。有關示例,請參見user/lsfd.c。

Exercise 10.

shell不支持I/O重定向。若是能運行sh <script就更好,而不是像上面那樣手工輸入script中的全部命令。將<的I/O重定向添加到user/sh.c

case '<':	// Input redirection
		// Grab the filename from the argument list
		if (gettoken(0, &t) != 'w') {
			cprintf("syntax error: < not followed by word\n");
			exit();
		}
		// LAB 5: Your code here.
		if ((fd = open(t, O_RDONLY)) < 0) {
			cprintf("open %s for read: %e", t, fd);
			exit();
		}
		if (fd != 0) {
			dup(fd, 0); 
			close(fd);
		}
		break;

11. Summary

好了828暫時也告一段落了,lab6就不寫了。。我認識的好多人都沒寫,我也就暫時不寫了。(lab5寫的確實有點草率,主要是中間間隔了過久。。。。遇上暑假了)後面應該是會去看一下xv6的源碼。而後看完leveldb源碼。後面會寫一個DDIA的閱讀筆記,而後就是偶爾會更新刷題的總結博客啦。

相關文章
相關標籤/搜索