实验目的
通过完成本次实验,希望能达到以下目标
- 了解基本的文件系统系统调用的实现方法;
- 了解一个基于索引节点组织方式的Simple FS文件系统的设计与实现;
- 了解文件系统抽象层-VFS的设计与实现;
某操作系统中建立了一个基于索引节点(index node)结构的文件系统very simple file system, 简称vsfs。
vsfs的用户操作包括(以函数形式表示):
-
mkdir(“str”) - 创建一个新目录,目录名称为”str”
-
creat(“str”) - 创建一个空文件,空文件名称为“str”
-
fd=open(“str”), write(fd), close(fd) – 打开文件”str”,会返回一个整型数fd, 然后对文件写一个buffer,注意常规文件的最大size是一个data block,所以第二次写(写文件的语义是在上次写的位置后再写一个data block)会报错(文件大小满了)。或者如果data block也满了,也会报错。
-
link(“a1”,”b1”) - 对文件”a1”创建一个硬链接(hard link)文件”b1”
-
unlink(“b1”) - 对文件“b1”取消一个硬链接,如果文件的链接数为0,则删除此文件
vsfs在硬盘上的布局:
-
superblock : 可用inode数量,可用data block数量
-
inode bitmap : inode的分配图(基于bitmap)
-
inodes : inode的存储区域
-
data bitmap : data block的分配图(基于bitmap)
-
data : data block的存储区域
vsfs的关键数据结构:
-
inode数据结构:
-
inode : 包含3个fields, 用 list 表示
-
file type: f -> 常规文件:regular file, d -> 目录文件:directory
-
data block addr of file content: -1 -> file is empty
-
reference count: file/directory的引用计数,注意directory的引用计数是指在此目录中的inode的个数
注意:比如,刚创建的一个空文件inode: [f a:-1 r:1], 一个有1个硬链接的文件inode: [f a:10 r:2]
-
数据块内容结构:
-
一般文件的内容的表示:只是包含单个字符的list,即占一个data block,比如[‘a’], [‘b’] …
-
目录内容的表示: 多个两元组(name, inode_number)形成的list,比如, 根目录[(.,0) (…,0)], 或者包含了一个’f’文件的根目录[(.,0) (…,0) (f,1)] 。
注意:
-
一个目录的目录项的个数是有限的。 block.maxUsed = 32
-
data block的个数是有限的,为 fs.numData
-
inode的个数是有限的,为 fs.numInodes
完整文件系统的例子:
-
fs.ibitmap: inode bitmap 11110000
-
fs.inodes: [d a:0 r:5] [f a:1 r:1] [f a:-1 r:1] [d a:2 r:2] [] …
-
fs.dbitmap: data bitmap 11100000
-
fs.data: [(.,0) (…,0) (y,1) (z,2) (x,3)] [u] [(.,3) (…,0)] [] …
表明: 此文件系统有8个inode空间, 8个data blocks. 其中,根目录包含5个目录项,“.”,“…”,“y”,“z”,“x”。 而“y”是常规文件,并有文件内容,包含一个data block,文件内容为“u”。“z”是一个空的常规文件。“x”是一个目录文件,是空目录。
如果vsfs初始状态为:
inode bitmap 10000000
inodes [d a:0 r:2] [] [] [] [] [] [] []
data bitmap 10000000
data [(.,0) (…,0)] [] [] [] [] [] [] []
请问接下来的连续6个状态变化的对应用户操作是什么?据此回答以下6个问题
练习0:填写已有实验
本实验依赖实验1/2/3/4/5/6/7。请把你做的实验1/2/3/4/5/6/7的代码填入本实验中代码中有“LAB1”/“LAB2”/“LAB3”/“LAB4”/“LAB5”/“LAB6” /“LAB7”的注释相应部分。并确保编译通过。注意:为了能够正确执行lab8的测试应用程序,可能需对已完成的实验1/2/3/4/5/6/7的代码进行进一步改进。
将前面lab的代码合入到lab8中
需要改进的代码:
static struct proc_struct *
alloc_proc(void) {
// process's file related informaction
proc->filesp = NULL;
}
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
// copy the files_struct from current to proc
if (copy_files(clone_flags, proc) != 0) {
goto bad_fork_cleanup_fs;
}
}
练习1: 完成读文件操作的实现(需要编码)
首先了解打开文件的处理流程,然后参考本实验后续的文件读写操作的过程分析,编写在sfs_inode.c中sfs_io_nolock读文件中数据的实现代码。
sfs_io_nolock代码如下:
/*
* sfs_io_nolock - Rd/Wr a file content from offset position to offset + length disk blocks<-->buffer (in memroy)
* @sfs: sfs file system
* @sin: sfs inode in memory
* @buf: the buffer Rd/Wr
* @offset: the offset of file
* @alenp: the length need to read (is a pointer). and will RETURN the really Rd/Wr lenght
* @write: BOOL, 0 read, 1 write
*/
static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
struct sfs_disk_inode *din = sin->din;
assert(din->type != SFS_TYPE_DIR);
off_t endpos = offset + *alenp, blkoff;
*alenp = 0;
// calculate the Rd/Wr end position
if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos) {
return -E_INVAL;
}
if (offset == endpos) {
return 0;
}
if (endpos > SFS_MAX_FILE_SIZE) {
endpos = SFS_MAX_FILE_SIZE;
}
if (!write) {
if (offset >= din->size) {
return 0;
}
if (endpos > din->size) {
endpos = din->size;
}
}
int (*sfs_buf_op)(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset);
int (*sfs_block_op)(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks);
if (write) {
sfs_buf_op = sfs_wbuf, sfs_block_op = sfs_wblock;
}
else {
sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock;
}
int ret = 0;
size_t size, alen = 0;
uint32_t ino;
// offset = 10110, endpos = 50960
// SFS_BLKSIZE = 4096
// blkno = 10110 / 4096 = 2
// nblks = 50960 / 4096 - 2 = 12 - 2 = 10, 即要读10个blocks
uint32_t blkno = offset / SFS_BLKSIZE; // The NO. of Rd/Wr begin block
uint32_t nblks = endpos / SFS_BLKSIZE - blkno; // The size of Rd/Wr blocks
//LAB8:EXERCISE1 YOUR CODE HINT: call sfs_bmap_load_nolock, sfs_rbuf, sfs_rblock,etc. read different kind of blocks in file
/*
* (1) If offset isn't aligned with the first block, Rd/Wr some content from offset to the end of the first block
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
* Rd/Wr size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset)
* (2) Rd/Wr aligned blocks
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_block_op
* (3) If end position isn't aligned with the last block, Rd/Wr some content from begin to the (endpos % SFS_BLKSIZE) of the last block
* NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op
*/
// 主要分三种情况,
// 第一块没有对齐,从偏移的地方开始对齐
// 中间对齐
// 最后一块没有对齐
// 1.第一块没有对齐,先读没有对齐的这一块
if ((blkoff = (offset % SFS_BLKSIZE)) != 0) {
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
// 根据DIR的inode和inode里面block的逻辑索引找到第No.个硬盘块
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {
// if ((ret = sfs_buf_op(sfs, buf, size, blkno, offset)) != 0) {
goto out;
}
alen += size;
buf += size;
offset += size;
blkno ++;
if (nblks == 0)
goto out;
else
nblks --;
}
// 2.读中间对齐的数据
if (nblks > 0) {
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0)
goto out;
if ((ret = sfs_block_op(sfs, buf, ino, nblks)) != 0)
goto out;
buf += nblks * SFS_BLKSIZE;
alen += nblks * SFS_BLKSIZE;
blkno += nblks;
}
// 3.读最后一块没有对齐的数据
// |-512-|-512-|-512-|-512-|
// 假如 为1050
if (size = endpos % SFS_BLKSIZE != 0) {
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
if ((ret = sfs_buf_op(sfs, buf, size, ino, 0)) != 0) {
goto out;
}
alen += size;
}
out:
*alenp = alen;
if (offset + alen > sin->din->size) {
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}
请在实验报告中给出设计实现”UNIX的PIPE机制“的概要设方案,鼓励给出详细设计方案
简单的可以在在磁盘上专门建立一个临时的文件作为缓冲区,可以通过这一缓冲区进行通信,可以在硬盘上建立一个缓冲文件pipe_temp但是效率太低,实际UNIX是根据FS的规范,在内存中选择一块区域,建立了一个在内存中的虚拟文件系统。
练习2: 完成基于文件系统的执行程序机制的实现(需要编码)
改写proc.c中的load_icode函数和其他相关函数,实现基于文件系统的执行程序机制。执行:make qemu。如果能看看到sh用户程序的执行界面,则基本成功了。如果在sh用户界面上可以执行”ls”,”hello”等其他放置在sfs文件系统中的其他执行程序,则可以认为本实验基本成功。
load_icode
函数可以在lab7原先的基础上进行修改,不需要从0开发。参考:uCore实验 - Lab8 | Kiprey’s Blog
代码如下:
// load_icode - called by sys_exec-->do_execve
static int
load_icode(int fd, int argc, char **kargv) {
/* LAB8:EXERCISE2 YOUR CODE HINT:how to load the file with handler fd in to process's memory? how to setup argc/argv?
* MACROs or Functions:
* mm_create - create a mm
* setup_pgdir - setup pgdir in mm
* load_icode_read - read raw data content of program file
* mm_map - build new vma
* pgdir_alloc_page - allocate new memory for TEXT/DATA/BSS/stack parts
* lcr3 - update Page Directory Addr Register -- CR3
*/
if (current->mm != NULL)
panic("load_icode: current->mm must be empty.\n");
int ret = -E_NO_MEM;
// (1) create a new mm for current process
struct mm_struct *mm;
if ((mm = mm_create()) == NULL)
goto bad_mm;
// (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT
if (setup_pgdir(mm) != 0) {
goto bad_pgdir_cleanup_mm;
}
// (3) copy TEXT/DATA/BSS parts in binary to memory space of process
// (3.1) read raw data content in file and resolve elfhdr
struct elfhdr elf;
if ((ret = load_icode_read(fd, &elf, sizeof(struct elfhdr), 0)) != 0)
goto out;
if (elf.e_magic != ELF_MAGIC) {
ret = -E_INVAL_ELF;
goto bad_elf_cleanup_pgdir;
}
size_t ph_off = elf.e_phoff;
size_t ph_end = elf.e_phoff + elf.e_phnum * sizeof(struct proghdr);
for (; ph_off < ph_end; ph_off += sizeof(struct proghdr)) {
// (3.2) read raw data content in file and resolve proghdr based on info in elfhdr
struct proghdr ph;
if ((ret = load_icode_read(fd, &ph, sizeof(struct proghdr), ph_off)) != 0)
goto out;
if (ph.p_type != ELF_PT_LOAD) {
continue ;
}
if (ph.p_filesz > ph.p_memsz) {
ret = -E_INVAL_ELF;
goto bad_cleanup_mmap;
}
if (ph.p_filesz == 0) {
continue ;
}
// (3.3) call mm_map to build vma related to TEXT/DATA
uint32_t vm_flags = 0, perm = PTE_U;
if (ph.p_flags & ELF_PF_X) vm_flags |= VM_EXEC;
if (ph.p_flags & ELF_PF_W) vm_flags |= VM_WRITE;
if (ph.p_flags & ELF_PF_R) vm_flags |= VM_READ;
if (vm_flags & VM_WRITE) perm |= PTE_W;
if ((ret = mm_map(mm, ph.p_va, ph.p_memsz, vm_flags, NULL)) != 0) {
goto bad_cleanup_mmap;
}
// (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file and copy them into the new allocated pages
struct Page *page;
size_t from = ph.p_offset;
size_t off, size;
uintptr_t start = ph.p_va, end, la = ROUNDDOWN(start, PGSIZE);
ret = -E_NO_MEM;
end = ph.p_va + ph.p_filesz;
while (start < end) {
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL)
goto bad_cleanup_mmap;
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la)
size -= la - end;
if ((ret = load_icode_read(fd, page2kva(page) + off, size, from)) != 0)
goto out;
start += size, from += size;
}
// (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
end = ph.p_va + ph.p_memsz;
if (start < la) {
/* ph->p_memsz == ph->p_filesz */
if (start == end)
continue;
off = start + PGSIZE - la, size = PGSIZE - off;
if (end < la)
size -= la - end;
memset(page2kva(page) + off, 0, size);
start += size;
assert((end < la && start == end) || (end >= la && start == la));
}
while (start < end) {
if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) {
goto bad_cleanup_mmap;
}
off = start - la, size = PGSIZE - off, la += PGSIZE;
if (end < la) {
size -= la - end;
}
memset(page2kva(page) + off, 0, size);
start += size;
}
}
// (4) call mm_map to setup user stack, and put parameters into user stack
uintptr_t esp = USTACKTOP;
uint32_t vm_flags = VM_READ | VM_WRITE | VM_STACK;
if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0)
goto bad_cleanup_mmap;
char** buffer[EXEC_MAX_ARG_NUM];
for (int i = 0; i < argc; i++) {
struct Page *page = pgdir_alloc_page(mm->pgdir, USTACKTOP-(i+1)*PGSIZE , PTE_USER);
assert(page != NULL);
uintptr_t kva = page2kva(page);
memcpy(kva, kargv[i], EXEC_MAX_ARG_LEN + 1);
esp -= PGSIZE;
buffer[i] = esp;
}
for (int i = argc; i < argc + 4; i++)
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-(i+1)*PGSIZE , PTE_USER) != NULL);
// (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO)
mm_count_inc(mm);
current->mm = mm;
current->cr3 = PADDR(mm->pgdir);
lcr3(PADDR(mm->pgdir));
// (6) setup uargc and uargv in user stacks
esp -= sizeof(uintptr_t) * (argc + 1);
memcpy(esp, buffer, sizeof(uintptr_t) * argc);
esp -= sizeof(int);
*((int*) esp) = argc;
// (7) setup trapframe for user environment
struct trapframe *tf = current->tf;
memset(tf, 0, sizeof(struct trapframe));
tf->tf_cs = USER_CS;
tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
tf->tf_esp = esp;
tf->tf_eip = elf.e_entry;
tf->tf_eflags = tf->tf_eflags | FL_IF;
ret = 0;
out:
return ret;
// (8) if up steps failed, you should cleanup the env.
bad_cleanup_mmap:
exit_mmap(mm);
bad_elf_cleanup_pgdir:
put_pgdir(mm);
bad_pgdir_cleanup_mm:
mm_destroy(mm);
bad_mm:
goto out;
}
请在实验报告中给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案,鼓励给出详细设计方案
硬链接:新建的文件是已经存在的文件的一个别名,当原文件删除时,新建的文件仍然可以使用。与普通文件没什么不同,inode
都指向同一个文件在硬盘中的区块
软链接:也称为符号链接,新建的文件以“路径”的形式来表示另一个文件,和Windows的快捷方式十分相似,新建的软链接可以指向不存在的文件。保存了其代表的文件的绝对路径,是另外一种文件,在硬盘上有独立的区块,访问时替换自身路径。