前言
本篇主要讲述src/fs目录下的代码,该部分代码是文件系统的核心部分,也是kernel最为复杂的部分。将从下面四部分进行分析:
1. 高速缓冲区的管理程序,是对磁盘以块为单位数据的cache;
buffer.c
1. struct buffer_head * getblk(int dev,int block); 在cache-buffer中找到指定的缓存块,如果不存在则找个未使用的bh(这个不从磁盘加载数据). icount++
2. void brelse(struct buffer_head * buf); 释放一个bh,如果icount为0则唤醒等待缓冲区的进程. icount--
3. struct buffer_head * bread(int dev,int block); 加载指定的缓存块,如果失败返回null.
4. void bread_page(unsigned long address,int dev,int b[4]); 读一页4KB数据到address开始处, 不能保证4个1KB数据都是有效的.
5. struct buffer_head * breada(int dev,int first, ...); 带预读功能的加载缓存块函数, 主要是读first, 后面的...是允许失败的且不执行icount++
6. void buffer_init(long buffer_end); fs的缓存模块初始化. main()中调用.
函数的调用关系: +-------------------------------+ | bread breada bread_page | 对外接口 +-------------------------------+ | getblk() | | | | | | | +-------------------------------+
资源竞争: 1. 空闲的buffer-header队列 2. 加锁的单个buffer-header
2. 文件系统的低层通用函数,说明了文件索引节点的管理、磁盘数据块的分配和释放、文件名于i节点的转换;
bitmap.c
这个处理了文件系统上的逻辑块位图和i节点位图. 1. void free_block(int dev, int block); dev: 设备号, block: 磁盘逻辑块号(盘块号) 释放磁盘上的逻辑块. 2. int new_block(int dev); 在文件系统dev上申请一个磁盘逻辑块(清零), 返回磁盘逻辑块号. 3. void free_inode(struct m_inode *inode); 释放指定的i节点, 复位文件系统中对应的i节点位图比特位. 4. struct m_inode *new_inode(int dev); 在文件系统dev中新建一个i节点,并在inode内存表中缓存之. 操作对象: 磁盘文件系统的资源(block+inode).
inode.c
仅对内存inode这个数组进行管理.
内部接口: 1. static inline void wait_on_inode(struct m_inode * inode); 2. static inline void lock_inode(struct m_inode * inode); 3. static inline void unlock_inode(struct m_inode * inode); 4. static int _bmap(struct m_inode * inode,int block,int create); 将一个文件(inode)的内部数据逻辑块号映射到该文件系统的数据逻辑块号. 5. static void read_inode(struct m_inode * inode); 读取指定inode. 6. static void write_inode(struct m_inode * inode); 将指定的inode写盘(有cache-buffer在,存在延迟写),最基本要求是同步到cache-buffer上.
外部接口: 1. void invalidate_inodes(int dev); 将dev的内存inode无效. 2. void sync_inodes(void); 将内存inode写盘(有cache-buffer在,存在延迟写). 3. int bmap(struct m_inode * inode,int block); 对内部函数4的包装, 此函数不会创建数据块,用于查询. 4. int create_block(struct m_inode * inode, int block); 对内部函数4的包装, 此函数会帮助创建数据块. 5. void iput(struct m_inode * inode); 释放一个内存inode, icount--, 干很多事情: a. 若为无名管道, 则唤醒等待进程,释放管道内存; b. 若为设备文件,则同步该设备; c. 若nlinks为0, 则从相应的fs释放之; d. 若dirt, 则write_inode. 6. struct m_inode * get_empty_inode(void); 获取一个空的的内存inode. 7. struct m_inode * get_pipe_inode(void); 获取一个无名管道内存inode. 8. struct m_inode * iget(int dev,int nr); 获取指定的内存inode, icount++. 考虑了inode为挂载点情况.
资源竞争: 1. 单个内存inode被加锁.
truncate.c 1. void truncate(struct m_inode *inode); 将普通文件or目录的内容截除为0, 并不是删除该i节点.
super.c 对文件系统的super block的管理, 存在一个super-blocks cache-buffer. 内部接口: 1. static void lock_super(struct super_block *sb); 2. static void unlock_super(struct super_block *sb); 3. static void wait_on_super(struct super_block *sb); 4. static struct super_block *read_super(int dev); 从设备上读取超级块到super-cache中, 如果超级块已经存在则直接返回.
外部接口: 1. struct super_block *get_super(int dev); 获取指定设备的超级块. 这个只是查找super_table, 别忘了文件系统是通过mount接口加载的. 2. void put_super(int dev); 将指定设备的超级块从super-cache中移除. 3. int sys_umount(char *dev_name); 卸载文件系统, dev_name为块设备名称. 4. int sys_mount(char *dev_name, char *dir_name, int rw_flag); 安装文件系统, 被加载的地方必须是一个目录名,并且对应的i节点没有被其它程序占用. 5. void mount_root(); 初始化系统的根文件系统.
namei.c 主要用于完成从一个给定文件系统路径名寻找并加载其对应i节点. 内部接口: 1. static int permission(struct m_inode * inode,int mask); 权限检查, user-mode ---> group-mode ---> other-mode 2. static int match(int len,const char * name,struct dir_entry * de); 目录项比较, 成功返回1, 否则返回0. 3. static struct buffer_head * find_entry(struct m_inode ** dir, const char * name, int namelen, struct dir_entry ** res_dir); 查找一个目录项,并返回所在的buffer-header. name仅是目录项的名字. 内部考虑了. .., 进程root节点, 挂载点. 4. static struct buffer_head * add_entry(struct m_inode * dir, const char * name, int namelen, struct dir_entry ** res_dir); 在目录文件中添加一个目录项. 5. static struct m_inode * get_dir(const char * pathname); 根据路径名找到顶层目录的inode, 例如project/include/math 返回include的节点, project/include/math/ 返回math的节点. 6. static struct m_inode * dir_namei(const char * pathname, int * namelen, const char ** name); 根据路径名找到目录inode, 并返回基本名称. 例如 project/include/math 返回include的inode, 并置name为"math". 7. static int empty_dir(struct m_inode * inode); 判断该目录是否为空目录.
外部接口: 1. struct m_inode * namei(const char * pathname); 从路径找到文件的inode. 例如project/include/math 返回math的inode. 2. int open_namei(const char * pathname, int flag, int mode, struct m_inode ** res_inode); 根据路径打开文件, 获取文件的inode. 3. int sys_mknod(const char * filename, int mode, int dev); 创建一个设备文件(字符或块设备). 4. int sys_mkdir(const char * pathname, int mode); 创建一个目录pathname. 5. int sys_rmdir(const char * name); 删除目录name. 6. int sys_unlink(const char * name); 取消硬链接. 7. int sys_link(const char * oldname, const char * newname); 创建一个硬链接newname,链接到oldname. 不能为目录创建别名.
3. 对文件中数据进行读写操作,包括对字符设备、管道、块读写文件中数据的访问;
block_dev.c
块设备的读写,是对整个文件系统占用的区域哦. 1. int block_write(int dev, long * pos, char * buf, int count); 向设备dev的pos开始处写数据. 2. int block_read(int dev, unsigned long * pos, char * buf, int count); 从设备dev的pos开始处读数据.
file_dev.c
对设备上文件的读写. 1. int file_read(struct m_inode * inode, struct file * filp, char * buf, int count); 读文件inode, filp为描述它的文件结构. 2. int file_write(struct m_inode * inode, struct file * filp, char * buf, int count); 向文件写数据.
char_dev.c
字符设备的读写. int rw_char(int rw,int dev, char * buf, int count, off_t * pos);
pipe.c
无名管道的读写操作. 1. int read_pipe(struct m_inode * inode, char * buf, int count); 读管道inode. 2. int write_pipe(struct m_inode * inode, char * buf, int count); 写管道inode. 3. int sys_pipe(unsigned long * fildes); 创建无名管道, 文件句柄fildes[0]&[1]均指向管道i节点。fildes[0]用于读管道数据, fildes[1]用于写管道数据。
read_write.c
通过文件描述符对文件操作. 1. int sys_lseek(unsigned int fd,off_t offset, int origin); 2. int sys_read(unsigned int fd,char * buf,int count); 根据fd的具体类型调用: a. read_pipe b. block_read c. file_read. d. rw_char 3. int sys_write(unsigned int fd,char * buf,int count); 同2.
4. 文件的系统调用接口的实现,涉及文件打开、关闭、创建及文件目录操作等。
execute.c
加载程序。
open.c 1. int sys_utime(char * filename, struct utimbuf * times); 读取或修改文件的访问和修改时间. 2. int sys_access(const char * filename,int mode); 验证文件的访问权限。 3. int sys_chdir(const char * filename); 将当前进程的工作目录改为filename. 4. int sys_chroot(const char * filename); 把指定的目录名设置成为当前进程的根目录'/'. 5. int sys_chmod(const char * filename,int mode); 修改文件属性。 6. int sys_chown(const char * filename,int uid,int gid); 修改文件宿主, 得有root权限。 7. int sys_open(const char * filename,int flag,int mode); 打开文件filename. flag -- 打开文件标志. O_RDONLY, O_WRONLY, O_RDWR, O_CREATE, O_APPEND, O_EXCL等组合。 mode -- 如果创建了文件,文件属性设置为此。 8. int sys_creat(const char * pathname, int mode); 9. int sys_close(unsigned int fd);
stat.c 1. int sys_stat(char * filename, struct stat * statbuf); 获取文件filename的状态信息。 2. int sys_fstat(unsigned int fd, struct stat * statbuf); 获取文件fd的状态信息。
fcntl.c 1. int sys_dup2(unsigned int oldfd, unsigned int newfd); 复制文件句柄oldfd到newfd. 2. int sys_dup(unsigned int fildes); 复制文件句柄,返回另一个句柄。 3. int sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg);
ioctl.c 1. int sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg); 输入输出控制。
下图是我整理的一个fs中数据的 layer图:
我将按照如上的思路阅读源码。
Minix文件系统格式
1. 对物理磁盘的划分(格式化)
对硬盘这样的大块,进行分区而治:
2. 数据结构
NOTE: 关于inode map的位0不使用,第0个inode在imap中的序号为1, 对于data logic-block的位0不使用,第0个data logic-block的在bmap中的序号为1.
File的内核表示
文件读写操作流程
疑问:
1. 文件的访问权限验证仅在open()函数中验证,这个不严谨,read() && write()中应该也验证。