0
点赞
收藏
分享

微信扫一扫

Linux文件系统二(虚拟文件系统VFS实现原理)

创作人QQ:851301776,邮箱:lfr890207@163.com
        欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点!

个人座右铭:
         1.没有横空出世,只要厚积一定发。
         2.你可以学历不高,你可以不上学,但你不能不学习

一、文件系统概述

        在Linux操作系统中,一切皆是文件,除了通常所说狭义的文件(文本文件和二进制文件)以外,目录、套接字、设备及管道等都是文件。

        文件系统在不同的上下文中有不同的含义。在存储设备缓存文件的方法数据结构和访问方法。按照某种文件系统类型格式化的一块存储介质,内核中负责管理和存储文件的模块,即文件系统,Linux文件系统的架构,分为用户空间、内核空间、硬件3个层面。

 

 1、用户空间

        应用程序可以直接使用内核提供的系统调用访问文件:

        (1)一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能访问这个文件系统。

        (2)系统调用umount用来卸载某个目录下挂载的文件系统。可以执行命令“umount dir”来卸载文件系统,umount命令调用系统调用umount。

备注:

        执行命令:mount -t fstype divice dir, 把文件系统挂载到某个目录下,卸载某个目录挂载的执行命令:umount dir。 然后可以执行其他系统调用:open/close/read/write/lseek/fsync/fdatasync.

        应用程序可以使用glibc库封装标准I/o流函数访问文件,标准I/o流提供缓冲区,目的尽可能减少调用read/write次数,提高性能。标准I/O流函数:fopen/fclose/fread/fwrite(fflush)/flseek

2、硬件层面

        外部存储设备分为块设备、闪存和NVDIMM(非易失性内存)设备3类。块设备主要有2种类型:机械硬盘和闪存类块设备。

        机械硬件读写单位为扇区,访问首先沿着半径方向移动磁头寻找磁道,然后转动盘片找到扇区。

        闪存作为存储设备,里面的控制器运行固化驱动程序,驱动程序的功能是内存转换层,把闪存转换为块设备,对外表现为块设备。Solid state drives,SSD。手机/平板嵌入式存储卡eMMC(embedded multi meida card)/通用闪存存储UFS(Universal flash storage)。

3、内核空间

        在内核的目录fs下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的文件操作系统接口,为了使不同的文件系统实现能够共存,内核实现一个抽象层,称为虚拟文件系统(Virtual File System VFS),也称为虚拟文件系统切换(Virtual Filesystem Switch,VFS)。

        文件系统分为:块设备文件系统(存储设备十二机械硬盘和SSD等块,EXT2/3/4)、闪存文件系统存储设备Nor,NAND闪存、内存文件系统(文件在内存中)、伪文件系统(假的文件系统)。

二、虚拟文件系统数据结构

        虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义一套统一的数据结构。超级块、索引节点、目录项。

1、超级块

        文件系统的第一块是超级块,用来描述文件系统的总体信息。当我们把文件系统挂载到内存中的目录树的一个目录下时,就会读取文件系统的超级块,在内存中创建超级块的副本,结构体super_block内核源码,主要成员如下:

         超级块操作系统集合的数据结构时:结构体super_operations,主要成员如下:

 2、挂载描述符

        一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统。每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符:mount 结构体。挂载描述符用来描述文件系统的一个挂载实类,同一个存储设备上的文件系统可以多次挂载,每次挂载到不同的目录下。

        结构体mount挂载描述符的主要成员如下:

 

 

 3、文件系统类型

        因为每种文件系统类型的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型file_system_type.并且实现mount方法用来读取和解析超级块。内核源码如下:

 

struct file_system_type {

        const char *name; // 文件系统类型的名称

        int fs_flags;

        #define FS_REQUIRES_DEV 1

        #define FS_BINARY_MOUNTDATA 2

        #define FS_HAS_SUBTYPE 4

        #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */

        #define FS_RENAME_DOES_D_MOVE 32768 /* FS will ha

        ndle d_move() during rename() internally. */

        // 用来在挂载文件系统的时候读取并且解析超有块

