Linux文件系统6(基于6.1内核)(完结篇)

Linux文件系统6(基于6.1内核)---深入理解文件系统总结


一、那么什么是文件系统?

文件系统是对一个存储设备上的数据和元数据进行组织的机制。Linux系统中每个分区都是一个文件系统,都有自己的目录层次结构 。(df && cat /proc/filesystems)

文件系统是一种用于组织和存储数据在计算机存储设备(如硬盘、固态硬盘、U盘等)上的结构和方法。它提供了一套规则和机制,使操作系统和用户能够高效、方便地管理和访问数据。文件系统通常包含以下几个关键要素:

  1. 文件:文件是数据的基本单位,可以包含文本、图像、音频、视频等多种类型的数据。每个文件都有一个唯一的名称和路径,用于标识和访问。

  2. 目录(文件夹):目录用于组织文件,可以将多个文件分组放在一起,便于管理和查找。目录还可以包含其他目录,形成层次结构(树形结构)。

  3. 命名规则:文件系统定义了文件的命名规则和目录的命名规则,如允许使用的字符、字符长度限制、区分大小写等。

  4. 存储结构:文件系统定义了数据在存储设备上的存储方式。例如,文件数据可能以连续块存储(顺序文件),也可能以链表形式存储(链式文件),或者通过索引表进行访问(索引文件)。

  5. 元数据:每个文件和目录都包含一些元数据(metadata),如创建时间、修改时间、文件大小、文件类型、权限等。元数据用于帮助操作系统和用户管理文件。

  6. 访问控制:文件系统提供访问控制机制,允许操作系统和应用程序设置文件的权限,决定哪些用户可以读取、写入或执行文件。

  7. 文件系统类型:不同的操作系统可能使用不同的文件系统类型,例如FAT(File Allocation Table)、NTFS(New Technology File System)、ext4(Fourth Extended Filesystem)、XFS(eXtended File System)等。每种文件系统类型都有其特定的特点和优势。

文件系统是操作系统和应用程序与存储设备之间的接口,它使得数据的存储、检索和管理变得高效和便捷。通过文件系统,用户可以创建、删除、修改文件,以及创建、删除和导航目录结构,从而方便地管理和使用存储在计算机上的数据。

二、Linux 文件系统的体系结构

下图所示的体系结构显示了用户空间和内核中与文件系统相关的主要组件之间的关系。

用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点。

VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象。

