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的阅读笔记,而后就是偶尔会更新刷题的总结博客啦。

相关文章
相关标签/搜索