从零到一手写操作系统(十、文件系统 2)linux虚拟文件系统)

image.png

如果追忆会荡起涟漪,那么今天的秋红落叶和晴空万里都归你
https://aeneag.xyz
微信公众号:技术乱舞
艾恩凝

手写操作系统目录

10.2)linux虚拟文件系统

什么是VFS?

可以理解为通用文件系统抽象层。在 Linux 中,支持 EXT、XFS、JFS、BTRFS、FAT、NTFS 等多达十几种不同的文件系统,但不管在什么储存设备上使用什么文件系统,也不管访问什么文件,都可以统一地使用一套 open(), read()、write()、close() 这样的接口。
105000.png

数据结构

超级块

 1struct super_block {
 2    struct list_head    s_list; //超级块链表
 3    dev_t           s_dev;     //设备标识
 4    unsigned char       s_blocksize_bits;//以位为单位的块大小
 5    unsigned long       s_blocksize;//以字节为单位的块大小
 6    loff_t          s_maxbytes; //一个文件最大多少字节
 7    struct file_system_type *s_type; //文件系统类型
 8    const struct super_operations   *s_op;//超级块函数集合
 9    const struct dquot_operations   *dq_op;//磁盘限额函数集合
10    unsigned long       s_flags;//挂载标志
11    unsigned long       s_magic;//文件系统魔数
12    struct dentry       *s_root;//挂载目录
13    struct rw_semaphore s_umount;//卸载信号量
14    int         s_count;//引用计数
15    atomic_t        s_active;//活动计数
16    struct block_device *s_bdev;//块设备
17    void            *s_fs_info;//文件系统信息
18    time64_t           s_time_min;//最小时间限制
19    time64_t           s_time_max;//最大时间限制
20    char            s_id[32];   //标识名称
21    uuid_t          s_uuid;     //文件系统的UUID
22    struct list_lru     s_dentry_lru;//LRU方式挂载的目录 
23    struct list_lru     s_inode_lru;//LRU方式挂载的索引结点
24    struct mutex        s_sync_lock;//同步锁  
25    struct list_head    s_inodes;   //所有的索引节点
26    spinlock_t      s_inode_wblist_lock;//回写索引节点的锁
27    struct list_head    s_inodes_wb;    //挂载所有要回写的索引节点
28} __randomize_layout;

超级块函数

 1struct super_operations {
 2    //分配一个新的索引结点结构
 3    struct inode *(*alloc_inode)(struct super_block *sb);
 4    //销毁给定的索引节点
 5    void (*destroy_inode)(struct inode *);
 6    //释放给定的索引节点
 7    void (*free_inode)(struct inode *);
 8    //VFS在索引节点为脏(改变)时,会调用此函数
 9    void (*dirty_inode) (struct inode *, int flags);
10    //该函数用于将给定的索引节点写入磁盘
11    int (*write_inode) (struct inode *, struct writeback_control *wbc);
12    //在最后一个指向索引节点的引用被释放后,VFS会调用该函数
13    int (*drop_inode) (struct inode *);
14    void (*evict_inode) (struct inode *);
15    //减少超级块计数调用
16    void (*put_super) (struct super_block *);
17    //同步文件系统调用
18    int (*sync_fs)(struct super_block *sb, int wait);
19    //释放超级块调用
20    int (*freeze_super) (struct super_block *);
21    //释放文件系统调用
22    int (*freeze_fs) (struct super_block *);
23    int (*thaw_super) (struct super_block *);
24    int (*unfreeze_fs) (struct super_block *);
25    //VFS通过调用该函数,获取文件系统状态
26    int (*statfs) (struct dentry *, struct kstatfs *);
27    //当指定新的安装选项重新安装文件系统时,VFS会调用此函数
28    int (*remount_fs) (struct super_block *, int *, char *);
29    //VFS调用该函数中断安装操作。该函数被网络文件系统使用,如NFS
30    void (*umount_begin) (struct super_block *);
31};

有了超级块和超级块函数集合结构,VFS 就能让一个文件系统的信息和表示变得规范了。也就是说,文件系统只要实现了 super_block 和 super_operations 两个结构,就可以插入到 VFS 中了。

目录结构
106000.png