每个文件系统实现(比如 ext2、fat 等等)导出一组通用接口,供 VFS 使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度。以最近使用(LRU)列表的形式管理缓冲区缓存。注意,可以使用 sync 命令将缓冲区缓存中的请求发送到存储媒体(迫使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。

最后来一个实例说明:不同文件系统间的文件拷贝


这就是 VFS 和文件系统组件的高层情况。现在,主要讨论VFS系统的主要结构。

三、VFS 虚拟文件系统

3.1、虚拟文件系统层

VFS 作为文件系统接口的根层。VFS 记录当前支持的文件系统以及当前挂装的文件系统。

可以使用一组注册函数在 Linux 中动态地添加或删除文件系统。内核保存当前支持的文件系统的列表,可以通过 /proc 文件系统在用户空间中查看这个列表。这个虚拟文件还显示当前与这些文件系统相关联的设备。在 Linux 中添加新文件系统的方法是调用 register_filesystem。这个函数的参数定义一个文件系统结构(file_system_type)的引用,这个结构定义文件系统的名称、一组属性和两个超级块函数。也可以注销文件系统。

在注册新的文件系统时,会把这个文件系统和它的相关信息添加到 file_systems 列表中(include/linux/fs.h)。这个列表定义可以支持的文件系统。在命令行上输入 cat /proc/filesystems,就可以查看这个列表。

向内核注册的文件系统:

VFS 中维护的另一个结构是挂装的文件系统。这个结构提供当前挂装的文件系统(include/linux/mount.h)。它链接下面讨论的超级块结构。

挂装的文件系统列表:

3.2超级块

超级块结构表示一个文件系统。它包含管理文件系统所需的信息,包括文件系统名称(比如 ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)。超级块通常存储在存储媒体上,但是如果超级块不存在,也可以实时创建它。可以在 include/linux/fs.h 中找到超级块结构。

超级块结构和 inode 操作:

超级块中的一个重要元素是超级块操作的定义。这个结构定义一组用来管理这个文件系统中的 inode 的函数。例如,可以用 alloc_inode 分配 inode,用 destroy_inode 删除 inode。可以用 read_inode 和 write_inode 读写 inode,用 sync_fs 执行文件系统同步。可以在 ./linux/include/linux/fs.h 中找到 super_operations 结构。每个文件系统提供自己的 inode 方法,这些方法实现操作并向 VFS 层提供通用的抽象。

3.3、inode 和 dentry

inode 表示文件系统中的一个对象,它具有惟一标识符。各个文件系统提供将文件名映射为惟一 inode 标识符和 inode 引用的方法。图 5 显示 inode 结构的一部分以及两个相关结构。请特别注意 inode_operations 和 file_operations。这些结构表示可以在这个 inode 上执行的操作。inode_operations 定义直接在 inode 上执行的操作,而 file_operations 定义与文件和目录相关的方法(标准系统调用)。

inode 结构和相关联的操作:

inode 和目录缓存分别保存最近使用的 inode 和 dentry。注意,对于 inode 缓存中的每个 inode,在目录缓存中都有一个对应的 dentry。include/linux/fs.h中找到 inode 和 dentry 结构。

3.4、缓冲区缓存

除了各个文件系统实现(可以在fs 中找到)之外,文件系统层的底部是缓冲区缓存。这个组件跟踪来自文件系统实现和物理设备(通过设备驱动程序)的读写请求。为了提高效率,Linux 对请求进行缓存,避免将所有请求发送到物理设备。缓存中缓存最近使用的缓冲区(页面),这些缓冲区可以快速提供给各个文件系统。

四、与进程相关的一个对象、两个结构

  • 文件对象(见上文)
  • 用户打开文件表 files_struct 结构
  • 建立进程与文件系统的关系 fs_struct 结构

(1) 文件对象。

(2) 用户打开文件对象集 files_struct 结构。

文件描述符是用来描述打开的文件的。每个进程用一个files_struct结构来记录文件描述符的使用情况,这个files_struct结构称为用户打开文件表,它是进程的私有数据。

include/linux/fdtable.h 

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
	atomic_t count;
	bool resize_in_progress;
	wait_queue_head_t resize_wait;

	struct fdtable __rcu *fdt;
	struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
	spinlock_t file_lock ____cacheline_aligned_in_smp;
	unsigned int next_fd;
	unsigned long close_on_exec_init[1];
	unsigned long open_fds_init[1];
	unsigned long full_fds_bits_init[1];
	struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

(3) 建立进程与文件系统的关系 fs_struct 结构。

fs_struct 结构描述进程与文件系统的关系。

include/linux/fs_struct.h 

struct fs_struct {
	int users;
	spinlock_t lock;
	seqcount_spinlock_t seq;
	int umask;
	int in_exec;
	struct path root, pwd;
} __randomize_layout;

五、四个对象与进程是如何协同工作的

被Linux支持的文件系统,不管它有零个或多个实例被安装到系统中,都有且仅有一 个file_system_type结构。每安装一个文件系统,就对应有一个超级块和安装点。超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的 文件系统通过file_system_type中的一个域fs_supers链接具有同一种文 件类型的超级块。同一种文件系统类型的超级块通过域s_instances链 接。

       超级块、安装点和具体的文件系统的关系

进程通过task_struct中的一个域files_struct files来了解它当前所打开的文件对象;而我们通常所说的文件 描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找 到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。最后,还有一点很重要的是, 文件对象所对应的文件操作函数 列表是通过索引结点的域i_fop得到的。图6对第三部分源码的理解起到很大的作用。

       进程与超级块、文件、索引结点、目录项的关系



好了,基本的也就这么多了,下面以read()函数为例来总结一下总的调用过程。

六、总结

read函数在用户空间是由read系统调用实现的,由编译器编译成软中断 int 0x80 来进入内核空间,然后在中断门上进入函数sys_read,从而进入内核空间执行read操作。
sys_read函数定义在fs/read_write.c文件。

进程打开一个文件时,会在内存组装一个文件对象,希望对该文件执行的操作方法已在文件对象设置好。所以对文件进行读操作时,VFS在做了一些简单的转换后(由文件描述符得到其对应的文件对象;其核心思想是返回current->files->fd[fd]所指向的文件对象,在函数 fget_light() 中进行的),就可以通过语句 file->f_op->read(file, buf, count, pos) 轻松调用实际文件系统的相应方法对文件进行读操作了。

具体代码调用如下:

1.进入陷阱门调用到函数 ksys_read()
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	return ksys_read(fd, buf, count); //最新的内核中的名字就是这个
}
2. 以下的都是旧版内核代码,但是意思真的一样,哈哈哈
ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
	struct file *file;/*文件指针*/
	ssize_t ret = -EBADF;
	int fput_needed;
	
	/*轻量级的由文件描述符得到文件指针函数*/
	file = fget_light(fd, &fput_needed);
	if (file) {
		/*file结构体里的指示文件读写位置的int变量读取*/
		loff_t pos = file_pos_read(file);
		/*vfs虚拟文件系统实现read操作的地方*/
		ret = vfs_read(file, buf, count, &pos);
		/*file结构体里的指示文件读写位置的int变量写入*/
		file_pos_write(file, pos);
		/*释放file结构体指针*/
		fput_light(file, fput_needed);
	}
	return ret;
}
3. vfs_read()函数
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
	ssize_t ret;
	/*首先检查文件是否可以读取,否则返回坏的文件描述符标记*/
	if (!(file->f_mode & FMODE_READ))
		return -EBADF;
	/*如果没有对应的文件操作函数集合,也返回错误*/
	if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
		return -EINVAL;
	/*检查有没有权限*/
	if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
		return -EFAULT;
	/*检查当前写入的地方有没有被上锁,是否可读写*/
	ret = rw_verify_area(READ, file, pos, count);
	if (ret >= 0) {
		count = ret;
		/*安全操作*/
		ret = security_file_permission (file, MAY_READ);
		if (!ret) {
			/*如果file结构体里有read函数,就调用*/
			if (file->f_op->read)
				ret = file->f_op->read(file, buf, count, pos);
			else
				/*否则就调用异步读取的*/
				ret = do_sync_read(file, buf, count, pos);
			if (ret > 0) {
			/*成功读取以后,通知父目录已经读取,并在当前进程结构体上记录*/
				fsnotify_access(file->f_path.dentry);
				add_rchar(current, ret);
			}
			inc_syscr(current);
		}
	}
	return ret;
}

附录:fget_light()函数
struct file fastcall *fget_light(unsigned int fd, int *fput_needed)
{
	struct file *file;
	/*得到当前进程的task_struct的打开的files指针*/
	struct files_struct *files = current->files;
	
	*fput_needed = 0;
	/*如果只有一个进程使用这个结构体,就不必考虑锁,否则要先得到锁才可以读取*/
	if (likely((atomic_read(&files->count) == 1))) {
		/*从files结构体的fd数组上得到file结构体*/
		file = fcheck_files(files, fd);
	} else {
		/*先上锁,在得到对应结构体*/
		rcu_read_lock();
		file = fcheck_files(files, fd);
		if (file) {
			if (atomic_inc_not_zero(&file->f_count))
				*fput_needed = 1;
			else
				/* Didn't get the reference, someone's freed */
				file = NULL;
		}
		rcu_read_unlock();
	}
	return file;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值