Linux文件系统详解

文件系统

操作系统三大特性:虚拟,并发,持久
文件系统对应持久,即将信息持久保存,为了实现文件保存,读取等相关功能,需要实现一个文件系统,针对不同的设备,文件系统自然也要不一样,但是对于编程用户来说,自然希望一套文件系统API通用,减小编程麻烦。
结合上述情况,Linux的文件系统大致架构如下:
在这里插入图片描述

图片来着wiki百科。
我们目前只需要关注VFS即可,其他的暂时用不到,左边的LIO是Linux中的IO模块,右边的nvmet模块是Linux中负责将该设备nvme设备提供到远程网络设备使用的模块,下面的block layer是块设备驱动的统一接口层。【像硬盘,软盘,CD这种就是块设备,一次读取固定大小的一块】

VFS下对接四种类型的文件系统,包括:

  • block fs:块设备文件系统,如ext2,ext3,ext4,fat等
  • network fs:网络文件系统,如nfs
  • presudo fs:虚拟文件系统,如procfs,pipefs,mqueue等。他们不对应任何实际的物理存储介质,而是在内存中动态生成的。
  • special purpose fs:特殊用途文件系统,如devfs,专门用于存储设备文件。

下图是简化版本的VFS文件系统的图
在这里插入图片描述

VFS虚拟文件系统

Linux的虚拟文件系统在上层看来,就是一个统一接口的文件系统,在下层看来

VFS系统是由C语言写的,其中中共有4个抽象数据类型

超级块super_block

对于磁盘类文件系统,超级块是存放在磁盘上的文件系统控制块,里面存放已安装文件系统的有关信息,也叫元数据,与普通的文件数据相比,元数据丢失会损坏整个文件系统,导致无法挂载之类的问题。
super_block源码【/linux/include/linux/fs.h】【节选】

struct super_block {
	struct list_head	s_list;		//超级块链表指针
	dev_t			s_dev;		//设备标识符
	unsigned char		s_blocksize_bits; //以位为单位的块的大小
	unsigned long		s_blocksize; //以字节为单位的块的大小
	loff_t			s_maxbytes;	//文件大小的上限
	struct file_system_type	*s_type; //指向文件系统的file_system_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; //魔术码,每个文件系统都有一个独特的魔术码,类似于物种id一样哈哈哈哈
	struct dentry		*s_root; //根目录的目录文件指针
	struct rw_semaphore	s_umount;
	int			s_count; //引用计数
	atomic_t		s_active; //活动引用计数

	/*
	 * The next field is for VFS *only*. No filesystems have any business
	 * even looking at it. You had been warned.
	 * 大佬们真贴心啊,甚至标注出来了,下面是关于VFS的,其他的文件系统不需要看
	 */
	struct mutex		s_sync_lock;	//互斥锁

	/* s_inode_list_lock protects s_inodes */
	spinlock_t		s_inode_list_lock ____cacheline_aligned_in_smp;
	struct list_head	s_inodes;	//用于存储该文件系统中所有的索引节点(inode)

	struct list_head	s_inodes_wb;	/* writeback inodes */
} __randomize_layout;

以上的代码只是节选一些与实现逻辑相关的成员变量,目前Linux已经非常庞大了,全读一遍没有意义。
看到19年前的代码,内心还是很震撼的,跟我差不多年纪的代码
在这里插入图片描述

索引节点inode

索引节点存放关于具体文件的一般信息。对于磁盘类文件系统,索引节点也是存放在磁盘上的文件控制块。每个索引节点都有一个索引节点号,这个节点号唯一地标识了文件系统中的文件。
每个文件(和目录)都有且只有一个对应的indoe,其中包含元数据(如访问权限、上次修改的日期,等等)和指向文件数据的指针。但inode并不包含一个重要的信息项,即文件名。文件名存储在dentry中
软链接文件也都会有一个独立的inode,里面存储着指向源文件的指针。硬链接会使用已有的inode。

源码【/linux/include/linux/fs.h】【节选】

