• linux虚拟文件系统


    小前言

    开写之前,插个题外话。内核相关内容写了几篇文章了,每篇文章篇幅都不少,特别是贴了不少代码,因为要看内核的具体实现,源码是必不可少的。为了方便阅读源码,添加了部分中文注释,也保留了原来的的英文注释,所以篇幅比较长。写这一段话,希望保持住啃内核的热情,不要轻易放弃。

    进入正题。在linux操作系统当中,一切皆是文件,除了通所说狭义的文件(文本文件和二进制文件)以外,目录、套接字、设备及管道等都是文件。为了处理与文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供媒介,linux中设计了虚拟文件系统。

    文件系统

    先介绍不虚拟的文件系统。
    文件系统在不同的上下文中有不同的含义:

    • 在存储设备缓存文件的方法,包括数据结构和访问方法。
    • 按照某种文件系统类型格式化的一块存储介质。
    • 内核中负责管理和存储文件的模块,即文件系统模块。

    在linux中,可以通过命令mount -t fstype device dir把文件系统挂载到某个目录下,其中fstype是文件系统相关类型,device是挂载设备,dir是设备所对应的目录,这个命令底层是调用内核mount()函数。卸载挂载在某个目录上的文件系统,执行命令umount dir。这两个命令还可以有其他参数,这里略掉。关于文件系统的命令,还有open、close、write、read、lseek、fsync、fdatasync等。
    fsync与fdatasync是磁盘同步命令。linux写文件时,内核的文件系统模块会把数据保存在缓存页中,不会立即写入存储设备,需要使用fsync命令把修改的文件属性和数据立即写入存储设备,或使用fdatasync把文件中修改的数据立即写入存储设备。
    两者的区别,fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。
    再多说一点,内核中fwrite()函数也是同样的原理,可以使用fflush()冲刷流,把写到缓冲区的数据立即写到内核中。
    linux文件系统的架构,分为用户空间、内核空间和硬件 3 个层面。看图
    在这里插入图片描述
    下面针对这三个层面,一个个介绍。

    用户空间

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

    • 一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能访问这个文件系统。
    • 系统调用 umount 用来卸载某个目录下挂载的文件系统 。 可 以 执 行 命 令 umount dir 来 卸 载 文 件 系 统 ,umount 命令调用系统调用 umount()函数。

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

    硬件层面

    外部存储设备分为块设备、闪存和 NVDIMM(非易失性内存)设备 3 类。
    块设备主要有 2 种类型:机械硬盘和闪存类块设备。机械硬盘读写单位为扇区,访问首先沿着半径备方向移动磁头寻找磁道,然后转动盘片找到扇区。闪存作为存储设备,里面的控制器运行固化驱动程序,驱动程序的功能是内存转换层,把闪存转换为块设备,对外表现为块设备。Solid state drives,SSD。手机/平板嵌入式存储卡 eMMC(embedded multi media card)/通用闪存存储 UFS(Universal flash storage)
    这里再介绍一下DIMM(双列直插式存储模块)。设备把NAND闪存、内存、超级电容集成到一起,做到访问速度和内存一样快,且断电不丢失,因为瞬间断电时会有超级电容供电,内存中的数据会转移到NAND。
    硬件不是重点,就介绍这么多。

    内核空间

    在内核的目录 fs 下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的文件操作接口,为了使不同的文件系统实现能够共享,内核实现一个抽象层,称为虚拟文件系统(Virtual File System,VFS),也称为虚拟文件系统切换(Virtual Filesystem Switch,VFS)。
    文件系统分为:块设备文件系统(存储设备是机械硬盘和 SSD 等块,如EXT2/3/4)、闪存文件系统(存储设备 NOR 闪存、NAND 闪存,常用的闪存文件系统有目录型闪存文件系统2JFFS2、无序区块镜像文件系统UBIFS)、内存文件系统(文件在内存中,如tmpfs)、伪文件系统。
    为什么需要针对闪存专门设计文件系统?一是因为软件需要识别并跳过NAND的存储坏块,二是因为需要实现损耗均匀,所有擦除块的擦除次数要做到均匀,避免一部分擦除块先损坏。
    伪文件系统是为了使用vfs的编程接口而设计的假的文件系统,常用的有文件系统有:

    • proc:把内核的信息导出到用户空间。
    • sockfs:文件系统使用套接字,可以使用read()接收报文write()发送报文。
    • sysfs:把内核设备的所有信息导出到用户空间,挂载目录是/sys。
    • cgroup:控制组用来控制进程的资源,能让管理员可以写成文件的方式来配置。

    虚拟文件系统数据结构

    虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义一套统一的数据结构。下面介绍一些,又到了贴源码的环节……

    超级块

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

    struct super_block {
        //用来把所有超级块实例链接到全局链表super_blocks
    	struct list_head	s_list;		/* Keep this first */
        //保存文件系统所有的块设备,保存设备号
    	dev_t			s_dev;		/* search index; _not_ kdev_t */
        //块长度,值为s_blocksize所占位数,即s_blocksize_bits=log2(s_blocksize)向上取整
    	unsigned char		s_blocksize_bits;
        //块长度,单位字节。
    	unsigned long		s_blocksize;
        //文件系统支持的最大长度
    	loff_t			s_maxbytes;	/* Max file size */
        //指向文件系统类型
    	struct file_system_type	*s_type;
        //指向超级块操作集合,下面会贴代码
    	const struct super_operations	*s_op;
    	const struct dquot_operations	*dq_op;
    	const struct quotactl_ops	*s_qcop;
    	const struct export_operations *s_export_op;
        //标志位
    	unsigned long		s_flags;  
    	unsigned long		s_iflags;	/* internal SB_I_* flags */
        //文件系统类型的魔幻数,每种文件系统类型分配一个唯一数
    	unsigned long		s_magic;
        //指向根目录的结构体struct dentry
    	struct dentry		*s_root;
    	struct rw_semaphore	s_umount;
    	int			s_count;
    	atomic_t		s_active;
    #ifdef CONFIG_SECURITY
    	void                    *s_security;
    #endif
    	const struct xattr_handler **s_xattr;
    
    	const struct fscrypt_operations	*s_cop;
    
    	struct hlist_bl_head	s_anon;		/* anonymous dentries for (nfs) exporting */
    	struct list_head	s_mounts;	/* list of mounts; _not_ for fs use */
    	struct block_device	*s_bdev;
    	struct backing_dev_info *s_bdi;
    	struct mtd_info		*s_mtd;
    	struct hlist_node	s_instances;
    	unsigned int		s_quota_types;	/* Bitmask of supported quota types */
    	struct quota_info	s_dquot;	/* Diskquota specific options */
    
    	struct sb_writers	s_writers;
    
    	char s_id[32];				/* Informational name */
    	u8 s_uuid[16];				/* UUID */
    
    	void 			*s_fs_info;	/* Filesystem private info */
    	unsigned int		s_max_links;
    	fmode_t			s_mode;
    
    	/* Granularity of c/m/atime in ns.
    	   Cannot be worse than a second */
    	u32		   s_time_gran;
    
    	/*
    	 * The next field is for VFS *only*. No filesystems have any business
    	 * even looking at it. You had been warned.
    	 */
    	struct mutex s_vfs_rename_mutex;	/* Kludge */
    
    	/*
    	 * Filesystem subtype.  If non-empty the filesystem type field
    	 * in /proc/mounts will be "type.subtype"
    	 */
    	char *s_subtype;
    
    	/*
    	 * Saved mount options for lazy filesystems using
    	 * generic_show_options()
    	 */
    	char __rcu *s_options;
    	const struct dentry_operations *s_d_op; /* default d_op for dentries */
    
    	/*
    	 * Saved pool identifier for cleancache (-1 means none)
    	 */
    	int cleancache_poolid;
    
    	struct shrinker s_shrink;	/* per-sb shrinker handle */
    
    	/* Number of inodes with nlink == 0 but still referenced */
    	atomic_long_t s_remove_count;
    
    	/* Being remounted read-only */
    	int s_readonly_remount;
    
    	/* AIO completions deferred from interrupt context */
    	struct workqueue_struct *s_dio_done_wq;
    	struct hlist_head s_pins;
    
    	/*
    	 * Owning user namespace and default context in which to
    	 * interpret filesystem uids, gids, quotas, device nodes,
    	 * xattrs and security labels.
    	 */
    	struct user_namespace *s_user_ns;
    
    	/*
    	 * Keep the lru lists last in the structure so they always sit on their
    	 * own individual cachelines.
    	 */
    	struct list_lru		s_dentry_lru ____cacheline_aligned_in_smp;
    	struct list_lru		s_inode_lru ____cacheline_aligned_in_smp;
    	struct rcu_head		rcu;
    	struct work_struct	destroy_work;
    
    	struct mutex		s_sync_lock;	/* sync serialisation lock */
    
    	/*
    	 * Indicates how deep in a filesystem stack this SB is
    	 */
    	int s_stack_depth;
    
    	/* s_inode_list_lock protects s_inodes */
    	spinlock_t		s_inode_list_lock ____cacheline_aligned_in_smp;
    	struct list_head	s_inodes;	/* all inodes */
    
    	spinlock_t		s_inode_wblist_lock;
    	struct list_head	s_inodes_wb;	/* writeback inodes */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    超级块操作集合

    struct super_operations {
        //用来为一个索引节点分配内存并初始化
       	struct inode *(*alloc_inode)(struct super_block *sb);
        //用来释放内存中的索引节点
    	void (*destroy_inode)(struct inode *);
    
        //用来把索引节点标记为脏
       	void (*dirty_inode) (struct inode *, int flags);
        //用来把一个索引节点写到存储设备
    	int (*write_inode) (struct inode *, struct writeback_control *wbc);
        //用来在索引节点的引用计数减到0时调用
    	int (*drop_inode) (struct inode *);
        //从存储设备上的文件系统删除一个索引节点
    	void (*evict_inode) (struct inode *);
        //用来释放超级块
    	void (*put_super) (struct super_block *);
    	int (*sync_fs)(struct super_block *sb, int wait);
    	int (*freeze_super) (struct super_block *);
    	int (*freeze_fs) (struct super_block *);
    	int (*thaw_super) (struct super_block *);
    	int (*unfreeze_fs) (struct super_block *);
    	int (*statfs) (struct dentry *, struct kstatfs *);
    	int (*remount_fs) (struct super_block *, int *, char *);
    	void (*umount_begin) (struct super_block *);
    
    	int (*show_options)(struct seq_file *, struct dentry *);
    	int (*show_devname)(struct seq_file *, struct dentry *);
    	int (*show_path)(struct seq_file *, struct dentry *);
    	int (*show_stats)(struct seq_file *, struct dentry *);
    #ifdef CONFIG_QUOTA
    	ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
    	ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
    	struct dquot **(*get_dquots)(struct inode *);
    #endif
    	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
    	long (*nr_cached_objects)(struct super_block *,
    				  struct shrink_control *);
    	long (*free_cached_objects)(struct super_block *,
    				    struct shrink_control *);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    挂载描述符

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

    /* 
    为了方便分析,给一个案例。案例分析如下:
    把文件系统2挂载到目录"/x"下,目录x属于文件系统1,目录x称为挂载点
    文件系统2的mount实例是文件系统1的mount实例的孩子
    文件系统1的mount实例是文件系统2的mount实例的父亲
    这里假设下面是文件系统2的挂载描述符
    */
    struct mount {
        //用来把挂载描述符加入全局散列表mnt_hashtable,关键字是(父挂载描述符,挂载点)
    	struct hlist_node mnt_hash;
        //指向父亲,即文件系统1的mount实例
    	struct mount *mnt_parent;
        //指向作为挂载点的目录,即文件系统1的目录x
    	struct dentry *mnt_mountpoint;
        //文件系统2的挂载信息,下面贴代码
    	struct vfsmount mnt;
    	union {
    		struct rcu_head mnt_rcu;
    		struct llist_node mnt_llist;
    	};
    #ifdef CONFIG_SMP
    	struct mnt_pcp __percpu *mnt_pcp;
    #else
    	int mnt_count;
    	int mnt_writers;
    #endif
        //孩子链表的头节点
    	struct list_head mnt_mounts;	/* list of children, anchored here */
        //用来加入父亲的孩子链表
    	struct list_head mnt_child;	/* and going through their mnt_child */
        //用来把挂载描述符添加到超级块的挂载实例链表中
        //同一个存储设备上的文件系统,可以多次挂载,每次挂载到不同的目录
    	struct list_head mnt_instance;	/* mount instance on sb->s_mounts */
        //指向存储设备的名称,例如"/dev/dsk/hda1"
    	const char *mnt_devname;	/* Name of device e.g. /dev/dsk/hda1 */
    	struct list_head mnt_list;
    	struct list_head mnt_expire;	/* link in fs-specific expiry list */
    	struct list_head mnt_share;	/* circular list of shared mounts */
    	struct list_head mnt_slave_list;/* list of slave mounts */
    	struct list_head mnt_slave;	/* slave list entry */
    	struct mount *mnt_master;	/* slave is on master->mnt_slave_list */
    	struct mnt_namespace *mnt_ns;	/* containing namespace */
        //指向挂载点,下面贴代码
    	struct mountpoint *mnt_mp;	/* where is it mounted */
    	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
    	struct list_head mnt_umounting; /* list entry for umount propagation */
    #ifdef CONFIG_FSNOTIFY
    	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
    	__u32 mnt_fsnotify_mask;
    #endif
    	int mnt_id;			/* mount identifier */
    	int mnt_group_id;		/* peer group identifier */
    	int mnt_expiry_mark;		/* true if marked for expiry */
    	struct hlist_head mnt_pins;
    	struct fs_pin mnt_umount;
    	struct dentry *mnt_ex_mountpoint;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    挂载信息

    struct vfsmount {
        //指向文件系统2的根目录
        struct dentry *mnt_root; /* root of the mounted tree */
        //指向文件系统2的超级块
        struct super_block *mnt_sb; /* pointer to superblock */
        int mnt_flags;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    挂载点

    struct mountpoint {
    struct hlist_node m_hash;
        //指向作为挂载点的目录
        struct dentry *m_dentry;
        //用来把同一个挂载点下的所有挂载描述符链接起来
        struct hlist_head m_list;
        int m_count;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    文件系统类型

    因为每种文件系统类型的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型 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 handle 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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    索引节点

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

    • 文件的属性,也称为元数据(metadata)。
    • 文件数据的存储位置,每个索引节点有一个唯一的编号。当内核访问存储设备上的一个文件时,会在内存中创建索引节点的一个副本。

    看代码

    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_ACL
    	struct 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
    
    	/* Stat data, not accessed from path walking */
    	unsigned long		i_ino;  //索引节点编号
    	/*
    	 * Filesystems may only read i_nlink directly.  They shall use 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;  //文件长度除以块长度的余数
        //inode块长度位数,即长度是 2 的 i_blkbits 次幂字节
        unsigned int		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 cares about */
    	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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    区分一下三个时间。

    • i_atime:文件最后访问时间,即是文件最后的读取时间,例如:用命令cat filename,此时间修改,其他两个时间不修改。
    • i_ctime:结点最后修改时间,即是修改inode结构的时间,例如:用命令ln filename1 filename2,此时间修改,其他两个时间不修改。
    • i_mtime:文件最后修改时间,即是对文件内容的修改时间,例如:用命令echo aaa >filename,以上三个时间都修改。

    目录项

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

    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;	/* lookup hash list */
        //指向父目录
    	struct dentry *d_parent;	/* parent directory */
        //存储文件名称
    	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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    目录项与索引节点的对应关系
    在这里插入图片描述

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

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

    struct file {
    	union {
    		struct llist_node	fu_llist;
    		struct rcu_head 	fu_rcuhead;
    	} f_u;
        //存储文件在目录树中的位置,下面贴代码
    	struct path		f_path;
        //指向文件的索引节点
    	struct inode		*f_inode;	/* cached value */
        //指向文件操作命令
    	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 */
    
    struct file_handle {
    	__u32 handle_bytes;
    	int handle_type;
    	/* file identifier */
    	unsigned char f_handle[0];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    解释一下为什么有private_data成员。由于挂载目录下可能会有多个文件,而read/write回调是针对文件系统的操作,如果对不同文件进行读写操作,那么这些不同的文件数据就无法区分。解决措施就是在inode成员里加入一个成员,因为inode是每个文件都有一个实例,所以这个成员可以实现私有空间的功能。
    path结构体

    struct path {
        struct vfsmount *mnt; // 指向文件所属文件系统的挂载描述符的成员 mnt
        struct dentry *dentry; // 文件对应的目录项
    };
    
    • 1
    • 2
    • 3
    • 4

    文件系统信息结构

    struct fs_struct {
    	int users;
    	spinlock_t lock;
    	seqcount_t seq;
    	int umask;
    	int in_exec;
        //root存储进程的根目录,pwd存储进程当前的工作目录
    	struct path root, pwd;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    打开文件表真正记录哪些文件描述符被使用了,哪些是空闲的,实际是一个文件描述符位图,每1bit表示了一个文件描述符。

    struct fdtable {
    	unsigned int max_fds;    /*fdtable能管理的打开文件最大数量,由位图大小决定*/
    	struct file __rcu **fd;  /*指向file指针数组的指针*/
    	unsigned long *close_on_exec;  /*执行execve()系统调用时关闭文件的位图*/
    	unsigned long *open_fds;  /*进程打开文件位图*/
    	unsigned long *full_fds_bits;
    	struct rcu_head rcu;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这些数据结构之间的关系
    在这里插入图片描述

    注册文件系统类型

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

    /**
     *	register_filesystem - register a new filesystem
     *	@fs: the file system structure
     *
     *	Adds the file system passed to the list of file systems the kernel
     *	is aware of for mount and other syscalls. Returns 0 on success,
     *	or a negative errno code on an error.
     *
     *	The &struct file_system_type that is passed is linked into the kernel 
     *	structures and must not be freed until the file system has been
     *	unregistered.
     */
     
    int register_filesystem(struct file_system_type * fs)
    {
    	int res = 0;
    	struct file_system_type ** p;
    
    	BUG_ON(strchr(fs->name, '.'));
    	if (fs->next)
    		return -EBUSY;
    	write_lock(&file_systems_lock);
    	p = find_filesystem(fs->name, strlen(fs->name));  //这里遍历的链表头是全局变量
    	if (*p)
    		res = -EBUSY;
    	else
    		*p = fs;
    	write_unlock(&file_systems_lock);
    	return res;
    }
    
    EXPORT_SYMBOL(register_filesystem);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

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

  • 相关阅读:
    AST反混淆实战|某国外混淆框架一小段混淆js还原分析
    【云原生之Docker实战】使用Docker部署moredoc文库系统
    IDEA(2023)修改默认缓存目录
    Android Framework 核心,为何初级开发需要重点学习Framework?
    Doris删库元数据删除怎么办?紧急恢复单副本情况
    工程(十二)Ubuntu20.04LSD_SLAM运行
    顺序表的应用-通讯录
    为什么五千块天价加急费都无法保证交期?
    还不会Nginx?京东技术官精心准备的一份Nginx高并发神器送给你
    HTML学生个人网站作业设计——中华美食(HTML+CSS) 美食静态网页制作 WEB前端美食网站设计与实现
  • 原文地址:https://blog.csdn.net/m0_65931372/article/details/126300841