补充资料:
file system
典型文件系统API:open
、write
、link
、unlink
、read
、close
文件系统结构
inode:代表文件的对象
文件系统中核心的数据结构就是inode和file descriptor
后者主要与用户进程进行交互。
文件系统还挺复杂的,所以最好按照分层的方式进行理解。可以这样看:
- 在最底层是磁盘,也就是一些实际保存数据的存储设备,正是这些设备提供了持久化存储。
- 在这之上是buffer cache或者说block cache,这些cache可以避免频繁的读写磁盘。这里我们将磁盘中的数据保存在了内存中。
- 为了保证持久性,再往上通常会有一个logging层。许多文件系统都有某种形式的logging
- 在logging层之上,有inode cache,这主要是为了同步(synchronization)。inode通常小于一个disk block,所以多个inode通常会打包存储在一个disk block中。为了向单个inode提供同步操作,XV6维护了inode cache。
- 再往上就是inode本身了。它实现了read/write。
- 再往上,就是文件名,和文件描述符操作。
1、disk
- sector通常是磁盘驱动可以读写的最小单元,它过去通常是512字节。
- block通常是操作系统或者文件系统视角的数据。它由文件系统定义,在XV6中它是1024字节。所以XV6中一个block对应两个sector。通常来说一个block对应了一个或者多个sector。
通常来说:
- block0要么没有用,要么被用作
boot sector
来启动操作系统。 - block1通常被称为
super block
,它描述了文件系统。它包含磁盘上有多少个block共同构成了文件系统这样的信息。我们之后会看到XV6在里面会存更多的信息,你可以通过block1构造出大部分的文件系统信息。 - 在XV6中,log从block2开始,到block32结束。实际上log的大小可能不同,这里在super block中会定义log就是30个block。
- 接下来在block32到block45之间,XV6存储了inode。我之前说过多个inode会打包存在一个block中,一个inode是64字节。
- 之后是bitmap block,这是我们构建文件系统的默认方法,它只占据一个block。它记录了数据block是否空闲。
- 之后就全是数据block了,数据block存储了文件的内容和目录的内容。
2、block cache(buffer cache)
block cache就是磁盘中block在内存中的拷贝
3、logging
文件系统crash之后的问题的解决方案,其实就是logging;
当需要更新文件系统时,我们并不是更新文件系统本身。假设我们在内存中缓存了bitmap block,也就是block 45。当需要更新bitmap时,我们并不是直接写block 45,而是将数据写入到log中,并记录这个更新应该写入到block 45。
4、inode
inode:64字节
- 通常来说它有一个type字段,表明inode是文件还是目录。
- nlink字段,也就是link计数器,用来跟踪究竟有多少文件名指向了当前的inode。
- size字段,表明了文件数据有多少个字节。
- 不同文件系统中的表达方式可能不一样,不过在XV6中接下来是一些block的编号,例如编号0,编号1,等等。XV6的inode中总共有12个block编号。这些被称为direct block number。这12个block编号指向了构成文件的前12个block。举个例子,如果文件只有2个字节,那么只会有一个block编号0,它包含的数字是磁盘上文件前2个字节的block的位置。
- 之后还有一个indirect block number,它对应了磁盘上一个block,这个block包含了256个block number,这256个block number包含了文件的数据。所以inode中block number 0到block number 11都是direct block number,而block number 12保存的indirect block number指向了另一个block。
由此可算出一个文件的最大长度等于=(256+12)*1024bytes——一个block1024字节(不同文件系统不一样)
本lab将实现JOS的文件系统,包括如下四部分:
- 引入一个**文件系统进程(FS进程)**的特殊进程,该进程提供文件操作的接口。
- 建立RPC机制,客户端进程向FS进程发送请求,FS进程真正执行文件操作,并将数据返回给客户端进程。
- 更高级的抽象,引入文件描述符。通过文件描述符这一层抽象就可以将控制台,pipe,普通文件,统统按照文件来对待。(文件描述符和pipe实现原理)
- 支持从磁盘加载程序并运行。
Sectors and Blocks
大多数磁盘无法以字节粒度执行读取和写入,而是以扇区为单位执行读取和写入。 在 JOS 中,每个扇区为 512 字节。 文件系统实际上以块为单位分配和使用磁盘存储。请注意这两个术语之间的区别:扇区大小是磁盘硬件的属性, 而块大小是使用磁盘的操作系统的一个方面。 文件系统的块大小必须为基础磁盘扇区大小的倍数。
UNIX xv6 文件系统使用 512 字节的块大小,与基础磁盘的扇区大小相同。 但是,大多数现代文件系统使用更大的块大小, 因为存储空间变得便宜得多。而且,以更大的粒度管理存储会更有效。 我们的文件系统将使用4096字节的块大小,方便地匹配处理器的页面大小。
Superblocks
文件系统通常保留磁盘上“易于查找”的位置 (例如最开始或最结束),以此来保存描述整个文件系统属性的元数据, 如块大小、磁盘大小、 查找根目录所需的任何元数据,上次挂载文件系统的时间,上次检查文件系统是否存在错误的时间,等等。 这些特殊块称为超级块
我们的文件系统将只有一个超级块, 它将始终位于磁盘上的块 1 处。 它的布局由 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
};
File Meta-data
文件系统使用struct File结构描述文件,该结构包含文件名,大小,类型,保存文件内容的block号。struct File结构的f_direct数组保存前NDIRECT(10)个block号,这样对于10*4096=40KB的文件不需要额外的空间来记录内容block号。对于更大的文件我们分配一个额外的block来保存4096/4=1024 block号。所以我们的文件系统允许文件最多拥有1034个block,一个文件最大为4MB
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
Disk Access
操作系统中的文件系统环境需要能够访问磁盘,但是我们还没有在内核中实现任何磁盘访问功能。
我们使用一个文件系统进程来作为磁盘驱动,通过轮询而不是中断来实现在用户空间进行磁盘访问。而不是像其他操作提供系统调用。
在kern/env.c
::env_create()
中添加代码:
// 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)
e->env_tf.tf_eflags |= FL_IOPL_MASK;//给文件进程以IO权限
The Block Cache
我们的文件系统将仅限于处理大小为 3GB 或更小的磁盘。 我们保留了一个大型固定的3GB区域文件系统环境的地址空间,从0x10000000(DISKMAP)最多0xD0000000(DISKMAP+DISKMAX),作为磁盘的“内存映射”版本。例如磁盘块0映射在虚拟地址0x10000000,磁盘块1映射在虚拟地址0x10001000,等等。
当然,将整个磁盘读取到内存需要很长时间,因此我们将实现一种需求分页的形式,其中我们只在磁盘映射区域中分配页面并读取来自磁盘的相应块,以响应此页面错误。这样,我们可以假设整个磁盘都在内存中。
fs/bc.c
::bc_pgfault(struct UTrapframe *utf)
:
是FS进程缺页处理函数,负责将数据从磁盘读取到对应的内存。与普通进程缺页处理相同
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 = (void *)ROUNDDOWN(addr, PGSIZE);
//为缺页的地址分配物理内存
if ((r = sys_page_alloc(0, addr, PTE_P | PTE_W | PTE_U)) < 0)
panic("bc_pgfault failed: sys_page_alloc: %e", r);
//将磁盘数据读入addr指向的内存中
if ((r = ide_read(blockno * BLKSECTS, addr, BLKSECTS)) < 0)
panic("bc_pgfault failed: ide_read: %e", r);
//因为我们已经重新读入了这个磁盘的内容,因此,dirty标志位需要清除,防止不必要的从内从中写入磁盘的操作。
// 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);
}
fs/bc.c
::flush_block(void *addr)
:
将内存中addr地址上的数据写回磁盘,如果没有被映射或者不是脏块则直接return
void flush_block(void *addr)
{
uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;
if (addr < (void *)DISKMAP || addr >= (void *)(DISKMAP + DISKSIZE))
panic("flush_block of bad va %08x", addr);
// LAB 5: Your code here.
int r;
addr = (void *)ROUNDDOWN(addr, PGSIZE);
if (va_is_mapped(addr) && va_is_dirty(addr))
{
r = ide_write(blockno * BLKSECTS, addr, BLKSECTS);
if (r < 0)
panic("flush_block failed: ide_write: %e", r);
if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
panic("flush_block failed: sys_page_map: %e", r);
}
// panic("flush_block not implemented");
}
The Block Bitmap
bitmap:将磁盘上的每个块用一位来记录是否使用
fs/fs.c
::alloc_block(void)
:
搜索bitmap,找到一个free block然后分配它,并及时刷新bitmap 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.
uint32_t blockno;
//block 0是保留的,可能作为boot sector
for (blockno = 1; blockno < super->s_nblocks; ++blockno)
{
if (block_is_free(blockno))
{
bitmap[blockno / 32] &= ~(1 << (blockno % 32));
flush_block(&bitmap[blockno / 32]);
return blockno;
}
}
// panic("alloc_block not implemented");
return -E_NO_DISK;
}
File Operations
基本的文件系统操作fs/fs.c
:
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
:
查找文件f中的第filebno个指向磁盘的块,并将此块的编号放入ppdiskbno中,如果alloc为真,且indirect block没有被分配,则分配一个新的block作为indirect block
static int
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
{
// LAB 5: Your code here.
//一个文件结构最多只能用NDIRECT+NINDIRECT个block来记录数据
if (filebno >= NDIRECT + NINDIRECT)
return -E_INVAL;
uint32_t *indirects;
int bno;
if (filebno < NDIRECT)
{
if (ppdiskbno)
*ppdiskbno = f->f_direct + filebno;
return 0;
}
else
{
if (f->f_indirect)
{
indirects = diskaddr(f->f_indirect);
}
else
{
if (!alloc)
return -E_NOT_FOUND;
if ((bno = alloc_block()) < 0)
return bno;
f->f_indirect = bno;
memset(diskaddr(bno), 0, BLKSIZE);
flush_block(diskaddr(bno));
indirects = diskaddr(bno);
}
if (ppdiskbno)
*ppdiskbno = &(indirects[filebno - NDIRECT]);
}
return 0;
// panic("file_block_walk not implemented");
}
file_get_block(struct File *f, uint32_t filebno, char **blk)
:
将文件f中的第filebno个block的虚拟地址存到*blk中
int file_get_block(struct File *f, uint32_t filebno, char **blk)
{
// LAB 5: Your code here.
uint32_t *ppdiskbno;
int ret = file_block_walk(f, filebno, &ppdiskbno, 1);
if (ret < 0)
return ret;
int bno;
if (*ppdiskbno == 0)
{
if ((bno = alloc_block()) < 0)
return bno;
*ppdiskbno = bno;
memset(diskaddr(bno), 0, BLKSIZE);
flush_block(diskaddr(bno));
}
*blk = diskaddr(*ppdiskbno);
return 0;
// panic("file_get_block not implemented");
}
dir_lookup(struct File *dir, const char *name, struct File **file)
:
在dir中查找一个文件名为name的文件,并保存在*file中。
采用的是轮询查找,并比较文件名
dir_alloc_file(struct File *dir, struct File **file)
:
在dir寻找一个空的file结构,将其地址保存在*flie中
walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)
:
解析字符串path,比如:/root/home/fs.c,将home文件夹放入pdir,将fs.c放入pf,lastelem存的是fs.c字符串
文件操作:
file_create(const char *path, struct File **pf)
:
创建path,如果创建成功,则将其保存在*pf中
file_open(const char *path, struct File **pf)
:
打开path对应的文件,并保存在*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位置
The file system interface
现在,在文件系统环境中,已经有必要的功能了,但是还需要提供其他进程对文件系统的访问接口。
RPC: Remote Procedure Call
通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
本质上RPC还是借助IPC机制实现的,普通进程通过IPC向FS进程间发送具体操作和操作数据,然后FS进程执行文件操作,最后又将结果通过IPC返回给普通进程。
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 |
| | | | ^ |
+-------|-------+ +-------|-------+
| |
+-------------------+
在虚线下的部分是普通进程如何发送一个读请求到文件系统服务进程的机制。首先read操作文件描述符,分发给合适的设备读函数devfile_read 。devfile_read函数实现读取磁盘文件,作为客户端文件操作函数。然后建立请求结构的参数,调用fsipc函数来发送IPC请求并解析返回的结果。
文件系统服务端代码位于fs/serv.c
中,serve()
函数无限循环,接收IPC请求,并将对应的请求分配到对应的处理函数,然后将处理结果通过IPC发送回去。
文件系统中,每个打开的文件有三个结构:
-
struct file
,主要是从磁盘映射到内存的一种结构,文件系统不能直接访问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 Fd
,文件描述符,它是所有类型文件的一种抽象,用户使用的就是Fd,其可以指代真是文件、Socket、管道等等。此lab中,只使用文件,所以union中只有一个FDFilestruct Fd { int fd_dev_id; off_t fd_offset; int fd_omode; union { // File server files struct FdFile fd_file; }; };
-
struct OpenFile
,将一个真实文件struct File和用户客户端打开的文件描述符struct Fd对应到一起,服务端使用了一个OpenFile数组,最多有MAXOPEN个OpenFile。客户端使用file ID与服务端通信struct OpenFile { uint32_t o_fileid; // file id struct File *o_file; // mapped descriptor for open file int o_mode; // open mode struct Fd *o_fd; // Fd page };
fs/serv.c
::serve_read(envid_t envid, union Fsipc *ipc)
:
向文件系统进行read请求,主要调用底层的file_read
来实现
从ipc->read.req_fileid中读ipc->read>req_n个字节,将其写入ipc->readRet中
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 *of;
int r;
if ((r = openfile_lookup(envid, req->req_fileid, &of)) < 0)
return r;
if ((r = file_read(of->o_file, ret->ret_buf, req->req_n, of->o_fd->fd_offset)) < 0)
return r;
of->o_fd->fd_offset += r;
return r;
}
fs/serv.c
::serve_write(envid_t envid, struct Fsreq_write *req)
:
向文件系统进行write请求,主要调用底层的file_write
来实现
从req_buf向req_fileid中写入req->req_n个字节
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 *of;
int r;
if ((r = openfile_lookup(envid, req->req_fileid, &of)) < 0)
return r;
if ((r = file_write(of->o_file, req->req_buf, req->req_n, of->o_fd->fd_offset)) < 0)
return r;
of->o_fd->fd_offset += r;
return r;
// panic("serve_write not implemented");
}
lib/file.c
::devfile_write(struct Fd *fd, const void *buf, size_t n)
:
客户端进程函数,可以参考devfile_read,直接调用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;
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);
// panic("devfile_write not implemented");
}
Spawning Processes
lib/spawn.c
中的spawn()
将用户程序从文件系统加载进来,并创建一个新的进程来运行这个程序。spawn()
的作用与UNIX中的fork()
后面马上跟着exec()
相同。
kern/syscall.c
::sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
:
将envid的trapframe设置为tf
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;
int ret = envid2env(envid, &e, 1);
if (ret < 0)
return ret;
user_mem_assert(e, tf, sizeof(struct Trapframe), PTE_U);
e->env_tf = *tf;
e->env_tf.tf_cs |= 3;
e->env_tf.tf_eflags &= (~FL_IOPL_MASK);
e->env_tf.tf_eflags |= FL_IF;
return 0;
// panic("sys_env_set_trapframe not implemented");
}
//还要在syscall函数中调用
case SYS_env_set_trapframe:
return sys_env_set_trapframe((envid_t)a1, (struct Trapframe *)a2);
Sharing library state across fork and spawn
UNIX文件描述符是一个一般的概念,包括管道、控制台 I/O 等。在JOS中,这些设备中的每一个类型具有相应的struct Dev
,该结构带有指向实现函数的指针,其指向该设备类型的read/write等。lib/fd.c
中实现了UNIX文件描述符接口。
在lib/fd.c
中,为每个进程维护了文件描述符表,从FSTABLE
开始,为每个描述符保留了1页的地址空间。当且仅当文件描述符在使用中时,映射特定文件描述符表页。
我们希望在调用fork
和spawn
来创建新进程时,能够共享文件描述符状态。目前,调用fork函数只能使用COW位,从而是将状态复制一份,而不是共享。在spawn中,更不会被复制,而是直接丢弃。
因此,我们在inc/lib.h
中,新定义了一个PTE_SHARE
标志位来表示页共享。当存在该标志位时,fork和spawn函数应该从父进程中拷贝PTE到子进程中。
lib/fork.c
::duppage(envid_t envid, unsigned pn)
:
在duppage中添加一个if语句来检查PTE_SHARE标志位
PTE_SHARE和PTE_COW都是将父进程的映射关系拷贝到子进程中,但之后的行为是不同的。PTE_SHARE是共享,其中一个进程修改,则直接修改其内容,另一个进程也会改变。 PTE_COW是写时复制,其中一个进程修改,会先复制出来,再修改,另一个进程不受影响
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)
{
if ((r = sys_page_map(0, addr, envid, addr, uvpt[pn] & PTE_SYSCALL)) < 0)
return r;
}
else if ((uvpt[pn] & PTE_W) || (uvpt[pn] & PTE_COW))
{
if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U | PTE_COW)) < 0)
return r;
if ((r = sys_page_map(0, addr, 0, addr, PTE_P | PTE_U | PTE_COW)) < 0)
return r;
}
else if ((r = sys_page_map(0, addr, envid, addr, PTE_P | PTE_U)) < 0)
return r;
// panic("duppage not implemented");
return 0;
}
lib/spawn.c
::copy_shared_pages(envid_t child)
:
复制共享页的映射到子进程中
static int
copy_shared_pages(envid_t child)
{
// LAB 5: Your code here.
int r, pn;
struct Env *e;
//遍历进程页表所有PTE,将设置了PTE_SHARE的页映射到子进程中
for (pn = PGNUM(UTEXT); pn < PGNUM(USTACKTOP); ++pn)
{
if ((uvpd[pn >> 10] & PTE_P) && (uvpt[pn] & PTE_P))
{
if (uvpt[pn] & PTE_SHARE)
{
if ((r = sys_page_map(0, (void *)(pn * PGSIZE),
child, (void *)(pn * PGSIZE),
uvpt[pn] & PTE_SYSCALL)) < 0)
return r;
}
}
}
return 0;
}
The keyboard interface
键盘中断和串口中断的实现,从而shell可正常工作。
在kern/trap.c
中调用kbd_intr()
来处理IRQ_OFFSET + IRQ_KBD
,调用serial_intr()
来处理IRQ_OFFSET + IRQ_SERIAL
:
// Handle keyboard and serial interrupts.
// LAB 5: Your code here.
if (tf->tf_trapno == IRQ_OFFSET + IRQ_KBD)
{
kbd_intr();
return;
}
if (tf->tf_trapno == IRQ_OFFSET + IRQ_SERIAL)
{
serial_intr();
return;
}
The Shell
运行user/icode.c
,它会调用spawnl
函数来运行init
进程,将console当作输入输出文件描述符,然后运行spawn
创建出shell
目前,shell不支持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();
}
// Open 't' for reading as file descriptor 0
// (which environments use as standard input).
// We can't open a file onto a particular descriptor,
// so open the file as 'fd',
// then check whether 'fd' is 0.
// If not, dup 'fd' onto file descriptor 0,
// then close the original 'fd'.
// LAB 5: Your code here.
if ((fd = open(t, O_RDONLY)) < 0)
{
fprintf(2, "file %s is no exist\n", t);
exit();
}
if (fd != 0)
{
dup(fd, 0);
close(fd);
}
// panic("< redirection not implemented");
break;
参考文章:
MIT-6.828-JOS-lab5:File system, Spawn and Shell - gatsby123 - 博客园 (cnblogs.com)
MIT6.828 Lab 5: File system, Spawn and Shell_bysui的博客-CSDN博客