图中显示了 Linux 文件目录情况,也显示了一个设备上的文件系统是如何挂载到某个目录下的。

 1//快速字符串保存关于字符串的 "元数据"(即长度和哈希值)
 2struct qstr {
 3    union {
 4        struct {
 5            HASH_LEN_DECLARE;
 6        };
 7        u64 hash_len;
 8    };
 9    const unsigned char *name;//指向名称字符串
10};
11struct dentry {
12    unsigned int d_flags;       //目录标志
13    seqcount_spinlock_t d_seq;  //锁
14    struct hlist_bl_node d_hash;//目录的哈希链表    
15    struct dentry *d_parent;    //指向父目录
16    struct qstr d_name;         //目录名称
17    struct inode *d_inode;      //指向目录文件的索引节点 
18    unsigned char d_iname[DNAME_INLINE_LEN];    //短目录名
19    struct lockref d_lockref;   //目录锁与计数
20    const struct dentry_operations *d_op;//目录的函数集
21    struct super_block *d_sb;   //指向超级块
22    unsigned long d_time;       //时间
23    void *d_fsdata;         //指向具体文件系统的数据
24    union {
25        struct list_head d_lru;     //LRU链表
26        wait_queue_head_t *d_wait;
27    };
28    struct list_head d_child;   //挂入父目录的链表节点 
29    struct list_head d_subdirs; //挂载所有子目录的链表
30} __randomize_layout;

dentry 结构中包含了目录的名字和挂载子目录的链表,同时也能指向父目录。但是需要注意的是,目录也是文件,需要用 inode 索引结构来管理目录文件数据。

目录文件函数

 1struct dentry_operations {
 2    //该函数判断目录对象是否有效
 3    int (*d_revalidate)(struct dentry *, unsigned int);
 4    int (*d_weak_revalidate)(struct dentry *, unsigned int);
 5    //该函数为目录项生成散列值,当目录项要加入散列表中时,VFS调用该函数
 6    int (*d_hash)(const struct dentry *, struct qstr *);
 7    //VFS调用该函数来比较name1和name2两个文件名。多数文件系统使用VFS的默认操作,仅做字符串比较。对于有些文件系统,比如FAT,简单的字符串比较不能满足其需要,因为 FAT文件系统不区分大小写
 8    int (*d_compare)(const struct dentry *,
 9            unsigned int, const char *, const struct qstr *);
10    //当目录项对象的计数值等于0时,VFS调用该函数
11    int (*d_delete)(const struct dentry *);
12    //当分配目录时调用 
13    int (*d_init)(struct dentry *);
14    //当目录项对象要被释放时,VFS调用该函数,默认情况下,它什么也不做
15    void (*d_release)(struct dentry *);
16    void (*d_prune)(struct dentry *);
17    //当一个目录项对象丢失了相关索引节点时,VFS调用该函数。默认情况下VFS会调用iput()函数释放索引节点
18    void (*d_iput)(struct dentry *, struct inode *);
19    //当需要生成一个dentry的路径名时被调用
20    char *(*d_dname)(struct dentry *, char *, int);
21    //当要遍历一个自动挂载时被调用(可选),这应该创建一个新的VFS挂载记录并将该记录返回给调用者
22    struct vfsmount *(*d_automount)(struct path *);
23    //文件系统管理从dentry的过渡(可选)时,被调用
24    int (*d_manage)(const struct path *, bool);
25    //叠加/联合类型的文件系统实现此方法
26    struct dentry *(*d_real)(struct dentry *, const struct inode *);
27} ____cacheline_aligned;

文件索引结点

VFS 用 inode 结构表示一个文件索引结点,它里面包含文件权限、文件所属用户、文件访问和修改时间、文件数据块号等一个文件的全部信息,一个 inode 结构就对应一个文件。

 1struct inode {
 2    umode_t         i_mode;//文件访问权限
 3    unsigned short      i_opflags;//打开文件时的标志
 4    kuid_t          i_uid;//文件所属的用户id
 5    kgid_t          i_gid;//文件所属的用户组id
 6    unsigned int        i_flags;//标志
 7    const struct inode_operations   *i_op;//inode函数集
 8    struct super_block  *i_sb;//指向所属超级块
 9    struct address_space    *i_mapping;//文件数据在内存中的页缓存
10    unsigned long       i_ino;//inode号
11    dev_t           i_rdev;//实际设备标志符
12    loff_t          i_size;//文件大小,以字节为单位
13    struct timespec64   i_atime;//文件访问时间
14    struct timespec64   i_mtime;//文件修改时间
15    struct timespec64   i_ctime;//最后修改时间
16    spinlock_t      i_lock; //保护inode的自旋锁
17    unsigned short          i_bytes;//使用的字节数
18    u8          i_blkbits;//以位为单位的块大小;
19    u8          i_write_hint;
20    blkcnt_t        i_blocks;
21    struct list_head    i_io_list;  
22    struct list_head    i_lru;      //在缓存LRU中的链表节点
23    struct list_head    i_sb_list;//在超级块中的链表节点
24    struct list_head    i_wb_list;
25    atomic64_t      i_version;//版本号
26    atomic64_t      i_sequence; 
27    atomic_t        i_count;//计数
28    atomic_t        i_dio_count;//直接io进程计数
29    atomic_t        i_writecount;//写进程计数
30    union {
31        const struct file_operations    *i_fop;//文件函数集合 
32        void (*free_inode)(struct inode *);
33    };
34    struct file_lock_context    *i_flctx;
35    struct address_space    i_data;
36    void            *i_private; //私有数据指针
37} __randomize_layout;

