练习0
填写已有实验
本实验依赖实验1~实验7.请把已做的实验1~实验7的代码填入本实验中代码中有lab1、lab2、lab3、lab4、lab5、lab6、lab7的注释相应部分,并确保编译通过。
注意:为了能够正确执行lab8的测试应用程序,可能需对已完成的实验1~实验7的代码进一步改进
用meld软件,将已完成的lab7和lab8进行对比,大致截图如下:
需要修改的文件罗列如下:
kdebug.c、proc.c、default_pmm.c、pmm.c、swap_fifo.c、vmm.c、trap.c、sche.c、monitor.c、check_sync.c
十个文件的相关代码,无需进行其他的修改。
练习1
完成读文件操作的实现(需要编码)
首先了解打开文件的处理流程,然后参考本实验后续的文件读写操作的过程分析,编写在sfs_inode.c中sfs_io_nolock读文件中数据的实现代码。
原理
ucore模仿了UNIX的文件系统设计,ucore的文件系统架构主要由四部分组成:
- 通用文件系统访问接口层
该层提供了一个从用户空间到文件系统的标准访问接口。这一层访问接口让应用程序能够通过一个简单的接口获得ucore内核的文件系统服务。
- 文件系统抽象层
向上提供一个一致的接口给内核其他部分(文件系统相关的系统调用实现模块和其他内核功能模块)访问。向下提供一个抽象函数指针列表和数据结构来屏蔽不同文件系统的实现细节。
- Simple FS文件系统层
一个基于索引方式的简单文件系统实例。向上通过各种具体函数实现以对应文件系统抽象层提出的抽象函数。向下访问外设接口
- 外设接口层
向上提供device访问接口屏蔽不同硬件细节。向下实现访问各种具体设备驱动的接口,比如disk设备接口/串口设备接口/键盘设备接口等。
四个部分的关系
打开文件原理:
先简单分析下一些重要的数据结构
首先是file数据结构:
struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status; //访问文件的执行状态
bool readable; //文件是否可读
bool writable; //文件是否可写
int fd; //文件在filemap中的索引值
off_t pos; //访问文件的当前位置
struct inode *node;//该文件对应的内存inode指针
atomic_t open_count;//打开此文件的次数
};
接下来inode数据结构,它是位于内存的索引节点,把不同文件系统的特定索引节点信息(甚至不能算是一个索引节点)统一封装起来,避免了进程直接访问具体文件系统
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所属文件系统类型
atomic_t ref_count; //此inode的引用计数
atomic_t open_count; //打开此inode对应文件的个数
struct fs *in_fs; //抽象的文件系统,包含访问文件系统的函数指针
const struct inode_ops *in_ops; //抽象的inode操作,包含访问inode的函数指针
};
首先假定用户进程需要打开的文件已经存在在硬盘上。以user/sfs_filetest1.c
为例,首先用户进程会调用在main函数中的如下语句:
int fd1 = safe_open("/test/testfile", O_RDWR | O_TRUNC);
从字面上可以看出,如果ucore能够正常查找到这个文件,就会返回一个代表文件的文件描述符fd1,这样在接下来的读写文件过程中,就直接用这样fd1来代表就可以了。
例如某一个应用程序需要操作文件(增删读写等),首先需要通过文件系统的通用文件系统访问接口层给用户空间提供的访问接口进入文件系统内部,接着由文件系统抽象层把访问请求转发给某一具体文件系统(比如Simple FS文件系统),然后再由具体文件系统把应用程序的访问请求转化为对磁盘上的block的处理请求,并通过外设接口层交给磁盘驱动例程来完成具体的磁盘操作。
- 通用文件访问接口层的处理流程
首先进入通用文件访问接口层的处理流程,即进一步调用如下用户态函数: open->sys_open->syscall,从而引起系统调用进入到内核态。到了内核态后,通过中断处理例程,会调用到sys_open内核函数,并进一步调用sysfile_open内核函数。到了这里,需要把位于用户空间的字符串”/test/testfile”拷贝到内核空间中的字符串path中,并进入到文件系统抽象层的处理流程完成进一步的打开文件操作中。 - 文件系统抽象层的处理流程
- 分配一个空闲的file数据结构变量file在文件系统抽象层的处理中,首先调用的是
file_open
函数,它要给这个即将打开的文件分配一个file数据结构的变量,这个变量其实是当前进程的打开文件数组current->fs_struct->filemap[]
中的一个空闲元素(即还没用于一个打开的文件),而这个元素的索引值就是最终要返回到用户进程并赋值给变量fd1。到了这一步还仅仅是给当前用户进程分配了一个file数据结构的变量,还没有找到对应的文件索引节点。
为此需要进一步调用vfs_open
函数来找到path指出的文件所对应的基于inode数据结构的VFS索引节点node。vfs_open
函数需要完成两件事情:通过vfs_lookup
找到path对应文件的inode;调用vop_open
函数打开文件。 - 找到文件设备的根目录
/
的索引节点需要注意,这里的vfs_lookup
函数是一个针对目录的操作函数,它会调用vop_lookup
函数来找到SFS文件系统中的/test
目录下的testfile文件。为此,vfs_lookup
函数首先调用get_device函数,并进一步调用vfs_get_bootfs
函数(其实调用了)来找到根目录/
对应的inode。这个inode就是位于vfs.c
中的inode变量bootfs_node
。这个变量在init_main
函数(位于kern/process/proc.c
)执行时获得了赋值。 - 找到根目录
/
下的test子目录对应的索引节点,在找到根目录对应的inode后,通过调用vop_lookup
函数来查找/
和test这两层目录下的文件testfile所对应的索引节点,如果找到就返回此索引节点。 - 把file和node建立联系。完成第3步后,将返回到
file_open
函数中,通过执行语句file->node=node;
,就把当前进程的current->fs_struct->filemap[fd]
(即file所指变量)的成员变量node指针指向了代表/test/testfile
文件的索引节点node。这时返回fd。经过重重回退,通过系统调用返回,用户态的syscall->sys_open->open->safe_open
等用户函数的层层函数返回,最终把把fd赋值给fd1。自此完成了打开文件操作。但这里我们还没有分析第2和第3步是如何进一步调用SFS文件系统提供的函数找位于SFS文件系统上的/test/testfile
所对应的sfs磁盘inode的过程。下面需要进一步对此进行分析。
- 分配一个空闲的file数据结构变量file在文件系统抽象层的处理中,首先调用的是
- SFS文件系统层的处理流程
这里需要分析文件系统抽象层中没有彻底分析的vop_lookup函数到底做了啥。下面我们来看看。在sfs_inode.c
中的sfs_node_dirops
变量定义了.vop_lookup = sfs_lookup
,所以我们重点分析sfs_lookup
的实现。
static int sfs_lookup(struct inode *node, char *path, struct inode **node_store) {