文件系统(二)

    技术2025-08-22  4

    前言

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

    最新回复(0)