如何实现一个文件系统(五)

实例

文件系统实在是个庞杂的“怪物”,我很难编写一个恰当的例子来演示文件系统的实现。开始我想写一个纯虚文件系统,但考虑到它几乎没有实用价值,而且更重要的是虚文件系统不涉及I/O操作,缺少现实文件系统中至关重要的部分,所以放弃了;后来想写一个实际文件系统,但是那样工程量太大,而且也不容易让大家简明扼要地理解文件系统的实现,所以也放弃了。最后我发现内核中提供的romfs文件系统是个非常理想的实例,它既有实际应用,结构也清晰明了,所以我们以romfs为实例分析文件系统的实现。

Linux文件系统实现要素

编写新文件系统需要一些基本对象[1][16]。具体而言,创建文件系统需要建立“一个结构四个操作表”:

n         文件系统类型结构(file_system_type)、

n         超级块操作表(super_operations)、

n         索引节点操作表(inode_operations)、

n         页高速缓存(address_space_operations)、

n         文件操作表(file_operations)。

对上述几种结构的处理贯穿了文件系统的主要操作过程,理清晰这几种结构之间的关系是编写文件系统的基础,下来我们具体分析这几个结构和文件系统实现的要点。

 

 

你必须首先建立一个文件系统类型结构来“描述”文件系统,它含有文件系统的名称、类型标志以及get_sb等操作。当你安装文件系统时(mount)时,系统会解析“文件系统类型结构”,然后使用get_sb函数来建立超级节点“sb”,注意,对于基于块的文件系统,如ext2、romfs等,需要从文件系统的宿主设备读入超级块以便在内存中建立对应的超级节点,如果是虚文件系统的话,则不是读取宿主设备的信息(因为它没有宿主设备),而是在现场创建一个超级节点,这项任务由get_sb完成。

超级节点可谓是一切文件操作的鼻祖,因为超级块是我们寻找索引节点——索引节点对象包含了内核在操作文件或目录时需要的全部信息——的唯一源头,我们操作文件必然需要获得其对应的索引节点(这点和建立超级节点一样或从宿主设备读取或现场建立),而获取索引节点是通过超级块操作表提供的read_node函数完成的,同样操作索引节点的低层次任务,如创建一个索引节点、释放一个索引节点,也都是通过超级块操作表提供的有关函数完成的。所以超级块操作表是我们第二个需要创建的数据类型。

除了派生或释放索引节点等操作是由超级块操作表中的函数完成外,索引节点还需要许多自操作函数,比如lookup搜索索引节点,为建立符号连接等,这些函数都包含在索引节点操作表中,因此我们下一个需要创建的数据类型就是索引节点操作表。

为了提高文件系统的读写效率,Linux内核设计了I/O缓存机制。所有的数据无论出入都会经过系统管理的高速缓存——对于非基于块的文件系统则可跳过该机制。出于操作数据的目的,页高速缓存同样提供了一个函数操作表,其中包含有readpage()、writepage()等函数,负责操作高速缓存中的页读写。

     文件系统最终和用户交互还需要实现文件操作表,该表中包含有关用户读写文件、打开、关闭、映射等用户接口。

     对于基于块的文件系统实现的一般方式而言,都离不开以上5种数据结构。但根据文件系统的特点(如有的文件系统只可读、有的没有目录),并非要实现操作表中的全部函数,换句话说,你只需要实现部分函数,而且系统已经存在很多函数现成的通用方法,因此留给你做的事情其实不多。

Romfs文件系统是什么

Romfs是基于块的只读文件系统,它使用块(或扇区)访问存储设备驱动(比如磁盘,CD,ROM盘)。由于它小型、轻量,所以常常用在嵌入系统和系统引导时。

Romfs是种很简单的文件系统,它的文件布局和Ext2等文件系统相比要简单得多。它比ext2文件系统要求更少的空间。空间的节约来自于两个方面,首先内核支持romfs文件系统比支持ext2文件系统需要更少的代码,其次romfs文件系统相对简单,在建立文件系统超级块(superblock)需要更少的存储空间。Romfs文件系统不支持动态擦写保存,对于系统需要动态保存的数据采用虚拟ram盘的方法进行处理(ram盘将采用ext2文件系统)。

下面我们来分析一下它的实现方法,为读者勾勒出编写新文件系统的思路和步骤。希望能为大家自己设计文件系统起到抛砖引玉的效果。

Romfs文件系统布局与文件结构

文件系统简单理解就是数据的分层存储结构,数据由文件组织,而文件又由文件系统安排存储形式,所以首先必须设计信息和文件组织形式——也就是这里所说的文件布局。

      

在Linux内核源代码中的Document/fs/romfs中介绍了romfs文件系统的布局和文件结构。下面我们简要说明一下它们。

 

                       图 5 romfs文件系统布局

上图是romfs布局图,可以看到文件系统中每部分信息都是16位对的,也就是说存储的偏移量最后4位必须为0,这样做是为了提高访问速度。如果信息不足时,需要填充0以保证所有信息的开始位置都为16为对齐[1][17]。

   文件系统的开始8个字节存储文件系统的ASCII形式的名称,比如“romfs”;接着4个字节记录文件大小;然后的4个字节存储的是文件系统开始处512字节的检验和;接下来是卷名;最后是第一个文件的文件头,从这里开始依次存储的信息就是文件本身了。

 Romfs的文件结构也非常简单,我们看下图

 

 

具体需要实现的对象

   Romfs文件系统定义针对文件系统布局和文件结构定义了一个磁盘超级块结构和磁盘inode(对应于文件)结构:

struct romfs_super_block {