        struct dentry (mount) (struct file_system_type *, int,const char *, void *);

        // 用来在卸载文件系统的时候释放超级块

        void (*kill_sb) (struct super_block *);

        struct module *owner;

        struct file_system_type * next;

        // 多个存储设备上的文件系统的类型可能相同,此成员用来把相同文件系统类型的超级块链接起来

        struct hlist_head fs_supers;

        struct lock_class_key s_lock_key;

        struct lock_class_key s_umount_key;

        struct lock_class_key s_vfs_rename_key;

        struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

        struct lock_class_key i_lock_key;

        struct lock_class_key i_mutex_key;

        struct lock_class_key i_mutex_dir_key;

};

4、索引节点

        在文件系统中,每个文件对应一个索引节点,索引节点描述两类信息:

        (1)文件的属性,也称为元数据(metadata)。

        (2)文件数据的存储位置,每个索引节点有一个唯一的编号。当内核访问存储设备上的一个文件时,会在内存中创建索引节点的一个副本,内核结构体inode源码如下:

 

struct inode {

        umode_t         i_mode; // 文件类型和访问权限

        unsigned         short i_opflags;

        kuid_t        i_uid; // 创建文件的用户的标识符

        kgid_t        i_gid; // 创建文件的用户所属的组标识符

        unsigned int        i_flags;

#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl *i_acl;

        struct         posix_acl *i_default_acl;

#endif

        const         struct inode_operations *i_op;

        struct         super_block *i_sb; // 指向文件所属的文件系统的超级块

        struct         address_space *i_mapping; // 指向文件的地址空间

#ifdef CONFIG_SECURITY

        void        *i_security;

#endif

        /* 索引节点编号 */

        unsigned long        i_ino;

/*

* Filesystems may only read i_nlink directly. They shall u

se the

* following functions for modification:

*

* (set|clear|inc|drop)_nlink

* inode(inc|dec)link_count

*/

        union {

                const unsigned int i_nlink; // 硬链接计数

                unsigned int __i_nlink;

        };

        dev_t        i_rdev;// 设备号

        loff_t        i_size; // 文件长度

        struct timespec i_atime; // 上一次访问文件的时间

        struct timespec        i_mtime; // 上一次修改文件数据的时间

        struct timespec i_ctime; // 上一次修改文件索引节点的赶时间

        spinlock_t        i_lock; /* i_blocks, i_bytes, maybe i_size */

        unsigned short        i_bytes; // 文件长度除以块长度的余数

        unsigned int        i_blkbits; // 块长度以 2 为底的对数,块长度是 2 的 i_blkbits 次幂

        blkcnt_t        i_blocks; // 文件的块数

#ifdef __NEED_I_SIZE_ORDERED

        seqcount_t        i_size_seqcount;

#endif

        /* Misc */

        unsigned long        i_state;

        struct rw_semaphore i_rwsem;unsigned long

        dirtied_when; /* jiffies of first dirtying*/

        unsigned long        dirtied_time_when;

        struct hlist_node i_hash;

        struct list_head i_io_list; /* backing dev IO list */

#ifdef CONFIG_CGROUP_WRITEBACK

        struct bdi_writeback *i_wb;

/* the associated cgroup wb */

/* foreign inode detection, see wbc_detach_inode() */

        int        i_wb_frn_winner;

        u16        i_wb_frn_avg_time;

        u16        i_wb_frn_history;

#endif

        struct list_head i_lru; /* inode LRU list */

        struct list_head i_sb_list;

        struct list_head i_wb_list; /* backing dev writeback list*/

        union {

                struct hlist_head i_dentry;

                struct rcu_head i_rcu;

        };

        u64        i_version;

        atomic_t        i_count;

        atomic_t        i_dio_count;

        atomic_t        i_writecount;

#ifdef CONFIG_IMA

        atomic_t        i_readcount; /* struct files open RO */

#endif

        const struct file_operations i_fop; /* former ->i_op->default_file_ops */

        struct file_lock_context *i_flctx;

        struct address_space i_data;

        struct list_head i_devices;

        union {

                struct pipe_inode_info *i_pipe;

                struct block_device *i_bdev; // 指向块设备

