我是靠谱客的博主 柔弱发卡,最近开发中收集的这篇文章主要介绍Linux 文件系统解析(二)fs驱动实现文件系统类型关键接口EXT4总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在VFS框架下编写一个文件系统是十分方便的,只需要实现VFS需要用到的接口即可,大量磁盘文件系统被加入了进来。除此之外,很多“虚拟”的文件系统也被创造出来,为内核提供了很多重要特性。本篇来梳理一下Linux文件系统的类型,以及以ext4为例来看看一个文件系统驱动是如何被实现的。

文件系统类型

再来回顾一下之前梳理的文件系统框架:

文件系统根据底层操作的硬件,基本可分为四类:

普通文件系统

传统意义的磁盘文件系统,是文件系统驱动占比最大的部分,用于访问磁盘上的数据。

内存文件系统

底层存储介质是内存的文件系统,常见的是tmpfs和ramfs。系统中/tmp目录被挂载为tmpfs,主要用于存放临时文件,系统重启即会清空。内存文件系统还在kernel启动时起到关键作用,kernel启动时各分区及关键驱动可能还没加载,此时通常先挂载一个基于内存的根文件系统initramfs,在其中完成系统初始化工作,再将rootfs切换到真正的根文件系统磁盘。

网络文件系统

nfs可以基于网络来共享本地数据或者访问远端设备的数据。nfs并不处理文件系统的实际操作,只是提供基于rpc协议的网络客户端与服务端程序。当共享本地数据时启动服务端,将收到的网络请求转换为对共享目录的操作。当访问远端设备时启动客户端,将文件读写等操作转换为网络请求发出。

特殊文件系统

Linux中的"文件",除了代表磁盘上真实的文件,还可以是对系统及硬件资源操作访问的入口。Linux用一些特殊的文件系统来管理这些文件。例如,devtmpfs用于管理设备节点文件,proc,sysfs中的文件用于访问内核运行时的一些数据结构。

关键接口

VFS框架提供了大量接口,编写一个文件系统驱动不需要全部实现,VFS也提供一些通用的接口可以直接使用。这里列出一些关键接口及其作用。

file_system_type

struct file_system_type用于描述一个文件系统类,是最先需要定义的结构,其中最重要的是实现mount接口。

  • mount() , 挂载一个该类型文件系统实例时被调用,创建super_block以及根目录dentry,并初始化。

super_operations

管理文件系统整体信息的接口,主要功能是对inode资源的管理。

  • alloc_inode(),在内存中分配一个inode并初始化,通常文件系统驱动会自己定义一个更大的结构并内嵌inode。
  • write_inode(),将一个inode写入磁盘。
  • evict_inode(),删除一个inode。

inode_operations

管理inode之间关系的接口

  • lookup(),在一个inode的子inode中查找指定名字的inode。
  • mkdir(),在一个inode下创建目录inode。
  • rmdir(),删除一个目录inode。
  • creat(),在一个inode下创建普通文件inode。
  • link(),创建硬链接。
  • unlink(),删除一个inode。

file_operations

对一个打开的文件进行操作的接口,大部分接口会封装成系统调用提供给应用层使用。

  • read_iter(),将磁盘数据读入iov_iter。
  • write_iter(),将iov_iter数据写入磁盘。

address_space_operations

磁盘上文件数据到内存的读写操作接口。

  • readpage()/readpages(),将磁盘数据读到内存。
  • writepage()/writepages(),将内存数据写入磁盘。
  • directio(),不经过pagecache读写数据。

EXT4

第 1 扩展文件系统(ext1)是专门针对Linux设计的文件系统,也是第一个使用虚拟文件系统(VFS)的文件系统,ext系列文件系统设计之初就考虑了与VFS的配合,使其在Linux上表现出良好的性能与可靠性,被众多发行版作为默认文件系统。ext4是最新一代扩展文件系统,通过它的实现来看看一个文件系统驱动是如何编写的。

磁盘数据结构

通常所说的磁盘文件系统,其本质是一种组织管理数据的规则,目的是在一段连续的存储空间内实现目录,文件这样的树形结构。为了后续描述其实现,这里简单介绍一下ext4的磁盘数据结构,更详细的可以参看这篇文章https://www.cnblogs.com/f-ck-need-u/p/7016077.html。

结构图如下:

ext4中用inode来表示一个“文件”,“文件”的含义与在linux中一致包含普通文件,目录等7种类型。在ext4中与一个文件相关的数据有两部分,一部分是文件真实包含的数据,还一部分是其inode数据,其中包含文件大小,属性,更新时间等参数以及指向其数据块的指针。ext4文件系统创建时已经设置了inode最大数量,每个inode分配一个唯一的inode num,用于查找inode。其中一些特殊inode的inode num是固定的,如根目录inode num=2 ,日志inode num=8 。

ext4将磁盘空间划分为GROUP来管理。其中各结构功能如下:

  • superblock:记录文件系统的整体信息,包含大小,inode数量,block大小等等,在多个group中有备份。
  • GDT:记录所有GROUP的信息,包含GROUP的起始block位置,包含inode num范围,可用block,inode数量等,在多个group中有备份。
  • Reserved GDT:GDT的保留空间,为以后扩展准备,在多个group中有备份。
  • Block Bitmap:以bitmap形式记录当前GROUP中block的使用情况。
  • Inode Bitmap:以bitmap形式记录当前GROUP中inode的使用情况。
  • Inode Table:顺序保存当前GROUP中所有inode数据。
  • Data Block:当前GROUP的数据block。

文件查找