                 __u32 word0;

            __u32 word1;

            __u32 size;

             __u32 checksum;

char name[0];         

 };

 struct romfs_inode {

         __u32 next;          

        __u32 spec;

         __u32 size;

         __u32 checksum;

         char name[0];

  };

上述两种结构分别描述了文件系统结构与文件结构,它们将在内核装配超级块对象和索引节点对象时被使用。

   Romfs文件系统首先要定义的对象是文件系统类型romfs_fs_type。定义该对象同时还要定义读取超级块的函数romfs_read_super。

ramfs_read_super()作用是从磁盘读取磁盘超级块给超级块对象,具体行为如下

1 装配超级块。

  1.1 初始化超级块对象某些域。

1.2 从设备中读取磁盘第0块到内存到内存bread(dev,0,ROMBSIZE),其中dev是文件系统安装时指定的设备,0指设备的首块,也就是磁盘超级块,ROMBSIZE是读取的大小。

 1.3 检验磁盘超级块中的校验和

 1.4 继续初始化超级块对象某些域

2 给超级块对象的操作表赋值(s->s_op= &romfs_ops)

3 为根目录分配目录项s->s_root = d_alloc_root(iget(s,sz), sz为文件系统开始偏移。

         超级块操作表中romfs文件系统实现了两个函数

static struct super_operations romfs_ops = {

       read_inode:     romfs_read_inode,

       statfs:         romfs_statfs,

};

第一个函数read_inode(inode)是用磁盘上的数据填充参数指定的索引节点对象的域;索引节点对象的i_ino域标识从磁盘上要读取的具体文件系统的索引节点。

1 根据inode参数寻找对应的索引节点。

2 初始化索引节点的某些域

3 根据文件的访问权限(类别)设置索引节点的相应操作表

  3.1 如果是目录文件,则将索引节点表设为i->i_op = &romfs_dir_inode_operations;文件操作表设置为->i_fop = &romfs_dir_operations;如果索引节点对应目录的话,那么需要的操作仅仅会是lookup操作(因为romfs是个功能很有限的文件系统);对于文件操作表中的两个方法一个为read,另一个为readdir。前者利用通用函数generic_read_dir,返回用户错误消息。后者是针对readdir/getdents等系统调用的实现返回目录中的文件的函数

  3.2 如果是常规文件,则将文件操作表设置为i->i_fop= &generic_ro_fops;

将页高诉缓存表设置为i->i_data.a_ops = &romfs_aops;由于romfs是只读文件系统,它在对正规文件操作时不需要索引节点操作,如mknod,link等,因此不用给出索引节点操作表。

对常规文件的操作也只需要使用内核提供的通用函数表struct generic_ro_fops ,它包含基本的三种常规文件操作:

 llseek:         generic_file_llseek,

     read:           generic_file_read,

mmap:           generic_file_mmap,

   利用这几种通用函数,完全能够满足romfs文件系统的的文件操作要求,具体函数请自己阅读源代码。

回忆前面我们提到过的页高速缓存,显然常规文件访问需要经过它,因此有必要实现页高诉缓存操作。因为只需要读文件,所以只用实现romfs_readpage函数,这里readpage函数使用辅助函数romfs_copyfrom完成将数据从设备读入页高速缓存,该函数根据文件格式从设备读取需要的数据。设备读取操作需要使用bread块I/O例程,它的作用是从设备读取指定的块[1][18]

  3.3 如果是连接文件,则将索引节点操作表设置为:i->i_op=&page_symlink_inode_operations;

将页高速缓存操作表设置为:

i->i_data.a_ops = &romfs_aops;

符号连接文件需要使用通用符号连接操作page_symlink_inode_operations实现,同时也需要使用页高速缓存方法。

  3.4 如果是套接字或管道,则进行特殊文件初始化操作init_special_inode(i, ino, nextfh);

到此,我们已经遍历了romfs文件系统使用的几种对象结构:romfs_super_blockromfs_inode、romfs_fs_type、super_operationsromfs_ops、address_space_operations romfs_aops 、file_operationsromfs_dir_operations、inode_operations romfs_dir_inode_operations 。实现上述对象是设计一个新文件系统的最低要求。

 

最后要说明的是,为了使得romfs文件系统作为模块挂载,需要实现static int __init init_romfs_fs(void)

{

  returnregister_filesystem(&romfs_fs_type);

}

staticvoid __exit exit_romfs_fs(void)

{

   unregister_filesystem(&romfs_fs_type);

}

两个在安装romfs文件系统模块时使用的例程。

安装和卸载

module_init(init_romfs_fs)

module_exit(exit_romfs_fs)

    到此,romfs文件系统的关键结构都已介绍完毕,至于细节还是需要读者仔细推敲。Romfs是最简单的基于块的只读文件系统,而且没有访问控制等功能。所以很多访问权限以及写操作相关的方法都不必去实现。

      使用romfs首先需要genromfs来制作romfs文件系统镜像(类似于使用mke2fs格式化文件系统),然后安装文件系统镜像 mount –t romfs ********。 Romfs可以在编译内核时制定编译成模块或编入内核,如果是模块则需要首先加载它。

    小结:实现文件系统必须向上要清楚文件系统和系统调用的关系,向下要了解文件系统和I/O调度、设备驱动等的联系。另外还必须了解关于缓存、进程、磁盘格式等概念。在这里并没有对这些问题进行深入分析,仅仅提供给大家一个文件系统全景图,希望能对自己设计文件系统有所帮助。文件系统内容庞大、复杂,许多问题我也不确定,有文件系统经验的朋友希望能够广泛交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值