lab8
练习0:填写已有实验
proc.c
default_pmm.c
pmm.c
swap_fifo.c
vmm.c
trap.c
sche.c
monitor.
check_sync.c
练习1: 完成读文件操作的实现(需要编码)
首先了解打开文件的处理流程,然后参考本实验后续的文件读写操作的过程分析,编写在sfs_inode.c中sfs_io_nolock读文件中数据的实现代码。
请在实验报告中给出设计实现”UNIX的PIPE机制“的概要设方案,鼓励给出详细设计方案
当进行文件读取/写入操作时,最终uCore都会执行到sfs_io_nolock函数。在该函数中,我们要完成对设备上基础块数据的读取与写入。
在进行读取/写入前,我们需要先将数据与基础块对齐,以便于使用sfs_block_op函数来操作基础块,提高读取/写入效率。
但一旦将数据对齐后会存在一个问题:
- 待操作数据的前一小部分有可能在最前的一个基础块的末尾位置
- 待操作数据的后一小部分有可能在最后的一个基础块的起始位置
我们需要分别对这第一和最后这两个位置的基础块进行读写/写入,因为这两个位置的基础块所涉及到的数据都是部分的。而中间的数据由于已经对齐好基础块了,所以可以直接调用sfs_block_op来读取/写入数据。以下是相关操作的实现:
/*
* sfs_io_nolock - Rd/Wr a file contentfrom 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);
// calculate the Rd/Wr end position
// 计算缓冲区读取/写入的终止位置
off_t endpos = offset + *alenp, blkoff;
*alenp = 0;
if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos)
return -E_INVAL;
// 如果偏移与终止位置相同,及欲读取/写入0字节的数据
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;
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
*/
// 对齐偏移。如果偏移没有对齐第一个基础块,则多读取/写入第一个基础块的末尾数据
if ((blkoff = offset % SFS_BLKSIZE) != 0) {
size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset);
// 获取第一个基础块所对应的block的编号`ino`
if ((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0)
goto out;
// 通过上一步取出的`ino`,读取/写入一部分第一个基础块的末尾数据
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 = SFS_BLKSIZE;
while (nblks != 0) {
// 获取inode对应的基础块编号
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;
}
out:
*alenp = alen;
if (offset + alen > sin->din->size) {
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}
-
给出设计实现”UNIX的PIPE机制“的概要设方案
- PIPE管道机制是进程间通信的较为重要的一种方式。在VFS中,最简单的做法就是在磁盘上建立一块pipe缓冲文件
pipe_tmp。之后,当打开了pipe_tmp文件的某进程fork出子进程后,父子进程就可以通过读写同一文件来实现进程间通信。 - 但实际上,上述的进程间通信是十分低效的,因为需要调用多个函数来完成文件读写,同时硬盘的读写速率也远远小于内存。由于用户与实际的文件系统间由虚拟文件系统VFS调控,因此我们可以在内存中根据文件系统规范,建立虚拟pipe缓冲区域文件来代替磁盘上的缓冲文件,这样便可大大提高通信速率。
- PIPE管道机制是进程间通信的较为重要的一种方式。在VFS中,最简单的做法就是在磁盘上建立一块pipe缓冲文件
练习2: 完成基于文件系统的执行程序机制的实现(需要编码)
改写proc.c中的load_icode函数和其他相关函数,实现基于文件系统的执行程序机制。执行:make qemu。如果能看看到sh用户程序的执行界面,则基本成功了。如果在sh用户界面上可以执行”ls”,”hello”等其他放置在sfs文件系统中的其他执行程序,则可以认为本实验基本成功。
请在实验报告中给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案,鼓励给出详细设计方案
基于文件系统的执行程序机制,有几部分地方需要添加代码,分别是alloc_proc、do_fork、load_icode三个函数。
1、alloc_proc
-
这个函数需要添加的内容最少,只需多补充一个
struct files_struct *filesp的初始化即可 -
修改后的源码如下
COPYstatic struct proc_struct * alloc_proc(void) { struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); if (proc != NULL) { // ... //LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ... // LAB8 添加一个filesp指针的初始化 proc->filesp = NULL; } return proc; }
2、do_fork
-
fork机制在原先lab7的基础上,多了
file_struct结构的复制操作与执行失败时的重置操作。这两部操作分别需要调用
copy_files和put_files函数 -
修改后的源码如下
COPYint do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { int ret = -E_NO_FREE_PROC; struct proc_struct *proc; if (nr_process >= MAX_PROCESS) { goto fork_out; } ret = -E_NO_MEM; if ((proc = alloc_proc()) == NULL) { goto fork_out; } proc->parent = current; assert(current->wait_state == 0); if (setup_kstack(proc) != 0) { goto bad_fork_cleanup_proc; } //LAB8:EXERCISE2 YOUR CODE HINT:how to copy the fs in parent's proc_struct? // LAB8 将当前进程的fs复制到fork出的进程中 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); bool intr_flag; local_intr_save(intr_flag); { proc->pid = get_pid(); hash_proc(proc); set_links(proc); } local_intr_restore(intr_flag); wakeup_proc(proc); ret = proc->pid; fork_out: return ret; // LAB8 如果复制失败,则需要重置原先的操作 bad_fork_cleanup_fs: //for LAB8 put_files(proc); bad_fork_cleanup_kstack: put_kstack(proc); bad_fork_cleanup_proc: kfree(proc); goto fork_out; }
3、load_icode
该函数可以在lab7原先的基础上进行修改,不需要从0开发。
-
原先lab7源码中,读取可执行文件是直接读取内存的,但在这里需要使用函数
load_icode_read来从文件系统中读取ELF header以及各个段的数据。 -
原先Lab7的
load_icode函数中并没有对execve所执行的程序传入参数,而我们需要在lab8中补充这个实现。 -
补充后的源码如下
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"); } int ret = -E_NO_MEM; // 创建proc的内存管理结构 struct mm_struct *mm; if ((mm = mm_create()) == NULL) { goto bad_mm; } if (setup_pgdir(mm) != 0) { goto bad_pgdir_cleanup_mm; } //(2)建立页目录 struct Page *page; //############## LAB8 这里要从文件中读取ELF header,而不是Lab7中的内存了############# struct elfhdr __elf, *elf = &__elf; if ((ret = load_icode_read(fd, elf, sizeof(struct elfhdr), 0)) != 0) { goto bad_elf_cleanup_pgdir; } // 判断读取入的elf header是否正确 if (elf->e_magic != ELF_MAGIC) { ret = -E_INVAL_ELF; goto bad_elf_cleanup_pgdir; } //(3)从文件加载程序到内存 // 根据每一段的大小和基地址来分配不同的内存空间 struct proghdr __ph, *ph = &__ph; uint32_t vm_flags, perm, phnum; for (phnum = 0; phnum < elf->e_phnum; phnum ++) { //########### LAB8 从文件特定偏移处读取每个段的详细信息(包括大小、基地址等等)############# 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; } //################## LAB8 读取elf对应段内的数据并写入至该内存中######################################## if ((ret = load_icode_read(fd, page2kva(page) + off, size, offset)) != 0) { goto bad_cleanup_mmap; } start += size, offset += size; } end = ph->p_va + ph->p_memsz; // 对于段中当前页中剩余的空间(复制elf数据后剩下的空间),将其置为0 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)); } // 对于段中剩余页中的空间(复制elf数据后的多余页面),将其置为0 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; } } // 关闭读取的ELF 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); // 设置CR3页表相关寄存器 current->mm = mm; current->cr3 = PADDR(mm->pgdir); lcr3(PADDR(mm->pgdir)); //(6)处理用户栈中传入的参数,其中argc对应参数个数,uargv[]对应参数的具体内容的地址 // #######################LAB8 设置execve所启动的程序参数############################ 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; } -
给出设计实现基于”UNIX的硬链接和软链接机制“的概要设方案
-
SFS中已经预留出硬链接/软链接的相关定义(没有实现)
COPY/* * VFS layer high-level operations on pathnames * * vfs_link - Create a hard link to a file. * vfs_symlink - Create a symlink PATH containing contents CONTENTS. * vfs_unlink - Delete a file/directory. * */ int vfs_link(char *old_path, char *new_path); int vfs_symlink(char *old_path, char *new_path); int vfs_unlink(char *path); -
硬链接机制的实现
- 创建硬链接时,仍然为
new_path建立一个sfs_disk_entry结构,但该结构的内部ino成员指向old_path的磁盘索引结点,并使该磁盘索引节点的nlinks引用计数成员加一即可。 - 删除硬链接时,令对应磁盘结点
sfs_disk_inode中的nlinks减一,同时删除硬链接的sfs_disk_entry结构即可。
- 创建硬链接时,仍然为
-
软链接的实现
- 与创建硬链接不同,创建软链接时要多建立一个
sfs_disk_inode结构(即建立一个全新的文件)。之后,将old_path写入该文件中,并标注sfs_disk_inode的type为SFS_TYPE_LINK即可。 - 删除软链接与删除文件的操作没有区别,直接将对应的
sfs_disk_entry和sfs_disk_inode结构删除即可。
- 与创建硬链接不同,创建软链接时要多建立一个
-
重要知识点
1. 文件系统和文件
- 文件系统是操作系统中管理持久性数据的子系统,提供数据存储和访问功能
- 组织、检索、读写访问数据
- 大多数计算机系统都有文件系统
- Google也是一个文件系统
- 文件是具有符号名,由字节序列构成的数据项集合
- 文件系统的基本数据单位
- 文件名是文件的标识符号
- 文件系统的功能
- 分配文件磁盘空间
- 管理文件块(位置和顺序)
- 管理空闲空间(位置)
- 分配算法(策略)
- 管理文件集合
- 定位:文件及其内容
- 命名:通过名字找到文件
- 文件系统结构:文件组织方式
- 数据可靠和安全
- 安全:多层次保护数据
- 可靠
- 持久保存文件
- 避免系统崩溃、媒体错误、攻击等
- 分配文件磁盘空间
- 文件属性
- 名称、类型、大小、位置、保护、创建者、创建时间、最近修改时间
- 文件头:文件系统元数据中的文件信息
- 文件属性
- 文件存储位置和顺序
2. 文件缓存和打开文件
-
数据块缓存
- 数据块按需读入内存
- 提供read()操作
- 预读:预先读取后面的数据块
- 数据块使用后被缓存
- 假设数据将会再次用到
- 写操作可能被缓存和延迟写入
- 两种数据块缓存方式
- 数据块缓存
- 页缓存:统一缓存数据块和内存页
- 数据块按需读入内存
-
页缓存
- 虚拟页式存储:在虚拟地址空间中虚拟页面可映射到本地外存文件中
- 文件数据块的页缓存
- 在虚拟内存中文件数据块被映射成页
- 文件的读写操作被转换成对内存的访问
- 可能导致缺页或设置为脏页
- 存在的问题:页置换算法需要协调虚拟存储和页缓存间的页面数
-
文件系统中打开文件的数据结构
- 文件描述符
- 每个被打开的文件都有一个文件描述符
- 文件状态信息:目录项、当前文件指针、文件操作设置等
- 打开文件表
- 每个进程都有一个进程打开文件表
- 一个系统级的打开文件表
- 有文件被打开时,文件卷就不能被卸载
- 文件描述符
-
打开文件锁
一些文件系统提供文件锁,用于协调多进程的文件访问
- 强制——根据锁保持情况和访问需求确定是否拒绝访问
- 劝告——进程可以查找锁的状态来决定怎么处理

4706

被折叠的 条评论
为什么被折叠?



