在lab中咱们要使用的文件系统比大多数“真实”文件系统更简单,包括XV6 UNIX的文件系统,但它足以提供基本功能:建立,读取,写入和删除在分层目录结构中组织的文件。node
咱们仅开发一个单用户操做系统, 所以,咱们的文件系统不支持文件全部权或权限。 咱们的文件系统目前也不支持硬连接,符号连接,时间戳或大多数UNIX文件系统的特殊设备文件。c++
大多数UNIX文件系统将可用磁盘空间分为两种主要类型的区域:inode区域和数据区域。 UNIX文件系统为文件系统中的每一个文件分配一个inode;文件的inode保存关于文件的关键元数据,例如其stat属性和指向其数据块的指针。数据区域被划分红更大(一般为8KB或更多)的数据块,文件系统在其中存储文件数据和目录元数据。目录条目包含文件名和指向inode的指针;若是文件系统中的多个目录条目引用该文件的inode,则文件被称为硬连接。因为咱们的文件系统不支持硬连接,因此咱们不须要这种级别的重定向,所以能够方便的简化:咱们的文件系统根本不会使用inode,而只是在(惟一)的目录条目中存储全部的文件(或子目录)的元数据。shell
文件和目录逻辑上都是由一系列数据块组成的,这些数据块可能散布在整个磁盘上,就像用户环境的虚拟地址空间的页面能够分散在整个物理内存中同样。文件系统环境隐藏数据块布局的细节,仅呈如今文件任意偏移量处读/写字节序列的接口。文件系统环境将对目录的全部修改做为文件建立和删除等操做内部处理的一部分。咱们的文件系统容许用户环境直接读取目录元数据(例如,read),这意味着用户环境能够本身执行目录扫描操做(例如,实现ls程序),而没必要依赖额外特殊的对文件系统的调用。对目录扫描方法的缺点,以及大多数现代UNIX变体阻止它的缘由在于它使应用程序依赖于目录元数据的格式,使得在不更改或至少从新编译应用程序的状况下难以更改文件系统的内部布局。数组
简单来说,咱们文件系统就只有一个数据结构保存文件,没有索引。缓存
大多数磁盘不能以字节粒度执行读取和写入,而是以扇区为单位执行读取和写入操做。在JOS中,扇区为512字节。文件系统实际上以块为单位分配和使用磁盘存储。请注意两个术语之间的区别:扇区大小是磁盘硬件的属性,而块大小是操做系统使用磁盘的一个方面。文件系统的块大小必须是底层磁盘扇区大小的倍数。服务器
UNIX xv6
文件系统使用512字节的块大小,与底层磁盘的扇区大小相同。然而,大多数现代文件系统使用更大的块大小,由于存储空间已经变得更便宜,而且以更大的粒度来管理存储效率更高。咱们的文件系统将使用4096字节的块大小,方便地匹配处理器的页面大小。数据结构
简单来说,磁盘默认512字节是一个扇区,咱们系统4096字节一个块,也就是8个扇区一个块。app
文件系统一般将某些磁盘块保留在磁盘上的“易于查找”位置(例如起始或最后),以保存描述整个文件系统属性的元数据,例如块大小,磁盘大小,找到根目录所需的任何元数据,文件系统上次挂载的时间,文件系统上次检查错误的时间等等。这些特殊块称为超级块。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 };
描述文件系统中的文件的元数据的布局由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
咱们的文件系统中的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,依此类推。
本lab
的目标不是实现整个文件系统,而是仅实现某些关键组件。特别是,须要实现将块读入块高速缓存
并将其刷新回磁盘
;分配磁盘块
;将文件偏移映射到磁盘块
;并在IPC接口中中实现读,写和打开
。由于你不会本身实现全部的文件系统,因此你须要熟悉提供的代码和各类文件系统接口。
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; }
在咱们的文件系统中,咱们将在处理器的虚拟内存系统的帮助下实现一个简单的“缓冲区缓存”(真正只是一个块缓存)。 块缓存的代码在fs/bc.c
中。
咱们的文件系统将仅限于处理小于等于3GB的磁盘。 咱们为文件系统预留了固定的3GB区域。从0x10000000(diskmap)到0xd0000000(diskmap + diskmax),做为磁盘的“内存映射”版本。 例如,磁盘块0映射在虚拟地址0x10000000,磁盘块1映射在虚拟地址0x10001000等等。
实现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); } }
fs_init()中已经初始化了bitmap,咱们能经过bitmap访问磁盘的block 1,也就是位数组,每一位表明一个block,1表示该block未被使用,0表示已被使用。咱们实现一系列管理函数来管理这个位数组。
实现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; }
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)
实现file_block_walk()和file_get_block()。
file_block_walk():
该函数查找f指针指向文件结构的第filebno
个block
的存储地址,保存到ppdiskbno
中。
filebno > NDIRECT
则须要去checkf_indirect
。若是还未建立indirect块。可是alloc
为真,那么将分配要给新的block做为该文件的f->f_indirect。类比页表管理的pgdir_walk()。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
block
file_block_walk
函数。若是没问题则直接okint 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; }
固然其余env也有对于文件系统环境的请求。这时候咱们须要有相似下面的机制
Implement serve_read
in fs/serv.c
.
serve_read 的繁重工做将由 fs/fs.c 中已经实现的 file_read 完成(反过来,它只是对 file_get_block 的一堆调用)。 serve_read 只须要提供用于文件读取的 RPC 接口。 查看 serve_set_size 中的注释和代码,以大体了解服务器功能的结构。
那咱们在写这个代码以前首先就要剖析一下整个ipc的发生过程
在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 };
随后是调用lib/file.c
的
- 这里是把请求参数存储在fsipcbuf.read中
- 而后调用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; }
看lib/file.c/fsipc()
函数
- 找到第一个fs类型的env
- 而后调用ipc_send发送ipc信号
- 利用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); }
接下来看fs/serv.c/server
函数
这里有一个while循环等到请求的接受,对于上面发送的ipc请求这里会被接收到
这里的逻辑仍是很是简单的
- 当ipc_recv获得结果以后,就会往下执行
- 而后根据请求的类型作出不一样的处理。对于read操做而言的话
- 就会执行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); } }
server_read
函数
这个函数就是咱们要实现的函数。根据提示咱们来实现一下
- 首先找到ipc->read->req_fileid对应的OpenFile。
- 而后调用
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; }
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); }
咱们已经为您提供了 spawn 的代码(参见 lib/spawn.c),它建立一个新环境,将文件系统中的程序映像加载到其中,而后启动运行该程序的子环境。 而后父进程独立于子进程继续运行。 spawn 函数的做用相似于 UNIX 中的 fork,而后是子进程中的 exec。
咱们实现了 spawn 而不是 UNIX 风格的 exec,由于 spawn 更容易以“外内核方式”从用户空间实现,而无需内核的特殊帮助。 考虑一下为了在用户空间中实现 exec 必须作什么。
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; }
咱们但愿在 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”:
如第一段所述,咱们要确保共享页面更新。
更改 lib/fork.c
中的 duppage
以遵循新约定。 若是页表条目设置了 PTE_SHARE
位,则直接复制映射。 (您应该使用 PTE_SYSCALL
,而不是 0xfff
,来屏蔽页表条目中的相关位。0xfff 也拾取访问的位和脏位。)
一样,在lib/spawn.c
中实现 copy_shared_pages
。 它应该遍历当前进程中的全部页表条目(就像 fork 所作的那样),将任何设置了 PTE_SHARE
位的页映射复制到子进程中。
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); }
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; }
要让shell工做,咱们须要一种方法来键入它。QEMU一直在显示咱们写入到CGA显示器和串行端口的输出,但到目前为止,咱们只在内核监视器中接受输入。在QEMU中,在图形化窗口中键入的输入显示为从键盘到JOS的输入,而在控制台中键入的输入显示为串行端口上的字符。kern/console.c已经包含了自lab 1以来内核监视器一直使用的键盘和串行驱动程序,可是如今您须要将它们附加到系统的其余部分。
在你的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; }
而后咱们来看一下这两个陷阱处理函数是怎么作的
kbd_intr()
kbd_proc_data()是从键盘读入a character就返回,若是没输入就返回-1*
void kbd_intr(void) { cons_intr(kbd_proc_data); }
cons_intr
serial_proc_data()很明显就是从串行端口读一个
void serial_intr(void){ if (serial_exists) cons_intr(serial_proc_data); }
这两个函数都调用了cons_intr
函数
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; }
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。
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;
好了828暂时也告一段落了,lab6就不写了。。我认识的好多人都没写,我也就暂时不写了。(lab5写的确实有点草率,主要是中间间隔了过久。。。。遇上暑假了)后面应该是会去看一下xv6的源码。而后看完leveldb源码。后面会写一个DDIA的阅读笔记,而后就是偶尔会更新刷题的总结博客啦。