Lab8 report
练习0:填写已有实验
用meld对比修改了以下文件:
kdebug.c
trap.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
proc.c
default_sched.c
monitor.c
check_sync.c
在练习0中暂时无需对以上文件做修改。
练习1: 完成读文件操作的实现(需要编码)
实验思路
打开文件时,调用 safe_open()
函数,然后依次调用open->sys_open->syscall
,从而引发系统调用然后进入内核态,然后会由sys_open
内核函数处理系统调用,进一步调用到内核函数sysfile_open
,然后将存有文件路径的字符串拷贝到内核空间中的字符串path中,并进入到vfs的处理流程完成进一步的打开文件操作中。
系统会分配一个file变量,这个变量其实是current->fs_struct->filemap[]
中的一个元素,内容为空,但是分配完了之后还不能找到对应对应的文件结点。所以系统在该层调用了vfs_open
函数通过调用vfs_lookup
找到path对应文件的inode,然后调用vfs_open
函数打开文件。然后层层返回,通过执行语句file->node=node;
,就把当前进程的current->fs_struct->filemap[fd]
(即file所指变量)的成员变量node指针指向了代表文件的索引节点node。这时返回fd。最后完成打开文件的操作。
实现过程
sfs_inode.c
sfs_io_nolock()函数
static int
sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) {
······
//读取第一部分的数据
if ((blkoff = offset % SFS_BLKSIZE) != 0) {
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
if ((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0) {
goto out;
}
alen += size;
if (nblks == 0) {
goto out;
}
buf += size, blkno ++, nblks --;
}
//读取中间部分的数据,将其分为size大学的块,然后一次读一块直至读完
size = SFS_BLKSIZE;
while (nblks != 0) {
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0) {
goto out;
}
if ((ret = sfs_block_op(sfs, buf, ino, 1)) != 0) {
goto out;
}
alen += size, buf += size, blkno ++, nblks --;
}
//读取第三部分的数据
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;
}
······
}
思考题
在实验报告中给出设计实现”UNIX的PIPE机制“的概要设方案,鼓励给出详细设计方案
管道可以看作是由内核管理的一个缓冲区,一端连接进程A的输出,另一端连接进程B的输入。进程A会向管道中放入信息,而进程B会取出被放入管道的信息。当管道中没有信息,进程B会等待,直到进程A放入信息。当管道被放满信息的时候,进程A会等待,直到进程B取出信息。当两个进程都结束的时候,管道也自动消失。
管道基于fork机制建立,从而让两个进程可以连接到同一个PIPE上。
最开始的时候,管道两端都连接在同一个进程A上,当fork复制进程A得到进程B的时候,会将这两个连接也复制到新的进程B上。随后,每个进程关闭自己不需要的一个连接。例如进程A关闭向PIPE输入的链接,进程B关闭接收PIPE输出的连接。 从而二者即可通过该PIPE进行通信。
在UNIX中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。具体来说,即有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。
当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。
写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:
- 内存中有足够的空间可容纳所有要写入的数据;
- 内存没有被读程序锁定。
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。
管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。
练习2: 完成基于文件系统的执行程序机制的实现(需要编码)
实验思路
load_icode主要是将文件加载到内存中执行,根据注释的提示分为了七个步骤:
- 建立内存管理器
- 建立页目录
- 将文件逐个段加载到内存中,这里要注意设置虚拟地址与物理地址之间的映射
- 建立相应的虚拟内存映射表
- 建立并初始化用户堆栈
- 处理用户栈中传入的参数
- 最后很关键的一步是设置用户进程的中断帧
此外,一旦发生错误还需要进行错误处理。
实现过程
proc.c
alloc_proc()函数
static struct proc_struct *
alloc_proc(void) {
······
proc->filesp = NULL;//初始化fs中的进程控制结构
}
return proc;
}
do_fork()函数
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
······
if (setup_kstack(proc) != 0) {
goto bad_fork_cleanup_proc;
}
/*if (copy_mm(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}*/
if (copy_files(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}
if (copy_mm(clone_flags, proc) != 0) {
goto bad_fork_cleanup_fs;
}
copy_thread(proc, stack, tf);
}
load_icode()函数
static int
load_icode(int fd, int argc, char **kargv) {
assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);
//(1)建立内存管理器
if (current->mm != NULL) {
panic("load_icode: current->mm must be empty.\n");
}
//(2)建立页目录
int ret = -E_NO_MEM;// E_NO_MEM代表因为存储设备产生的请求错误
struct mm_struct *mm;// 建立内存管理器
if ((mm = mm_create()) == NULL) {
goto bad_mm;
}
if (setup_pgdir(mm) != 0) {
goto bad_pgdir_cleanup_mm;
}
struct Page *page;
//(3)从文件加载程序到内存
struct elfhdr __elf, *elf = &__elf;
if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) {
goto bad_elf_cleanup_pgdir;
}
if (elf->e_magic != ELF_MAGIC) {
ret = -E_INVAL_ELF;
goto bad_elf_cleanup_pgdir;
}
struct proghdr __ph, *ph = &__ph;
uint32_t vm_flags, perm, phnum;
for (phnum = 0; phnum < elf->e_phnum; phnum ++) {
off_t phoff = elf->e_phoff + sizeof(struct proghdr) * phnum;//循环读取程序的每个段的头部
if ((ret = load_icode_read(fd, ph, sizeof(struct proghdr), phoff)) != 0) {
goto bad_cleanup_mmap;
}
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 ;
}
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;
}
off_t offset = 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) {
ret = -E_NO_MEM;
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, offset)) != 0) {
goto bad_cleanup_mmap;
}
start += size, offset += size;
}
//建立BSS段
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) {
ret = -E_NO_MEM;
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;
}
}
sysfile_close(fd);
//(4)建立相应的虚拟内存映射表
vm_flags = VM_READ | VM_WRITE | VM_STACK;//建立虚拟地址与物理地址之间的映射
if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) {
goto bad_cleanup_mmap;
}
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL);
assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL);
//(5)设置用户栈
mm_count_inc(mm);
current->mm = mm;
current->cr3 = PADDR(mm->pgdir);
lcr3(PADDR(mm->pgdir));
//(6)处理用户栈中传入的参数,其中argc对应参数个数,uargv[]对应参数的具体内容的地址
uint32_t argv_size=0, i;
for (i = 0; i < argc; i ++) {
argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
}
uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long)+1)*sizeof(long);
char** uargv=(char **)(stacktop - argc * sizeof(char *));
argv_size = 0;
for (i = 0; i < argc; i ++) {
uargv[i] = strcpy((char *)(stacktop + argv_size ), kargv[i]);
argv_size += strnlen(kargv[i],EXEC_MAX_ARG_LEN + 1)+1;
}
stacktop = (uintptr_t)uargv - sizeof(int);
*(int *)stacktop = argc;
//(7)设置进程的中断帧
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 = stacktop;
tf->tf_eip = elf->e_entry;
tf->tf_eflags = FL_IF;
ret = 0;
//(8)错误处理部分
out:
return ret;
bad_cleanup_mmap:
exit_mmap(mm);
bad_elf_cleanup_pgdir:
put_pgdir(mm);
bad_pgdir_cleanup_mm:
mm_destroy(mm);
bad_mm:
goto out;
}
实验结果
ls
@ is [directory] 2(hlinks) 23(blocks) 5888(bytes) : @'.'
[d] 2(h) 23(b) 5888(s) .
[d] 2(h) 23(b) 5888(s) ..
[-] 1(h) 10(b) 40383(s) softint
[-] 1(h) 11(b) 44571(s) priority
[-] 1(h) 11(b) 44584(s) matrix
[-] 1(h) 10(b) 40391(s) faultreadkernel
[-] 1(h) 10(b) 40381(s) hello
[-] 1(h) 10(b) 40382(s) badarg
[-] 1(h) 10(b) 40404(s) sleep
[-] 1(h) 11(b) 44694(s) sh
[-] 1(h) 10(b) 40380(s) spin
[-] 1(h) 11(b) 44640(s) ls
[-] 1(h) 10(b) 40386(s) badsegment
[-] 1(h) 10(b) 40435(s) forktree
[-] 1(h) 10(b) 40410(s) forktest
[-] 1(h) 10(b) 40516(s) waitkill
[-] 1(h) 10(b) 40404(s) divzero
[-] 1(h) 10(b) 40381(s) pgdir
[-] 1(h) 10(b) 40385(s) sleepkill
[-] 1(h) 10(b) 40408(s) testbss
[-] 1(h) 10(b) 40381(s) yield
[-] 1(h) 10(b) 40406(s) exit
[-] 1(h) 10(b) 40385(s) faultread
lsdir: step 4
$ hello
Hello world!!.
I am process 14.
hello pass.
思考题
在实验报告中给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案,鼓励给出详细设计方案
- 硬链接机制的设计实现:
vfs中预留了硬链接的实现接口int vfs_link(char *old_path, char *new_path);
。在实现硬链接机制,创建硬链接link时,为new_path创建对应的file,并把其inode指向old_path所对应的inode,inode的引用计数加1。在unlink时将引用计数减去1即可。 - 软链接机制的设计实现:
vfs中预留了软链接的实现接口int vfs_symlink(char *old_path, char *new_path);
。在实现软链接机制,创建软连接link时,创建一个新的文件(inode不同),并把old_path的内容存放到文件的内容中去,给该文件保存在磁盘上时disk_inode类型为SFS_TYPE_LINK,再完善对于该类型inode的操作即可。unlink时类似于删除一个普通的文件。
和实验参考答案的对比分析。
在lab8_result中make grade之后发现得分是0,sfs_inode.c函数基本相同;而对比proc.c中的函数,发现有以下几处区别
init_main()函数
参考答案中在cprintf("init check memory pass.\n");
之前多了如下两行:
assert(nr_free_pages_store == nr_free_pages());
assert(kernel_allocated_store == kallocated());
经过验证,这两句会导致一些用户进程无法正常输出"init check memory pass."导致result错误。
函数的命名
我自己的lab7中的copy_files()和put_files()函数在参考答案中分别被命名为copy_fs()和put_fs()函数。
另外,我还猜想参考答案没有满分的原因是,前几次实验的代码在“迁移”的过程中可能产生了错误或者没有完全“迁移”过来。
知识点的分析
考察了基本的文件系统系统调用的实现方法,和基于索引节点组织方式的Simple FS文件系统的设计与实现,以及文件系统抽象层-VFS的设计与实现。前者的考察是基于分析理解,后两者则是需要具体实现。
知识点的补充
对于知识点的考察挺全面的,没有需要补充的地方。
网友评论