                struct cdev *i_cdev; // 指向字符设备

                char        *i_link;

                unsigned        i_dir_seq;

        };

        __u32        i_generation;

#ifdef CONFIG_FSNOTIFY

        __u32        i_fsnotify_mask; /* all events this inode caresabout */

        struct fsnotify_mark_connector __rcu *i_fsnotify_marks;

#endif

#if IS_ENABLED(CONFIG_FS_ENCRYPTION)

        struct fscrypt_info *i_crypt_info;

#endif

        void        i_private; / fs or device private pointer */

};

5、目录项

        文件系统把目录当做文件,这种文件的数据是由目录项结构组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号。当内核访问存储设备上的一个目录项时,会在内核中创建目录项的一个副本,结构体dentry主要成员如下:

 

struct dentry {

        /* RCU lookup touched fields */

        unsigned int d_flags;/* protected by d_lock */

        seqcount_t d_seq;        /* per dentry seqlock */

        struct hlist_bl_node d_hash; /* 用来把目录项加入散列表 */

        struct dentry d_parent; / 指向父目录 */

        struct qstr d_name; // 存储文件名称

        struct inode *d_inode;

        /* Where the name belongs to- NULL is * negative */

        unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */

        /* Ref lookup also touches following */

        struct lockref d_lockref;

        /* per- dentry lock and refcount */

        const struct dentry_operations *d_op;

        struct super_block d_sb; / The root of the dentry tree*/

        unsigned long d_time;

        /* used by d_revalidate */

        void *d_fsdata;

        /* fs-specific data */

        union {

                struct list_head d_lru; /* LRU list */

                wait_queue_head_t d_wait; / in-lookup ones only */

        };

        struct list_head d_child; /* child of parent list */

        struct list_head d_subdirs; /* our children */

        /*

        * d_alias and d_rcu can share memory

        */

        union {

                struct hlist_node d_alias; /* inode alias list */

                struct hlist_bl_node d_in_lookup_hash; /* only for in-

                lookup ones */

                struct rcu_head d_rcu;

        } d_u;

};

6、文件打开实例及打开文件表 

        当进程打开一个文件的时候,虚拟文件系统就会创建一个打开实例:file结构体

 

struct file {

        union {

                struct llist_node fu_llist;

                struct rcu_head fu_rcuhead;

        } f_u;

        /*struct path {

                struct vfsmount *mnt; // 指向文件所属文件系统的挂载描述符的成员 mnt

                struct dentry *dentry; // 文件对应的目录项

        };*/

        struct path f_path; // 存储文件在目录树中的位置

        struct inode        f_inode; / 指向文件的索引节点 */

        // 指向文件操作命令

        const struct file_operations *f_op;

        /** Protects f_ep_links, f_flags.* Must not be taken from IRQ context.*/

        spinlock_t        f_lock;

        atomic_long_t        f_count;

        unsigned int        f_flags;

        fmode_t        f_mode; // 访问模式

        struct mutex        f_pos_lock;

        loff_t        f_pos; // 文件偏移,进程当前正在访问的位置

        struct fown_struct f_owner;

        const struct cred *f_cred;

        struct file_ra_state f_ra;

        u64        f_version;

#ifdef CONFIG_SECURITY

        void        *f_security;

#endif

/* needed for tty driver, and maybe others */

void        *private_data;

#ifdef CONFIG_EPOLL

/* Used by fs/eventpoll.c to link all the hooks to this file *

/

        struct list_head f_ep_links;

        struct list_head f_tfile_llink;

#endif /* #ifdef CONFIG_EPOLL */

        struct address_space *f_mapping; // 指向文件的地址空间

} attribute((aligned(4))); /* lest something weird decides that 2 is OK */

 文件系统信息结构主要成员如下:

打开文件表的数据结构如下:

 三、注册文件系统类型

        因为每种文件系统的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件类型:file_system_type,实现mount方法来读取和解析超级块,函数register_filesystem来注册文件系统类型:

         管理员可以执行命令:cat /proc/filesystems--查看已经注册的文件系统类型。

举报

相关推荐

0 条评论