前言
本篇主要讲述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()中应该也验证。