Romfs文件系统如何实现
Romfs 是基于块的只读文件系统,它使用块(或扇区)访问存储设备驱动(比如磁盘,CD,ROM 盘驱动)。由于它小型、轻量,所以常常用在嵌入系统和系统引导时。这种文件系统结构简 单,实现容易,我们下面就分析一下它的实现方法,为读者勾勒出编写新文件系统的思路和 步骤。希望能为大家自己设计文件系统起到抛砖引玉的效果。
Romfs 文件系统布局与文件结构
文件系统简单理解就是数据的分层存储结构,数据由文件组织,而文件又由文件系统安 排存储形式,所以首先必须设计信息和文件组织形式——也就是这里所说的文件布局。 Romfs 是种很简单的文件系统,它的文件布局和 Ext2 等文件系统相比要简单的得多。 在 Linux 内核源代码种得 Document/fs/romfs 中介绍了 romfs 文件系统得布局和文件结构。现 面我们简要说明一下它们。
上图是 romfs 布局图,可以看到文件系统中每部分信息都是 16 位对其的,也就是说存储的 偏移量必须最后 4 位为 0,这样作是为了提高访问速度。随意如果信息不足时,需要填充 0 以保证所有信息的开始位置都为 16 为对其 16[9] 。 文件系统的开始 8 个字节存储文件系统的 ASCII 形式的名称,比如“romfs”;接着 4 个字 节记录文件大小;然后的 4 个字节存储的是文件系统开始处 512 字节的检验和;接下来是卷名; 最后是第一个文件的文件头,从这里开始依次存储的信息就是文件本身了。 Romfs 的文件结构也非常简单,我们看下图
具体需要实现的对象
编写新文件系统自己需要一些基本对象 17[10] :文件系统类型对象;文件对象;索引节点对象; 高速缓存对象;超级块对象。这些文件对象前面已经介绍过了,要强调的是,多数对象中的 数据和操作都已经定义或有通用实现,所以最需要自己定义的往往是针对具体文件系统中对 象的操作函数。 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 例程,它的作用是从设备读 取指定的块 18[11] 。
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_block、 romfs_inode、romfs_fs_type、 super_operations romfs_ops、 address_space_operations romfs_aops 、file_operations romfs_dir_operations、inode_operations romfs_dir_inode_operations 。
实现上述对象是设计一个新文件系统的最低要求。 最后要说明的是为了使得 romfs 文件系统作为模块挂载,需要实现
static int __init
init_romfs_fs(void)
{return register_filesystem(&romfs_fs_type);
}
和
static void __exit exit_romfs_fs(void)
{
unregister_filesystem(&romfs_fs_type);
}
两个在安装 romfs 文件系统模块时使用的例程。
安装和卸载
module_init(init_romfs_fs)
module_exit(exit_romfs_fs)
到此,romfs 文件系统的关键结构都已介绍,至于细节还是需要读者仔细推敲。 Romfs 是最简单的基于块的只读文件系统,而且没有访问控制等功能。所以很多访问权限以 及写操作相关的方法都不必去实现。
总结:
实现文件系统必须想上要清楚文件系统和系统调用的关系,想下要了解文件系统和 I/O 调度、设备驱动等的联系。另外还必须了解关于缓存、进程、磁盘格式等概念。在这里 并没有对这些问题进行是深入分析,仅仅提供给大家一个文件系统全景图,希望能对自己设 计文件系统有所帮助。文件系统内容庞大、复杂,许多问题我也比较含混,有文件系统经验 的朋友希望能够广泛交流。