struct inode {
	umode_t			i_mode; //访问权限控制,我们常用的chmod 777,根源就在这里
	unsigned short		i_opflags; //存储了 inode 操作的标志位。在 Linux 内核中,inode 结构体中的操作函数指针(如读取、写入、创建、删除等函数指针)可能会被缓存或预先计算,而这个标志位则用于标识 inode 上的这些操作是否可用或已经初始化。
	kuid_t			i_uid; //使用者id
	kgid_t			i_gid; //使用组id
	unsigned int		i_flags; //文件系统标识

	const struct inode_operations	*i_op; //指向索引系欸但操作结构体的指针
	struct super_block	*i_sb; //指回super_block,一些对inode本身的操作会用到
	struct address_space	*i_mapping; //相关地址映射

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino; //索引节点号,可以使用ls -i查看
	/*
	 * 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; //硬链接计数,软链接有单独的inode,不需要
		unsigned int __i_nlink;
	};
	dev_t			i_rdev; //设备信息
	loff_t			i_size; //文件大小信息
	struct timespec64	__i_atime; //最后访问时间
	struct timespec64	__i_mtime; //最后修改时间
	struct timespec64	__i_ctime; /*状态变化时间,大佬提示: use inode_*_ctime accessors! */
	spinlock_t		i_lock;	//自旋锁
	unsigned short          i_bytes; //存储文件数据的实际字节数。它记录了文件所占用的数据块中的有效字节数。
	struct list_head	i_lru;		//最近最少使用链表
	struct list_head	i_sb_list;  // 超级块链表
	struct list_head	i_wb_list;	//写回链表,当文件被修改后,就会链接到写回链表

	struct address_space	i_data; // inode 对应的文件的地址空间(address space)
	struct list_head	i_devices; //用于将 inode 结构体连接到设备相关的链表中。
	union {
		struct pipe_inode_info	*i_pipe; //当文件是一个管道文件时,存储管道对象的指针
		struct cdev		*i_cdev; //当文件是一个设备文件时,存储设备对象的指针
		char			*i_link; //当文件时一个链接文件时,指向目标文件路径字符串的指针
		unsigned		i_dir_seq; //当文件是一个目录时,保存目录的序列号
	};

} __randomize_layout;

inode是存储在实际介质【磁盘】中的,在需要使用时读取的内存中,有一些成员变量在存储时是没有的,是读取到内存中时内核动态生成的

目录对象dentry

存放 dentry 与对应文件链接的有关信息,每个 dentry 代表路径中的一个特定部分,每个磁盘类文件系统以自己的方式将目录项信息存放在磁盘上。
源码【linux/include/linux/dcache.h】【节选】

struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* 包含有关dentry状态的标志位。protected by d_lock */
	struct dentry *d_parent;	/* 指向父目录 parent directory */
	struct qstr d_name; //名称
	struct inode *d_inode;		/* 指向与该目录有关的inode。Where the name belongs to - NULL is
					 * negative */

	/* 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;	/* 指向根superblock,The root of the dentry tree */

	union {
		struct list_head d_lru;		/* LRU列表 LRU list */
		wait_queue_head_t *d_wait;	/* 等待列表表头,in-lookup ones only */
	};
	struct hlist_node d_sib;	/* 兄弟目录的哈希表节点信息,child of parent list */
	struct hlist_head d_children;	/*子目录的哈希表信息 our children */
};

文件对象file

存放被打开文件与进程间交互的信息,这类信息仅当进程访问文件期间存放在内存中。
源码【/linux/include/linux/fs.h】【节选】

struct file {

	/*
	 * Protects f_ep, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	
	fmode_t			f_mode; //文件打开模式
	atomic_long_t		f_count; //文件计数
	struct mutex		f_pos_lock; //锁,用于保护f_pos
	loff_t			f_pos; //文件偏移量,用于文件随机读取,可以直接从便宜位置开始读取
	unsigned int		f_flags; //文件状态flag
	struct fown_struct	f_owner; //owner
	struct path		f_path; //路径信息,这是一个数据结构,不是一个字符串
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/* 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 hlist_head	*f_ep; //与epoll相关的机制,将所有的钩子hook链接到当下的文件上
#endif /* #ifdef CONFIG_EPOLL */
} __randomize_layout
  __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */

总结下来,VFS做的事情就是用来提供一种操作文件,目录,以及其他对象的统一方法,并与底层的具体文件系统实现达成妥协。
所有有的文件系统并不支持VFS中的所有抽象,因此使用这些文件系统的性能就会受到影响,例如FAT,Linux支持最好的是EXT系列的文件系统。VFS的结构设计就是和EXT2非常相似的,从EXT2转到VFS基本不会损失时间【《深入Linux内核架构》这本书写的嗷,这本书讲的还是2.65的代码结构,目前是否还适用自行甄别】
请添加图片描述
Linux VFS主要数据结构之间的关系,图片来自《深入Linux内核架构》

文件系统进程相关部分

上面讲的都是关于文件系统本身的,是面向操作系统的,那么一个普通用户进程如何读取文件呢?以及如何进行互斥和文件安全保护呢?
Linux的每一个进程都会有一个task_struct结构

struct task_struct { 
... 
/* 文件系统信息 */ 
        struct fs_struct *fs;  //进程的文件系统相关数据
/* 打开文件信息 */ 
        struct files_struct *files;  //包含当前进程打开的文件的文件描述符
/* 命名空间 */ 
        struct nsproxy *nsproxy; 
... 
} 

其中的file_struct的结构如下:

struct files_struct { 
        atomic_t count; 
        struct fdtable *fdt; 
        struct fdtable fdtab; //fdtable

        int next_fd;  //表示下一次打开新文件时使用的文件描述符
        struct embedded_fd_set close_on_exec_init; //位图,执行exec时要关闭的文件描述符对应的bit置1
        struct embedded_fd_set open_fds_init;  //init时最初的文件描述符集合
        //struct embedded_fd_set只是一个简单的unsigned long整数
        struct file * fd_array[NR_OPEN_DEFAULT];  //每个数组项都是一个指针,指向每个打开文件的struct file实例
        //NR_OPEN_DEFAULT表示默认允许打开文件标识符数量,
        //定义处:#define NR_OPEN_DEFAULT BITS_PER_LONG,32位最大打开32个文件,64位最大打开64个
        //这里这么设定的原因是因为上面的位图是用unsigned long存储的,所以用long的长度来作为默认允许打开数量,如果要打开更多操作系统会将embedded_fd_set换成fd_set,
}; 

在files_struct中还嵌入着一个数据结构:fdtable,这个数据结构内部都是指针,它的主要作用是当文件多于NR_OPEN_DEFAULT时,指针指向一个新的fd table。

struct fdtable { 
        unsigned int max_fds; 
        struct file ** fd; /* 当前fd_array,默认初始化时指向file_struct的fd_array,但是如果文件太多时就会新开一个fd array */ 
        fd_set *close_on_exec; 
        fd_set *open_fds; 
        struct rcu_head rcu; 
        struct files_struct *free_files; 
        struct fdtable *next; 
}; 

在这里插入图片描述
到file这一步,算是和上面的操作系统级文件系统结构接上头了。

Linux文件目录

Linux文件目录架构如下:

  • bin:一些最基础的可执行文件,例如ls
  • boot:存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。
  • dev:设备相关文件,根据Linux的哲学,设备也是文件
  • etc:存放所有的系统管理所需要的配置文件和子目录。
  • home:用户文件夹,每个用户都有一个自己的目录,一般该目录名是以用户的账号命名的。
  • lib:这个目录里存放着系统最基本的动态连接共享库
  • mnt:系统提供该目录是为了让用户临时挂载别的文件系统的
  • opt:这是给主机额外安装软件所摆放的目录。
  • proc:这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录来获取系统信息。这个目录的内容不在硬盘上而是在内存里
  • root:该目录为系统管理员,也称作超级权限者的用户主目录。
  • usr:用户的很多应用程序和文件都放在这个目录下,类似于windows下的program files目录。
  • var:这个目录中存放着在不断扩充着的东西,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
  • srv:该目录存放一些服务启动之后需要提取的数据。
  • sys:这是linux2.6内核的一个很大的变化。该目录下安装了2.6内核新出现的一个文件系统 sysfs ,sysfs文件系统集成了下面3种文件系统的信息:针对进程信息的proc文件系统、针对设备的devfs文件系统以及针对伪终端的devpts文件系统。该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象子系统中被创建。
  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值