使用meld可以简单地将前几个lab的代码填入lab8中,其他实验完成的部分基本直接合并即可,部分代码需要做出修改但要结合练习1和练习2的内容,在练习1和练习2中指出
以任一文件作为测试用例,ucore的执行顺序如下,其文件系统架构主要由四部分组成
文件系统测试用例
user/*.c
1.通用文件系统访问接口层
write user/libs/file.c
sys_write user/libs/syscall.c
syscall user/libs/syscall.c
sys_write kern/syscall/syscall.c
2.文件系统抽象层Virtual File System
sysfile_write kern/fs/sysfile.c
file_write kern/fs/file.c
vop_write kern/fs/vfs/inode.h
3.文件系统Simple File System
sfs_write kern/fs/sfs/sfs_inode.c
sfs_wbuf kern/fs/sfs/sfs_io.c
4.外部I/O设备接口层
dop_io kern/fs/devs/dev.h
disk0_io kern/fs/devs/dev_disk0.c
硬件驱动
ide_write_secs kern/driver/ide.c
在ucore系统中文件系统架构包含四类主要的数据结构,分别是:
文件系统抽象层是把不同文件系统的对外共性接口提取出来,形成一个函数指针数组,这样,通用文件系统访问接口层只需访问文件系统抽象层,而不需关心具体文件系统的实现细节和接口。
进程在内核中直接访问的文件接口数据结构及文件相关信息数据结构定义在kern/fs/fs.h
和kern/fs/file.h
中
struct proc_struct{ //进程控制块
...
struct files_struct *filesp; //文件接口
};
struct files_struct {
struct inode *pwd; //进程当前执行目录的内存inode指针
struct file *fd_array; //进程打开文件的集合fd_array[]即filemap
int files_count; //打开文件的个数
semaphore_t files_sem; //确保互斥访问
};
struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status; //文件的当前状态
bool readable; //是否可读
bool writable; //是否可写
int fd; //文件在fd_array[]即filemap中的索引
off_t pos; //访问文件的当前位置
struct inode *node; //该文件对应的inode指针
int open_count; //打开此文件的次数
};
当创建一个进程后,子进程将会初始化或复制父进程的files_struct
,因此在练习0中需要修改do_fork
的部分代码,加上如下代码用以复制文件信息
int
do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
...
if(copy_files(clone_flags, proc) !=0){ //复制父进程的file_struct,若出错进入错误处理
goto bad_fork_cleanup_fs;
}
...
bad_fork_cleanup_fs:
put_files(proc);
...
}
VFS中的另一类重要数据结构是inode
,位于内存的索引节点,负责把不同文件系统的特定索引节点信息统一封装,并定义了能够操作此节点的函数,inode
定义在kern/fs/vfs/inode.h
struct inode {
union { //包含不同文件系统特定inode信息的union成员变量
struct device __device_info; //设备文件系统内存inode信息
struct sfs_inode __sfs_inode_info; //SFS文件系统内存inode信息
} in_info;
enum {
inode_type_device_info = 0x1234,
inode_type_sfs_inode_info,
} in_type; //此inode所属文件系统类型
int ref_count; //此inode的引用计数
int open_count; //打开此inode对应文件的个数
struct fs *in_fs; //inode所在的抽象的文件系统,包含访问文件系统的函数指针
const struct inode_ops *in_ops; //抽象的inode操作,包含访问inode的函数指针
};
struct fs {
union {
struct sfs_fs __sfs_info;
} fs_info; //具体文件系统信息,这里只有SFS
enum {
fs_type_sfs_info,
} fs_type; //文件系统类型
int (*fs_sync)(struct fs *fs); //写回修改
struct inode *(*fs_get_root)(struct fs *fs); //返回文件系统的root inode
int (*fs_unmount)(struct fs *fs); //解除挂载的文件系统
void (*fs_cleanup)(struct fs *fs); //清除文件系统
};
struct inode_ops {
unsigned long vop_magic;
int (*vop_open)(struct inode *node, uint32_t open_flags);
int (*vop_close)(struct inode *node);
...
};
(1)SFS文件系统的基本布局为
| superblock | root-dir inode | freemap | inode/file data/dir data blocks |
struct sfs_fs {
struct sfs_super super; //超级块superblock
struct device *dev; //挂载的设备
struct bitmap *freemap; //空闲区域freemap
bool super_dirty; //修改标记
void *sfs_buffer;
semaphore_t fs_sem;
semaphore_t io_sem;
semaphore_t mutex_sem;
list_entry_t inode_list;
list_entry_t *hash_list;
};
struct sfs_super {
uint32_t magic; //魔数magic = SFS_MAGIC = 0x2F8DBE2A,检测是否合法的SFS img
uint32_t blocks; //SFS中所有block的数量
uint32_t unused_blocks; //SFS中所有未被使用的block的数量
char info[SFS_MAX_INFO_LEN + 1]; //info = "simple file system"
};
kern/fs/sfs/bitmap.[ch]
(2)磁盘索引节点
SFS中的磁盘索引节点代表了一个实际位于磁盘上的文件。
如果inode表示的是文件,则成员变量direct[]
直接指向了保存文件内容数据的数据块索引值。indirect间接指向了保存文件内容数据的数据块,indirect
指向的是间接数据块(indirect block),此数据块实际存放的全部是数据块索引,这些数据块索引指向的数据块才被用来存放文件内容数据
struct sfs_disk_inode {
uint32_t size; //文件大小
uint16_t type; //文件类型
uint16_t nlinks; //硬链接数
uint32_t blocks; //block个数
uint32_t direct[SFS_NDIRECT]; //直接数据块索引
uint32_t indirect; //一级间接数据块索引,为0时表示不使用一级间接数据块
};
struct sfs_disk_entry { //若inode是目录,索引指向的数据块为sfs_disk_entry
uint32_t ino; //索引节点所占数据块的索引值
char name[SFS_MAX_FNAME_LEN + 1]; //文件名
};
(3)内存索引节点
内存inode是在打开一个文件后才创建的,包含有磁盘索引节点,如果关机则相关信息都会消失。而硬盘inode的内容是保存在硬盘中的,只是在进程需要时才被读入到内存中,用于访问文件或目录的具体内容数据。
struct sfs_inode {
struct sfs_disk_inode *din; //对应的磁盘索引节点
uint32_t ino; //inode编号
bool dirty; //修改标记
int reclaim_count;
semaphore_t sem; //对磁盘索引节点的互斥访问
list_entry_t inode_link;
list_entry_t hash_link;
};
整体流程即ucore文件系统架构中描述的过程,详细函数调用处理如下
(1)通用文件访问接口层的处理
调用如下用户态函数: open->sys_open->syscall
,从而引起系统调用进入到内核态。
进入内核态后,通过中断处理例程,会调用sys_open->sysfile_open
内核函数。到了这里,需要把位于用户空间的字符串/test/testfile
拷贝到内核空间中的字符串path
中,并进入到文件系统抽象层的处理流程完成进一步的打开文件操作中。
(2)文件系统抽象层VFS的处理
调用file_open
给打开的文件分配一个struct file
,这个元素在struct file *fd_array
中的索引值就是要返回给用户进程的文件描述符fd
。
调用vfs_open
,进一步调用vfs_lookup
找到path
对应的基于inode数据结构的VFS索引节点,再调用vop_open
打开文件。
(3)SFS文件系统层的处理
在(2)中调用vfs_lookup
时会调用vop_lookup
来找到具体的文件。
vop_lookup
在ucore中实际指的是sfs_lookup
(定义在sfs_inode.c
中),sfs_lookup
以/
为分割符,从左至右逐一分解path
获得各个子目录和最终文件对应的inode节点。在本例中是分解出test
子目录,并调用sfs_lookup_once
函数获得子目录对应的inode节点subnode,然后循环进一步调用sfs_lookup_once
查找以test
子目录下的文件testfile1
所对应的inode节点。
sfs_lookup_once
将调用sfs_dirent_search_nolock
函数来查找与路径名匹配的目录项,如果找到目录项,则根据目录项中记录的inode所处的数据块索引值找到路径名对应的SFS磁盘inode,并读入SFS磁盘inode对的内容,创建SFS内存inode。
/*
* sfs_lookup - Parse path relative to the passed directory
* DIR, and hand back the inode for the file it
* refers to.
*/
static int
sfs_lookup(struct inode *node, char *path, struct inode **node_store) {
struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs);
assert(*path != '\0' && *path != '/');
vop_ref_inc(node);
struct sfs_inode *sin = vop_info(node, sfs_inode);
if (sin->din->type != SFS_TYPE_DIR) {
vop_ref_dec(node);
return -E_NOTDIR;
}
struct inode *subnode;
int ret = sfs_lookup_once(sfs, sin, path, &subnode, NULL);
vop_ref_dec(node);
if (ret != 0) {
return ret;
}
*node_store = subnode;
return 0;
}
/*
* sfs_lookup_once - find inode corresponding the file name in DIR's sin inode
* @sfs: sfs file system
* @sin: DIR sfs inode in memory
* @name: the file name in DIR
* @node_store: the inode corresponding the file name in DIR
* @slot: the logical index of file entry
*/
static int
sfs_lookup_once(struct sfs_fs *sfs, struct sfs_inode *sin, const char *name, struct inode **node_store, int *slot) {
int ret;
uint32_t ino;
lock_sin(sin);
{ // find the NO. of disk block and logical index of file entry
ret = sfs_dirent_search_nolock(sfs, sin, name, &ino, slot, NULL);
}
unlock_sin(sin);
if (ret == 0) {
// load the content of inode with the the NO. of disk block
ret = sfs_load_inode(sfs, node_store, ino);
}
return ret;
}
(1)通用文件访问接口层的处理
调用如下用户态函数: read->sys_read->syscall
,从而引起系统调用进入到内核态。
进入内核态后,通过中断处理例程,会调用sys_read->sysfile_read
内核函数,并进入VFS进一步操作。
(2)文件系统抽象层VFS的处理
首先检查错误并分配缓冲空间,没有问题就进行读文件操作,调用file_read
函数将文件内容读取到缓冲区中,然后再将内容拷贝到用户内存空间,循环直到读取完成。
file_read
调用fd2file
函数找到对应的file结构,并检查是否可读。调用fd_array_acquire
函数使打开这个文件的计数加1。调用vop_read
函数将文件内容读到iob
中。调整文件指针偏移量的值,使其向后移动实际读到的字节数iobuf_used(iob)
。最后调用fd_array_release
函数使打开这个文件的计数减1,若打开计数为0,则释放file。
(3)SFS文件系统层的处理
在(2)中调用file_read
时会调用vop_read
来读文件。
vop_read
在ucore中实际指的是sfs_read
(定义在sfs_inode.c
中),sfs_read
调用sfs_io
先确定文件inode对应的struct sfs_fs
和struct sfs_inode
,再进一步调用sfs_io_nolock
完成读取文件的操作,具体实现为练习1的内容,如下
/*
* 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);
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;
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
* sfs_bmap_load_nolock:将对应 sfs_inode 的第 index 个索引指向的 block 的索引值取出存到相应的指针指向的单元(ino_store),具体实现定义在sfs_inode.c中
*/
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){ //调用sfs_bmap_load_nolock获得内存索引节点第index个索引指向的block索引值存到ino中
goto out;
}
if((ret = sfs_buf_op(sfs, buf, size, ino, blkoff)) != 0){ //实际调用sfs_rbuf完成读取
goto out;
}
alen += size; //alen记录实际读取长度
if(nblks == 0){
goto out;
}
blkno ++; //blkno记录读取的块编号
nblks --; //nblks记录读取的块数量
buf += size; //更新缓冲区指针
}
size = SFS_BLKSIZE;
while(nblks){ //第二部分,循环读取对齐的整块数据
if((ret = sfs_bmap_load_nolock(sfs, sin, blkno, &ino)) != 0){
goto out;
}
if((ret = sfs_block_op(sfs, buf, ino, 1)) != 0){ //实际调用sfs_rblock一次性读取1块block长度
goto out;
}
alen += size; //第二部分连续整块读取,故size=SFS_BLKSIZE
blkno ++;
nblks --;
buf += size;
}
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; //alenp指向实际读取的长度alen
if (offset + alen > sin->din->size) { //若操作的长度超过了原节点记录的长度(写操作会发生,读不会),更新节点信息并标记需要写回
sin->din->size = offset + alen;
sin->dirty = 1;
}
return ret;
}
在lab5用户进程管理中,我们通过load_icode
实现了运行用户程序,但是当时是直接将执行文件附在ucore的末端并定义全局变量记录程序入口,具体参考lab5实验报告
http://blog.csdn.net/jasonyuchen/article/details/77018104
在lab8实现了文件系统后,执行程序的机制需要略加修改,主要体现了load_icode
函数中,对比当时实现的load_icode
,文件系统更新后,函数的参数变为了文件标识符fd
,执行程序的入口参数个数argc
及具体参数内容kargv
,具体变化及注释如下
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
*/
assert(argc >= 0 && argc <= EXEC_MAX_ARG_NUM);
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
struct Page *page;
struct elfhdr __elf, *elf = &__elf;
//(3.1) read raw data content in file and resolve elfhdr
//与lab5不同,由于不是直接获得binary文件,故这里调用load_icode_read来获得ELF header
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 ++){
//(3.2) read raw data content in file and resolve proghdr based on info in elfhdr
//与lab5不同,这里调用load_icode_read来获得program header
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;
}
//(3.3) call mm_map to build vma related to TEXT/DATA
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){
//(3.4) call pgdir_alloc_page to allocate page for TEXT/DATA, read contents in file and copy them into the new allocated pages
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;
}
//与lab5直接memcpy不同,这里调用load_icode_read来复制数据
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;
if(start < la){
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){
//(3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages
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) call mm_map to setup user stack, and put parameters into user stack
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) 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
// ----------
// |kargv[i]| <--uargv[i]
// ----------
// | ...... |
// ----------
// |kargv[0]| <--uargv[0]
// ----------
// | argc | <--stacktop
// ----------
uint32_t argv_size = 0, i;
//循环累计每个具体参数的长度,记录在argv_size中,注意/0因此要加1
for(i = 0; i < argc; i++){
argv_size += strnlen(kargv[i], EXEC_MAX_ARG_LEN + 1) + 1;
}
//更新栈顶为原栈顶减去argv_size值,argv_size需要8字节对齐并向上取整故要做如下处理
uintptr_t stacktop = USTACKTOP - (argv_size/sizeof(long) + 1) * sizeof(long);
//设置uargv指向第1个指向具体参数指针的指针
char ** uargv = (char **)(stacktop - argc * sizeof(char *));
argv_size = 0;
//循环使得uargv[i]指向第i个具体的参数
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;
}
//最后栈顶再往下一个空间存储参数个数argc
stacktop = (uintptr_t)uargv - sizeof(int);
*(int *)stacktop = 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 = stacktop;
tf->tf_eip = elf->e_entry;
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;
}
全部编码完成后,运行make qemu
可以获得输出如下,输入ls
、hello
、exit
等指令运行程序,可以看出实验成功
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) 40200(s) badarg
[-] 1(h) 10(b) 40204(s) badsegment
[-] 1(h) 10(b) 40220(s) divzero
[-] 1(h) 10(b) 40224(s) exit
[-] 1(h) 10(b) 40204(s) faultread
[-] 1(h) 10(b) 40208(s) faultreadkernel
[-] 1(h) 10(b) 40228(s) forktest
[-] 1(h) 10(b) 40252(s) forktree
[-] 1(h) 10(b) 40200(s) hello
[-] 1(h) 10(b) 40360(s) ls
[-] 1(h) 10(b) 40304(s) matrix
[-] 1(h) 10(b) 40192(s) pgdir
[-] 1(h) 10(b) 40292(s) priority
[-] 1(h) 11(b) 44508(s) sh
[-] 1(h) 10(b) 40220(s) sleep
[-] 1(h) 10(b) 40204(s) sleepkill
[-] 1(h) 10(b) 40200(s) softint
[-] 1(h) 10(b) 40196(s) spin
[-] 1(h) 10(b) 40224(s) testbss
[-] 1(h) 10(b) 40332(s) waitkill
[-] 1(h) 10(b) 40200(s) yield
lsdir: step 4
$ hello
Hello world!!.
I am process 14.
hello pass.
$ exit
I am the parent. Forking the child...
I am parent, fork a child pid 16
I am the parent, waiting now..
I am the child.
waitpid 16 ok.
exit pass.
$ sfs: cleanup: 'simple file system' (259/32509/32768)
all user-mode processes have quit.
init check memory pass.
kernel panic at kern/process/proc.c:534:
initproc exit.
至此ucore的八个实验全部完成,每次涉及到的层次多,例如lab1从底层开始启动os的过程或是lab8从顶层向下访问底层的文件,就会容易犯难。对照着参考答案、piazza上同学们和老师们的指导以及其他同学完成的实验报告,磕磕碰碰总算也是做完了全部的实验。收获很多,尤其在如何阅读大量代码、如何利用GDB调试、理解os运行等各个方面上都有很多新的收获,非常感谢两位老师及诸多助教、同学们将这门课程打造的如此精美,让我能过通过在线平台也能完成学习。
与各位踏实学习的同学共勉!