JOS
JOS不像其他操作系统一样在内核添加磁盘驱动,然后提供系统调用。我们实现一个文件系统进程来作为磁盘驱动。
- 引入一个文件系统进程(FS进程)的特殊进程,该进程提供文件操作的接口,并被赋予io权限。(x86处理器使用EFLAGS寄存器的IOPL为来控制保护模式下代码是否能执行设备IO指令,比如in和out。)
- 建立RPC机制,客户端进程向FS进程发送请求,FS进程真正执行文件操作,并将数据返回给客户端进程。
- 更高级的抽象,引入文件描述符。通过文件描述符这一层抽象就可以将控制台,pipe,普通文件,统统按照文件来对待。(文件描述符和pipe实现原理)
- 支持从磁盘加载程序并运行。
结构
superblock
依然使用超级块来记录文件系统的元数据
file
File struct用来描述文件:文件名,大小,类型,保存文件内容的block号
tips: Directories与file结构相同,只是内容是一些file
Block Cache
在FS进程的虚拟内存空间中映射一段磁盘区域。
FS进程在创建时,set特殊的缺页处理函数。
当发生缺页中断时call那个缺页处理函数,从磁盘上把数据读入物理内存。
根据FS进程内存地址空间的映射关系,FS可以很方便的通过虚拟内存找到刚读入的数据在物理内存中的位置。
The Block Bitmap
磁盘块的是否使用的bitmap
The file system interface
文件系统建立好后,还需要通过ipc来构建供用户进程操作文件的API栈,课程的图拿来用一下:
Regular env FS env
+---------------+ +---------------+
| read | | file_read |
| (lib/fd.c) | | (fs/fs.c) |
...|.......|.......|...|.......^.......|...............
| v | | | | RPC mechanism
| devfile_read | | serve_read |
| (lib/file.c) | | (fs/serv.c) |
| | | | ^ |
| v | | | |
| fsipc | | serve |
| (lib/file.c) | | (fs/serv.c) |
| | | | ^ |
| v | | | |
| ipc_send | | ipc_recv |
| | | | ^ |
+-------|-------+ +-------|-------+
| |
+-------------------+
Spawning Processes
spawn()创建一个新的进程,从文件系统加载用户程序,然后启动该进程来运行这个程序。spawn()就像UNIX中的fork()后面马上跟着exec():
- 从文件系统打开prog程序文件
- 调用系统调用sys_exofork()创建一个新的Env结构
- 调用系统调用sys_env_set_trapframe(),设置新的Env结构的Trapframe字段(该字段包含寄存器信息)。
- 根据ELF文件中program herder,将用户程序以Segment读入内存,并映射到指定的线性地址处。
- 调用系统调用sys_env_set_status()设置新的Env结构状态为ENV_RUNNABLE。
Linux Kernel
VFS
虚拟文件系统,是一个中间层,向上给用户提供一致性的文件系统接口,向下兼容各类文件系统。
规定了通用文件模型:
结构
- 超级块对象: 存放已安装文件系统的元数据
- 索引节点对象(inode):关于具体文件的一般信息,索引节点号唯一标识文件,记录文件操作函数指针。
- 文件对象file:打开文件会在内核生成的文件对象,通过fd可以找到
- 目录项对象dentry:存放目录项与对应文件进行链接的信息。
例子: 三个进程打开同一个文件,生成三个文件对象,其中两个进程用一个硬链接,所以使用两个目录对象,同一个文件所以使用一个inode,通过inode能找到超级块对象,然后来调用对应文件系统的方法IO,除此之外,尝试用的目录项对象还会被缓存在一个叫做目录cache里,方便下次直接取用。
索引节点对象(inode)由链表组织。
file和dentry都没有磁盘映像,属于内核结构,显然slab里有对应的结构。
对于进程查找路径上的每一个分量,都为其创建目录项对象,只是属于不同级:/var/init -> 第一级目录项对象:/ 第二级: var 第三级:init
文件描述符: 文件描述符是进程对象task_struct里的files_struct这个记录进程的文件信息的结构内的一个成员:struct file*[] fd_array的索引。也就是说, 假如我们有一个文件描述符fd, 进程通过fd要操作文件,只需要去task_struct->files_struct->fd_array[fd]处查找,看有无对应的struct file*,找到那个指向内核缓存中的文件对象的指针。然后通过file找到对应的dentry再通过dentry找到inode再通过inode找到superblock,基于superblock解析inode数据找到磁盘上真正的存储位置。 fd_array的第一二项一般是标准输入输出文件,第三项一般是标准错误文件,其他一般指向打开文件
所以进程可打开文件的数量,由这个数组限制。