文件函数

 1struct inode_operations {
 2    //VFS通过系统create()和open()接口来调用该函数,从而为dentry对象创建一个新的索引节点
 3    int (*create) (struct inode *, struct dentry *,int);
 4    //该函数在特定目录中寻找索引节点,该索引节点要对应于dentry中给出的文件名
 5    struct dentry * (*lookup) (struct inode *, struct dentry *);
 6    //被系统link()接口调用,用来创建硬连接。硬链接名称由dentry参数指定
 7    int (*link) (struct dentry *, struct inode *, struct dentry *);
 8    //被系统unlink()接口调用,删除由目录项dentry链接的索引节点对象
 9    int (*unlink) (struct inode *, struct dentry *);
10    //被系统symlik()接口调用,创建符号连接,该符号连接名称由symname指定,连接对象是dir目录中的dentry目录项
11    int (*symlink) (struct inode *, struct dentry *, const char *);
12    //被mkdir()接口调用,创建一个新目录。
13    int (*mkdir) (struct inode *, struct dentry *, int);
14    //被rmdir()接口调用,删除dentry目录项代表的文件
15    int (*rmdir) (struct inode *, struct dentry *);
16    //被mknod()接口调用,创建特殊文件(设备文件、命名管道或套接字)。
17    int (*mknod) (struct inode *, struct dentry *, int, dev_t);
18    //VFS调用该函数来移动文件。文件源路径在old_dir目录中,源文件由old_dentry目录项所指定,目标路径在new_dir目录中,目标文件由new_dentry指定
19    int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *);
20    //被系统readlink()接口调用,拷贝数据到特定的缓冲buffer中。拷贝的数据来自dentry指定的符号链接
21    int (*readlink) (struct dentry *, char *, int);
22    //被VFS调用,从一个符号连接查找他指向的索引节点
23    int (*follow_link) (struct dentry *, struct nameidata *);
24    //在follow_link()调用之后,该函数由vfs调用进行清除工作
25    int (*put_link) (struct dentry *, struct nameidata *);
26    //被VFS调用,修改文件的大小,在调用之前,索引节点的i_size项必须被设置成预期的大小
27    void (*truncate) (struct inode *);
28    //该函数用来检查给定的inode所代表的文件是否允许特定的访问模式,如果允许特定的访问模式,返回0,否则返回负值的错误码
29    int (*permission) (struct inode *, int);
30    //被notify_change接口调用,在修改索引节点之后,通知发生了改变事件
31    int (*setattr) (struct dentry *, struct iattr *);
32    //在通知索引节点需要从磁盘中更新时,VFS会调用该函数
33    int (*getattr) (struct vfsmount *, struct dentry *, struct kstat *);
34    //被VFS调用,向dentry指定的文件设置扩展属性
35    int (*setxattr) (struct dentry *, const char *, const void *, size_t, int);
36    //被VFS调用,拷贝给定文件的扩展属性name对应的数值
37    ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
38    //该函数将特定文件所有属性列表拷贝到一个缓冲列表中
39    ssize_t (*listxattr) (struct dentry *, char *, size_t);
40    //该函数从给定文件中删除指定的属性
41    int (*removexattr) (struct dentry *, const char *);      
42};

文件

 1struct file {
 2    union {
 3        struct llist_node   fu_llist;
 4        struct rcu_head     fu_rcuhead;
 5    } f_u;
 6    struct path     f_path; //文件路径
 7    struct inode        *f_inode;  //文件对应的inode
 8    const struct file_operations    *f_op;//文件函数集合
 9    spinlock_t      f_lock;  //自旋锁
10    enum rw_hint        f_write_hint;
11    atomic_long_t       f_count;//文件对象计数据。
12    unsigned int        f_flags;//文件标志
13    fmode_t         f_mode;//文件权限
14    struct mutex        f_pos_lock;//文件读写位置锁
15    loff_t          f_pos;//进程读写文件的当前位置
16    u64         f_version;//文件版本
17    void            *private_data;//私有数据
18} __randomize_layout

关系
107000.png

文件操作

打开
108000.jpeg

读写
109000.webp

关闭
110000.webp

主要了解了linux中的文件系统,需要学习的东西还有很多

注:本节于2022年5月13日结束

手写操作系统目录


    


公众号'艾恩凝'
个人公众号
个人微信
个人微信
    吾心信其可行,
          则移山填海之难,
                  终有成功之日!
                                  ——孙文
    评论
    0 评论
avatar

取消