对于ext4中的inode,唯一的索引就是它的inode num。当要访问一个文件时,如果已经知道了inode num,先根据GDT的信息找到其所在的GROUP,再在Inode Table中根据偏移找到inode数据,再按照inode中记录的数据块指针找到要访问的数据。inode的inode num,记录在其父inode即其所在目录的inode的数据块中。

假设要查找/file/1.bin的inode,流程如下:

  1. 先根据inode num=2找到跟目录inode
  2. 根据inode中的数据块指针找到其数据块,其中保存了根目录下inode的文件名和inode num,找到file目录的inode num=i1
  3. 根据 i1找到file目录的inode
  4. 读取file目录的数据块找到1.bin 的inode num=i2
  5. 根据 i2 找到1.bin 的inode

关键函数

ext4文件系统看似结构复杂,但其实对于文件系统的大部分操作,基本上都可以用两个步骤反复组合来完成,

  • 根据inode num查找inode。
  • 查找inode数据块,并读写。

这里介绍几个关键的函数。

ext4_fill_super()

在mount时被调用,这是一个超过1000行的庞大函数,super_block的数据在这里被初始化。其中会将磁盘上SUPER BLOCK 和GDT数据读到内存,以便后续使用及更新,为之后操作提供了基础(扩展阅读:EXT4分区mount状态下也可以用tune2fs修改属性吗?)。还将根目录inode读取并保存起来,作为inode查找的起点。

__ext4_iget()

根据inode num查找inode,该函数返回的是vfs的inode结构,但此时该inode已经被内嵌到ext4_inode_info结构中,其中包含了ext4 inode数据,可由EXT4_I(inode)获取。

ext4_map_blocks()

用于查找inode的数据块,查找方式是通过一个ext4_map_blocks结构。

struct ext4_map_blocks {
	ext4_fsblk_t m_pblk;       //起始物理块号(返回参数)
	ext4_lblk_t m_lblk;        //起始逻辑块号 (传入参数)
	unsigned int m_len;        //逻辑块数量,同时这个参数也作为返回值保存实际映射物理块的长度
	unsigned int m_flags;      //块状态(返回参数)
};

该函数负责查找或者创建与文件逻辑块在磁盘上对应的物理块。大致流程是,首先找磁盘上是否有已经对应的块,若没有再根据传入flag决定是否要在磁盘上分配新的块。为兼容ext2,ext3,其中有两种数据块管理方式,ext4_ext_map_blocks(extent tree),ext4_ind_map_blocks(inode,映射)。

__ext4_new_inode()

用于分配一个ext4 inode,inode结构在文件系统格式化时已经创建好了,分配大致流程是,选择一个group在其inode table中选择一个未使用的inode,初始化inode参数并建立与其相关结构的联系,更新与该inode相关的inode,group,superblock等结构的数据,并同步到磁盘。

VFS接口实现

有了这些基础函数,最后来简要看下在ext4中VFS的接口是如何被实现的。

file_system_type

ext4_mount:调用mount_bdev()通用接口,主要初始化操作在ext4_fill_super()中实现。

super_operations

ext4_alloc_inode:分配一个ext4_inode_info结构,将其中内嵌的inode结构返回。

ext4_write_inode:若使用日志由jbd2驱动完成inode同步,否则由__ext4_get_inode_loc()找到inode数据块直接更新。

inode_operations

ext4_lookup:找到当前目录inode的数据块,根据文件名在其中找到要查找的inode num,由ext4_iget()找到inode。

ext4_mkdir:由__ext4_new_inode()新建inode,并设置其inode_operations,file_operations为目录操作方法。

ext4_cread:由__ext4_new_inode()新建inode,并设置其inode_operations,file_operations为普通文件操作方法。

ext4_unlink/ext4_rmdir:删除该inode的引用关系,将inode放入orphan list。

file_operations

ext4_file_read_iter:调用通用接口generic_file_read_iter()。

ext4_file_write_iter:调用通用接口__generic_file_write_iter()。

address_space_operations

ext4_readpage:根据偏移计算出数据在文件中的起始逻辑块及长度,由ext4_map_blocks()找到磁盘上的物理块,提交IO读取。

ext4_write_begin:在pagecache中查找或分配文件映射的内存页,并由ext4_map_blocks()找到在磁盘上对应的物理块。

ext4_write_end:标识写入的page为“脏”,更新文件大小等数据。

ext4_writepage:提交IO,将pagecache内存页写入磁盘。

 

至此ext4驱动的主体流程就介绍完了,所有的设计都是为了高效的实现VFS需求的接口。然而ext4的驱动是相当复杂的,有太多的细节以及特性没有涉及,实际流程还是要参看具体代码。

总结

本篇主要通过ext4,简要介绍了如何编写一个磁盘文件系统驱动,核心是实现VFS需求的各种接口,接口之下可以是对磁盘,网络,内存等等资源的管理,这使得在Linux中文件系统被极大扩展,实现了丰富多样的功能。

文件系统的代码中,往往能看着这样的流程,“优先在cache中查找,查找不到再去访问实际的资源”,这种设计显著的提高了文件系统的性能,下篇来梳理下这一部分,文件系统如何与内存子系统一起管理cache。

 

 

参考资料

https://www.ibm.com/developerworks/cn/linux/l-anatomy-ext4/

https://www.cnblogs.com/f-ck-need-u/p/7016077.html

 

最后

以上就是柔弱发卡为你收集整理的Linux 文件系统解析(二)fs驱动实现文件系统类型关键接口EXT4总结的全部内容,希望文章能够帮你解决Linux 文件系统解析(二)fs驱动实现文件系统类型关键接口EXT4总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部