Introduction
在这次实验中将会实现创建进程并调用库函数装载和运行磁盘上的可执行文件。同时实现在操作系统内核的console上运行shell。这些特点都需要实现文件系统,在这里我们将实现1个简单可读写的文件系统。
本次实验新增加的文件如下:
fs/fs.c 操作文件系统在磁盘上的结构。
fs/bc.c 基于用户级页错误处理机制的块缓存。
fs/ide.c 最简单基于PIO的IDE磁盘驱动。
fs/serv.c 文件系统与客户端进程进行交互的服务端代码
lib/fd.c 实现传统UNIX文件描述符接口。
lib/file.c 磁盘类型的文件系统驱动
lib/console.c console类型的文件系统驱动
lib/spawn.c spawn系统调用实现
File system preliminaries
这部分内容主要介绍了一般文件系统的结构,包括扇区、块、超级块、块位图、文件元数据和目录的概念。后面JOS实现的文件系统设计到了这些东西,需要仔细阅读。
The File System
本次实验的目的不是让你实现整个文件系统,而是只要实现关键部分。尤其是如何读取块到块缓存并写回磁盘;映射文件偏移到磁盘块;实现文件的读取、写入和打开IPC接口调用。
Disk Access
JOS的文件系统需要能够访问磁盘,但是我们现在还没在内核实现访问磁盘。为了简化,这里我们抛弃传统单内核操作系统将磁盘驱动作为系统调用的实现方式,将磁盘驱动作为用户进程访问磁盘来实现。
这将很简单,通过轮询而不是中断来实现在用户空间进行磁盘访问。在x86处理器中可以通过设置EFLAGS寄存器中的IOPL位来允许用户态进程执行IO指令比如in和out。
Exercise 1:
i386_init函数中会创建1个文件系统进程,通过传递ENV_TYPE_FS标志给env_create函数,需要在该函数中允许文件系统进程执行IO指令。
回答:
在env_create函数中修改进程的eflag值。
if (type == ENV_TYPE_FS)
e->env_tf.tf_eflags |= FL_IOPL_MASK;
Question 1:
在进程切换时如何保证IO特权设置被保存和重载?
回答:
在进程切换时调用了env.pop_tf函数,其中进行了寄存器的恢复,在iret指令中恢复了eip,cs,eflags等寄存器。
The Block Cache
在JOS中,实现了1个简单的磁盘块缓存机制。该机制支持的磁盘大小最大为3GB,可以使用类似Lab 4中实现fork的COW页面机制。
其实现机制如下:
1、用文件系统服务进程的虚拟地址空间(0x10000000 (DISKMAP)到0xD0000000 (DISKMAP+DISKMAX))对应到磁盘的地址空间(3GB)。
2、初始文件系统服务进程不映射页面,如果要访问1个磁盘的地址空间,则发生页错误。
3、在页错误处理程序中,在内存中申请一个块的空间映射到相应的文件系统虚拟地址,然后去实际的物理磁盘上读取这个区域的数据到该内存区域,最后恢复文件系统服务进程。
Exercise2:
实现bc_pgfault和flush_block函数,其中bc_pgfault是页错误处理程序,作用是从磁盘上装载页。
回答:
主要是实现磁盘块缓冲的页面处理和写回部分,主要用到跟磁盘直接交互的IDE驱动函数。
int ide_read(uint32_t secno, void *dst, size_t nsecs)
int ide_write(uint32_t secno, void *dst, size_t nsecs)
secno对应IDE磁盘上的扇区编号,dst为当前文件系统服务程序空间中的对应地址,nsecs为读写的扇区数。
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.
addr = ROUNDDOWN(addr, PGSIZE);
if ((r = sys_page_alloc(0, addr, PTE_U | PTE_P | PTE_W)) < 0)
panic("in bc_pgfault, sys_page_alloc: %e", r);
if ((r = ide_read(blockno * BLKSECTS, addr, BLKSECTS)) < 0)
panic("in bc_pgfault, 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);
if (bitmap && block_is_free(blockno))
panic("reading free block %08x\n", blockno);
}
先根据地址计算出对应的blockno,然后检查正确性包括地址是否在映射范围内、对应的block是否存在等。
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);
int r;
addr = ROUNDDOWN(addr, PGSIZE);
if (va_is_mapped(addr) && va_is_dirty(addr)) {
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("in flush_block, sys_page_map: %e", r);
}
}
先根据地址计算对应的blockno,然后然后检查正确性,最后判断是否是脏块,如果是则写回磁盘并清除dirty位。
The Block Bitmap
在fs_init函数设置块位图之后,我们就能将位图当做位数组来对待。
Exercise 3:
以free_block为参考实现alloc_block,功能是在位图中查找1个空闲磁盘块,标记为占用并返回块序号。当你分配1个块时,为了维护文件系统的一致性,你需要快速地使用flush_block函数写回你对位图的修改。
回答:
这部分比较简单,参考free_block函数的实现即可。
int
alloc_block(