2024年1月8日发(作者:)

linux中romfs的详解

Romfs是一种相对简单、占用空间较少的文件系统。

空间的节约来自于两个方面:首先内核支持Romfs文件系统比支持 ext2文件系统需要更少的代码;其次romfs文件系

统相对简单,在建立文件系统超级块(Superblock)需要更少的存储空间。

Romfs是只读的文件系统,禁止写操作,因此系统同时需要虚拟盘(RAMDISK)支持临时文件和数据文件的存储。

对ROMFS文件系统的分析

引言:ROMFS是在嵌入式设备上常用的一种文件系统,具备体积小,可靠性好,读取速度快等优点。同时支持目录,

符号链接,硬链接,设备文件。但也有其局限性。ROMFS是一种只读文件系统,同时由于ROMFS本身设计上的原因,使

得ROMFS支持的最大文件不超过256M。本文讨论了ROMFS的原理,并针对其代码做了详细的分析,指出了ROMFS的优缺点并做了相应的改进。Linux, uclinux都支持ROMFS文件系统。除ROMFS外,其它常用的嵌入式设备的文件系统还有

CRAMFS,JFFS2等,它们各有特色。

1.ROMFS文件系统的特点

ROMFS是一种只读的文件系统,它使用顺序存储方式,所有数据,包括目录,链接等都按目录树的顺序存放。相对于EXT2等较大型的文件系统而言,ROMFS非常节省空间。通常ROMFS用在嵌入式设备中作为根文件系统,或者用于保存boot

loader以便引导系统启动。

2.ROMFS文件系统的数据存储方式

设计一个文件系统首先要确定它的数据存储方式。不同的数据存储方式对文件系统占用空间,读写效率,查找速度等主要性能有极大影响。ROMFS是一种只读的文件系统,它使用顺序存储方式,所有数据都是顺序存放的。因此ROMFS中的数据一旦确定就无法修改,这是ROMFS只能是一种只读文件系统的原因,它的数据存储方式决定了无法对ROMFS进行写操作。由于采用了顺序存放策略,ROMFS中每个文件的数据都能连续存放,读取过程中只需要一次寻址操作,进而就可以读入整块数据,因此ROMFS中读取数据效率很高。

整个ROMFS文件系统的布局如下:

ROMFS的首部

前八个字节是文件系统的名字,在这里是”-rom1fs-“, 8-11字节存放该文件系统大小,12-15字节为前512字节的校验和,从16字节开始是文件系统的卷名,卷名的长度必须的16字节的整数倍,不足的部分可以用‘0’填充。

ROMFS的首部是ROMFS的超级块信息,操作系统通过超级块来识别文件系统的类型。首部之后就是实际的数据,包括目录,普通文件,设备文件,硬链接等。ROMFS支持所有这些类型的文件。

的主要数据结构

ROMFS的数据结构比较简单,主要有文件系统结构和文件结构两种数据结构。

ROMFS的文件系统结构如下:

struct romfs_super_block

{

__u32 word0;

__u32 word1;

__u32 size;

__u32 checksum;

char name[0]; /* volume name */

};

该结构用于识别整个ROMFS文件系统,大小为512字节,word0初始值为'-','r','o','m',word1初始值为 '-','1','f','s',通过这两个字操作系统确定这是一个ROMFS文件系统。size字段用于记录整个文件系统的大小,理论上ROMFS大小最多可以达到4G。checksum是前512字节的校验和,用于确认整个文件系统结构数据的正确性。前面4个字段占用了16字节,剩下的都可以用作文件系统的卷名,如果整个首部不足512字节便用0填充,以保证首部符合16字节对齐的规则。

ROMFS的文件结构如下:

struct romfs_inode

{

__u32 next; /* low 4 bits see ROMFH_ */

__

u32 spec;

__u32 size;

__u32 checksum;

char name[0];

};

next 字段是下一个文件的偏移地址,该地址的后4位是保留的,用于记录文件模式信息,其中前两位为文件类型,后两位则标识该文件是否为可执行文件。因此ROMFS用于文件寻址的字段实际上只有28bit,所以ROMFS中文件大小不能超

过256M。spec字段用于标识该文件类型。目前ROMFS支持的文件类型包括普通文件,目录文件,符号链接,块设备和字符设备文件。size是文件大小,checksum是校验和,校验内容包括文件名,填充字段。name是文件名首地址,文件名长度必须保证16字节对齐,不足的部分用可以0填充。

的实现

在Linux系统中定义一个文件系统首先要定义相应的file_system_type以及读取超级块的函数。

static DECLARE_FSTYPE_DEV(romfs_fs_type,"romfs",romfs_read_super);

具体到ROMFS本身,这两个对象分别

是romfs_fs_type和romfs_read_super,文件系统名为"romfs",通过宏DECLARE_FSTYPE_DEV来实现对

romfs_fs_type的定义以及初始化工作。此外还需要实现对目录,文件的读写操作。

在Linux对ROMFS的实现中,比较重要的数据结构如下:

超级块操作表

static struct super_operations romfs_ops =

{

read_inode: romfs_read_inode,

statfs: romfs_statfs,};

页操作表

static struct address_space_operations romfs_aops =

{

readpage: romfs_readpage};

常规文件操作表

static struct file_operations romfs_dir_operations =

{

read: generic_read_dir,

readdir: romfs_readdir,};

索引节点操作表

static struct inode_operations romfs_dir_inode_operations =

{

lookup: romfs_lookup,};

romfs_read_super()用来读取ROMFS文件系统的首部,并利用该首部初始化一个超级块对象作为相应ROMFS的超级块,具体流程如下

1 初始化超级块。

A 设置一次读取的块大小并初始化超级块对象某些域。

B 从指定ROMFS中读取第0块到一个缓冲区。bh=sb_bread(s, 0),其中s是文件系统的超级块对象。ROMFS的文件系统结构被保存到缓冲区bh中。

C 取出ROMFS的文件系统结构,rsb = (struct romfs_super_block

*)bh->b_data,rsb是一个

romfs_super_block结构,用以保存该ROMFS的文件系统结构的数据。然后对该数据进行检验,确定其文件系统类型,检验和,文件系统大小。

D 继续初始化超级块对象某些域,比较重要的是s_magic = ROMFS_MAGIC和s_flags |= MS_RDONLY,分别表明了该超级块的magic签名和s_flags参数,此处它们分别表示该文件系统类型为romfs,并且是只读文件系统。

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,

};

函数romfs_read_inode是从ROMFS中读取一个inode索引节点对象并进行一些初始化工作,具体流程如下:

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

2 初始化索引节点某些域

3 根据该inode对应的文件的访问权限和类别来设置索引节点的相应操作表

A 如果是目录文件则将索引节点操作表设为i_>i_op=&romfs_dir_inode_operations;文件操作表设置为i->i_fop=&romfs_dir_operations。

B 如果是常规文件,则将文件操作表设置为i->i_fop=&generic_ro_fops;将页高诉缓存表设置为

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

对常规文件的操作也只需要使用内核提供的通用函数表

generic_ro_fops ,它包含基本的三种常规文件操作:

llseek: generic_file_llseek,

read: generic_file_read,

mmap: generic_file_mmap,

这几种函数是块设备读取的通用函数,它们可以实现对ROMFS中常规文件的读取,寻址等操作。

C 如果是符号链接文件,则将索引节点操作表设置为

i->i_op = &page_symlink_inode_operation; page_symlink_inode_operations是通用的符号链接操作表。同时还需要实现页高速缓存操作,

因此将页高速缓存表设置为i->i_data.a_ops=&romfs_aops。

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

函数romfs_statfs用于提取一些ROMFS的基本信息,包括文件系统大小,卷名等。相对而言非常简单。

1.1 什么是romfs

romfs是一个只读文件系统,主要用在 mainly for initial RAM disks of

installation disks.使用romfs文件系统可以构造出一个最小的内核,并且很节省内存。相比而言,早期的minix和xiafs(现在已经过时)文件系统如果编译为模块的形式则大小超过20000字节(在x86机器上大小为38502字节),而romfs却小于一页(在linux系统中,一页大小为PAGE_OFFSET,一般为4K),大约4000字节(在x86机器上大小为10479字节)。在相同的条件下,msdos文件系统模块大约30K(并且不支持设备节点和符号链接,在x86机器上大小为12K)。ntfs和nfsroot文件系统模块大约57K(在x86机器上大小为102K)。

注:上面叙述中的数值都是针对i586机器,括号中叙述的数值是在现在的x86机器上的大小,针对2.6.28内核。

1.2 romfs的用途

romfs本设计的主要目标是构造一个最小内核,在内核中只链接romfs文件系统,这样就可以使用romfs在稍后加载其他模块。romfs也可以用来运行一些程序,从而决定你是否需要SCSI设备,或者IDE设备,或者如果你使用的是"initrd"结构的内核,romfs也可以用来在之后加载软驱驱动。romfs的另一个用途是在你使用romfs文件系统的时候,你可以关闭ext2或者minix甚至affs文件系统直到你确信需要的时候再开启。

1.3 romfs的性能

romfs的操作是基于块设备的,它的底层结构非常简单。为了快速访问,每个单元被设计为起始于16字节边界。一个最小的文件为32字节(文件内容为空,并且文件名长度小于16字节)。对于一个非空文件的最大的开销是位于文件内容前面的文件头和其后的16字节的文件名(因为大多数的文件名长度大于3字节并且小于15字节,所以预置文件名长度为16字节)。

1.4 如何使用romfs映像

要使用一个制作好的romfs格式的映像,是将其挂载在其他文件系统的某个节点上。并且还有一个很重要的前提,就是内核要支持romfs文件系统。这一点可以通过配置内核实现,有两个方法:

1.将romfs配置成直接编译进内核,方法为使用make menuconfig命令进入内核配置界面,选择"File systems"并进入,选择“Miscellaneous

filesystems”并进入,选择“ROM file system support(ROMFS)”,将其配置成"*"(直接编译进内核)。这样生成的内核就直接包含对romfs文件系统的支持。

2.将romfs配置成模块的形式,步骤和前面一样,只是在最后选择"ROM file

system support(ROMFS)"的时候将其配置成"M"(编译为内核模块)。这样编译好的内核并不包含对romfs文件系统的支持,只是生成了模块(fs/romfs/),需要在启动系统后将其加载进内核才能使内核支持romfs文件系统。

有了内核对romfs文件系统的支持,就可以直接挂载romfs格式的映像了,挂载方法为:

niutao@niutao:~/kernel/romfs$ ls

niutao@niutao:~/kernel/romfs$ file

: romfs filesystem, version 1 208 bytes, named rom 49e05ac0.

niutao@niutao:~/kernel/romfs$ sudo mount -o loop /mnt

niutao@niutao:~/kernel/romfs$ cd /mnt/

niutao@niutao:/mnt$ ls

hello.c

niutao@niutao:/mnt$

可以看到使用mount命令将挂载到了/mnt目录下,其内只有一个文件。

卸载一个已经被挂载的romfs格式映像使用umount命令。

1.5如何制作romfs映像

如果要创建一个romfs文件系统,需要使用genromfs工具。具体用法为:

-f IMAGE 指定输出romfs映像的名字

-d DIRECTORY 指定源目录(将该目录制作成romfs文件系统)

-v 显示详细的创建过程

-V VOLUME 指定卷标

-a ALIGN 指定普通文件的对齐边界(默认为16字节)

-A ALIGN,PATTERN 匹配参数PATTERN的对象对齐在ALIGN边界上

-x PATTERN 不包括匹配PATTERN的对象。

-h 显示帮助文档。

上节我们说到使用genromfs工具生成romfs映像,现在我们一一详细讲述genromfs的各个参数的含义及用法。

1. -f参数

该参数指定要生成的romfs映像的名字,是一个必须参数,如果缺少,则无法生成映像。例如:

niutao@niutao:~/kernel/romfs$ ls

hello.c

niutao@niutao:~/kernel/romfs$ genromfs -f

niutao@niutao:~/kernel/romfs$ ls

hello.c

可以看到生成了名为的romfs格式的映像。那么genromfs是将那个目录制作成了?将挂载之后,可以看到其内的文件为当前路径下的文件(hello.c和)。所以在没有指定源目录(要将那

个目录制作成romfs映像)的时候,默认为当前目录。这一点也可以从genromfs的源代码中看到:

701 int main(int argc, char *argv[])

702 {

703 int c;

704 char *dir = ".";

705 char *outf = NULL;

706 char *volname = NULL;

注:代码摘自genromfs-0.5.2/genromfs.c。

可以看到其中的dir就是指定的源目录,默认为当前目录。那么如何指定源目录?通过使用-d参数指定。

2. -d参数

该参数指定源目录,意思是要将那个目录及其下面的文件制作成romfs格式的映像。如果不使用-d参数指定,则默认为当前目录。例如:

niutao@niutao:~/kernel/romfs$ ls

hello

niutao@niutao:~/kernel/romfs$ ls hello/

hello.c

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/

niutao@niutao:~/kernel/romfs$ ls

hello

niutao@niutao:~/kernel/romfs$

3. -v参数

-v表示显示romfs映像创建的详细过程。例如:

niutao@niutao:~/kernel/romfs$ ls

hello

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -v

0 rom 49e06097 [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x1e4204 ] 0040755, sz 0, at 0x20

1 .. [0x80e , 0x1e41ea ] 0040755, sz 0, at 0x40 [link to 0x20 ]

1 hello.c [0x80e , 0x1e4266 ] 0100644, sz 71, at 0x60

niutao@niutao:~/kernel/romfs$ ls

hello

niutao@niutao:~/kernel/romfs$

(1).第一列为目录深度(0为顶极目录),各项的含义具体见genromfs.c的shownode函数。

(2).第二列为文件或者目录名。

(3).第三列为前一个为文件或者目录所在设备的设备号,后一个为文件或者目录的节点号(这两个对应struct stat结构体的st_dev项和st_ino项)。

(4).第四列为文件或者目录的属性(对应struct stat结构体的st_mode项)。

(5).第五列为文件或者目录的大小,对于目录为0,文件则为文件以字节为单位的大小。

(6).第六列为文件或者目录在生成的romfs格式映像文件中的偏移。

(7).第七列表示该文件或者目录是一个硬链接,其指向的位置。这里需要注意一个细节:对于顶极目录下的目录,..目录是一个硬链接,除此之外,所有的目录都是目录(也就是说此第七列对于顶极目录下的目录就不存在)。而对于顶极目录下的子目录内的目录,则都是硬链接。这个在往后还会继续从代码的角度分析为什么会是这样(见对genromfs工具的分析)。

4. -V参数

指定当前要生成的romfs格式映像的卷标,如果没有指定,则默认为字符串"rom "加当前的时间(16进制格式)。例如:

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/

niutao@niutao:~/kernel/romfs$ ls

hello

niutao@niutao:~/kernel/romfs$ file

: romfs filesystem, version 1 256 bytes, named rom 49e069d8.

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -V

niutao

niutao@niutao:~/kernel/romfs$ file

: romfs filesystem, version 1 256 bytes, named niutao.

niutao@niutao:~/kernel/romfs$

5. -a参数

指定普通文件的对齐边界(默认为16字节),目的是为了快速访问文件内容。给出的对齐边界值必须大于16并且为16的倍数。这里所说的对齐指的是普通文件的文件内容的对齐,而并非文件头(见内核include/linux/romfs_fs.h中结构体struct romfs_inode )的对齐。假如指定普通文件x的对齐边界为

align,其在生成的romfs格式映像中的偏移为start,文件名长度为len,则文件内容在romfs映像中的偏移offset为:

offset = start + 16 + (len / 16) * 16 + len % 16 ? 16 : 0

其计算过程为文件头偏移加上文件头大小(struct romfs_inode),其文件头大小为16字节,再加上文件名以16字节对齐的长度。

例如:

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -V

niutao -a 64 -v

0 niutao [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x1e4204 ] 0040755, sz 0, at 0x20

1 .. [0x80e , 0x1e41ea ] 0040755, sz 0, at 0x40 [link to 0x20 ]

1 hello.c [0x80e , 0x1e4266 ] 0100644, sz 71, at 0x60

niutao@niutao:~/kernel/romfs$

我们使用-a参数指定普通文件的对齐algin = 64。可以看到普通文件hello.c文件头(也叫节点)开始偏移为start = 0x60,文件名长度len = 7,则文件内容起始位置offset为:

offset = start + 16 + (len / 16) * 16 + len % 16 ? 16 : 0

= 0x60 + 16 + (7 / 16) * 16 + 7 % 16 ? 16 : 0

= 0x80

其正好对齐在64边界上。

注意:-a参数只对普通文件起作用,如果要对所有或者部分或者指定的文件起作用,则要使用-A或者-x参数。

6. -A参数

genromfs -A ALIGN,PATTERN

匹配PATTERN的对象对齐在ALIGN边界上。给出的对齐边界值必须大于16并且为16的倍数。和-a不同的是,-A参数可以指定某个文件或者某些文件对其在ALIGN边界上。例如:

指定目录"."对齐在64字节边界上:

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -v -A

64,.

0 rom 49e08490 [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x1e4204 ] 0040755, sz 0, at 0x40

1 .. [0x80e , 0x1e41ea ] 0040755, sz 0, at 0x60 [link to 0x40 ]

1 hello.c [0x80e , 0x1e4266 ] 0100644, sz 71, at 0x80

niutao@niutao:~/kernel/romfs$

指定所有的.c文件对其在64字节边界上:

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -v -A

64,*.c

0 rom 49e08553 [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x1e4204 ] 0040755, sz 0, at 0x20

1 .. [0x80e , 0x1e41ea ] 0040755, sz 0, at 0x40 [link to 0x20 ]

1 hello.c [0x80e , 0x1e4266 ] 0100644, sz 71, at 0x60

niutao@niutao:~/kernel/romfs$

7. -x参数

不包括匹配PATTERN的对象。也就是说在生成romfs格式映像的时候,不包括指定的源目录下的匹配PATTERN的文件或者目录。例如:

niutao@niutao:~/kernel/romfs$ ls -al hello

total 12

drwxr-xr-x 2 niutao niutao 4096 2009-04-11 20:02 .

drwxr-xr-x 3 niutao niutao 4096 2009-04-11 19:49 ..

-rw-r--r-- 1 niutao niutao 0 2009-04-11 20:02 aa.c

-rw-r--r-- 1 niutao niutao 71 2009-04-11 16:54 hello.c

niutao@niutao:~/kernel/romfs$ genromfs -f -d hello/ -v -x

hello.c

0 rom 49e086ed [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x1e4204 ] 0040755, sz 0, at 0x20

1 .. [0x80e , 0x1e41ea ] 0040755, sz 0, at 0x40 [link to 0x20 ]

1 aa.c [0x80e , 0x1e4276 ] 0100644, sz 0, at 0x60

可以看出,我们使用-x参数将hello.c文件过滤掉了,在生成的romfs格式的映像中,没有hello.c。

前面说了我们使用genromfs工具可以生成romfs文件系统,那其生成的映像的格式是什么?这就要探究romfs文件系统的本质了。

映像结构

使用genromfs生成的romfs格式映像中,文件或者目录是顺序存放,每个文件头对齐在16字节边界上,基本结构如图:

romfs映像开始16字节对应struct romfs_super_block结构体,接下来为romfs映像的卷标。上图所示情况为卷标小于16字节,如果卷标大于16字节,则顺序存放在romfs_super_block之后,并且对齐在16字节边界上。紧随卷标之后的就是第一个文件的文件头romfs_inode结构,共16字节,之后就是文件名,也是16字节对齐,然后才是文件内容。其中在每个文件的文件头中的前4字节(不包括低4位)为下一个文件头在romfs映像中的偏移。romfs正是这样将整个文件组织在一起的。下面分别看romfs_super_block和romfs_inode结构。

1.1 romfs超级块结构

在linux内核中,romfs超级块结构定义在include/linux/romfs_fs.h中,源代码为:

/* On-disk "super block" */

struct romfs_super_block {

__be32 word0;

__be32 word1;

__be32 size;

__be32 checksum;

char name[0]; /* volume name */

};

(1)word0和word1

word0和word1的值是固定的,分别为"-rom"和"1fs-",是用来识别romfs文件系统的。这两个值被定义在内核中:

#define ROMSB_WORD0 __mk4('-','r','o','m')

#define ROMSB_WORD1 __mk4('1','f','s','-')

(2)size域

size域表示romfs映像可访问的大小。也就是最后一个文件的结束位置(以16字节对齐),比如:

niutao@niutao:~/romfs$ ls -l romtest

total 4

-rw-r--r-- 1 niutao niutao 44 2009-04-08 20:15 len.c

niutao@niutao:~/romfs$ genromfs -f -d romtest/ -v -V niutao

0 niutao [0xffffffff, 0xffffffff] 37777777777, sz 0, at 0x0

1 . [0x80e , 0x18aada ] 0040755, sz 0, at 0x20

1 .. [0x80e , 0x18aac6 ] 0040755, sz 0, at 0x40 [link to 0x20 ]

1 len.c [0x80e , 0x18aadb ] 0100644, sz 44, at 0x60

niutao@niutao:~/romfs$

对于上面生成的映像,在内最后一个文件为len.c,其文件头偏移为0x60,文件名为len.c(小于16字节),文件大小为44字节,由此我们可以计算出该可访问的大小为:

size = (0x60 + 16 + 16 + 44 ) / 0x10 * 0x10 + 0x10 = 0xb0

第一个16表示文件头(struct romfs_inode结构)的大小,第二个16表示文件名占用的长度。下面我们使用file命令验证计算是否正确:

niutao@niutao:~/romfs$ file

rom: romfs filesystem, version 1 176 bytes, named niutao.

niutao@niutao:~/romfs$

可以看到文件可访问大小正是b0字节(176 bytes)。当然最好还是直接查看超级结构中size的值(位于偏移0x8处):

可以看到其值正是0x000000b0。

从size的定义我们可以看到,其为__be32(unsigned int),所以理论支持的romfs映像最大为4G

(3)checksum域

接下来是checksum域。该域是用于校验文件的。使用的算法为checksum算法。简单的说就是一种数据校验方式,早期用于数据传输中对数据正确性的校验,其基本思想是将要校验的数据加起来作为校验码,和数据一并发送给对方,在对方接收完数据后,将接收到的数据加起来和传输过来的校验码比较,如果相等,则说明数据是正确的,否则说明传输错误。可见这种校验方式是及其简单的。那么在romfs中是如何使用的呢?

在romfs中,使用checksum算法校验的数据最大为512字节,这一点可以从romfs_fill_super函数的这段代码看出:

if (romfs_checksum(rsb, min_t(int, sz, 512))) {

printk ("romfs: bad initial checksum on dev "

"%s.n", s->s_id);

goto out;

}

比如我们现在要对romfs_super_block校验,是将该结构的所有数据每四字节转化成整形,然后加起来,看其是否为0。如果为0,则说明数据正确,否则则发生了错误。那怎样保证该结构的所有数据加起来就是0呢?这个就是通过checksum域调整的。我们知道要生成romfs映像,则需使用genromfs工具。该工具在生成romfs映像的时候,就计算好的了校验和。假如我们现在是要对romfs_super_block生成校验码(checksum域的值),我们首先将该结构的这一项设置为0,然后使用checksum算法将要校验的数据(romfs_super_block结构)加起来,取其负数,赋值给checksum域。这样再使用checksum算法将romfs_super_block结构的数据加起来,则结果必然为0。

具体计算过程可以参见genromfs源码的romfs_checksum函数和fixsum函数。这里还要说明的一点是,对于romfs映像头(romfs_super_block),其校验和是针对前512字节的(如果romfs可访问的大小没有512字节(romfs_super_block->size < 512)),则只校验可访问大小。对于文件头(romfs_inode),其校验和(romfs_inode->checksum)是针对文件头和文件名的校验和。

(4)name域

接下来的name域被定义为char name[0],如果我们使用sizeof(struct

romfs_super_block),会发现name域不占空间,这样写的好处是我们可以直接使用romfs_super_block->name来访问volume。但我们为什么不直接将volume也作为romfs_super_block的一个域呢?一个原因是volume是一个长度不固定的域,你可以给他任意长度,如果放在romfs_super_block中,则势必要对volume有一个限制,这样就限制了整个文件系统的灵活性。另一个原因是由前一个原因直接导致的,也就是如果将其作为romfs_super_block的一个域,其势必会有固定长度,这样在volume很短的使用,就造成了空间的浪费.

上一节(/u2/73528/?id=1903687)说的是romfs的超级块结构(struct romfs_super_block),下面主要介绍romfs的inode结构(struct romfs_inode)。

众所周知,在文件系统中,我们都会提到inode节点。那么对于romfs文件

系统,是否也有inode节点?如果有,那是否也向EXT2等如此庞大复杂?

对于第一个问题,我们的回答是“是”,第二个回答是“否”。romfs设计的初衷就是构建一个最小内核。为了达到目的,我们是一切从简。越简单其自身就越小,越能提高空间的利用率;越简单处理起来效率就越高。所以romfs的inode也和其超级块结构一样,非常简单:

/* On disk inode */

struct romfs_inode {

__be32 next; /* low 4 bits see ROMFH_ */

__be32 spec;

__be32 size;

__be32 checksum;

char name[0];

};

初看,似乎和其超级块结构一样,都有5个域。结构本身大小也一样(都为16字节)。实际上,在生成romfs映像的工具genromfs中,就是使用一个结构(struct romfsfh)来代表这两个结构的,之所以分开,是因为其代表的对象不同。romfs_super_block代表整个romfs文件系统,而romfs_inode只代表文件。虽然两个结构一样,但个个域的含义是不一样的。下面我们一一讲解。

1. next域

该域是非常重要的一个字段。因为整个romfs映像中的文件头最少都对齐在16字节边界上,也就是说其在romfs映像文件中的偏移的低四位一定为0。所以我们正还利用这4位来做些什么。

romfs文件系统使用其低4位来表示文件类型和文件的权限,具体分配为:[0:2]共3位和起来表示文件类型,可见在romfs文件系统中,支持的文件类型有8中,具体有:

#define ROMFH_HRD 0 /* 硬链接文件 */

#define ROMFH_DIR 1 /* 目录文件 */

#define ROMFH_REG 2 /* 普通文件 */

#define ROMFH_LNK 3 /* 链接文件 */

#define ROMFH_BLK 4 /* 块设备文件 */

#define ROMFH_CHR 5 /* 字符设备文件 */

#define ROMFH_SCK 6 /* 套接字文件 */

第4位表示文件是否有可执行权限。如果其他用户具有读写执行权限,并且文件是一个目录或者普通文件,则给文件加上可执行权限。硬链接文件不具有可执行权限。将next域的低4位置0就是下一个文件在romfs中的偏移。romfs正是通过这个域将所有文件组织起来的。

这里还有一个小小的问题。在网上看到许多关于romfs的文章,说romfs文件系统使用next域的高28位来寻址下一个文件,所以romfs文件系统支持的最大的映像为256M。还有人经过理论,使用name域的一些空间和next域联合起来,使romfs文件系统支持大于256M的文件。这个说法是完全错误的。最好的证据就是你直接拖几个五六百兆的电影,然后使用genromfs工具看其是否能生成romfs映像;如果能生成,使用mount命令挂载,看其是否挂载上;如果能挂载上,你可以使用cp命令将大于256M的电影文件拷出来,使用diff命令和原来的电影文件对比一下,看是否一样。在我的机子上,这些假设都成立。这并非偶然,我们可以从最最本质的代码角度继续证实这一点.

2. spec域

初识romfs的时候,这个域我困惑了好久,不知道它和next域表示的文件类型到底有什么联系和区别,看内核文档(Documention/filesystem/)的时候也是没弄明白。经过这些天的分析,现在终于弄明白了。如果你也在学习romfs文件系统,希望对你有帮助。

这个域是文件类型相关的,也就是说对于不同的文件类型,这个域表示的含义是不一样的。

(1).对于硬链接文件(next & 0xf = 0),这个域的值为其指向的文件的偏移。

(2).对于目录文件(next & 0x1 = 1),这个域指向其内第一个文件的文件头

在romfs映像中的偏移。

(3).对于普通文件(next & 0x2 = 2),链接文件(next & 0x3 = 3)

,SOCKET文件(next & 0x6 = 6)和管道文件(next & 0x7 = 7),这

个域一定为0,这一点可以从genromfs工具代码的角度证明(具体见

dumpnode函数,我们在后面还会详细分析)。

(4).对于块设备文件(next & 0x4 = 4)和字符设备文件(next & 0x5 = 5)

,这个域的高16位为设备文件代表的设备的主设备号,低16位为次设备号。

3. size域

该域表示文件的大小。文件内容是16字节对齐的。如果文件的末尾不够16字节,则使用0填充.

4. checksum域

和romfs_super_block结构不同的是,这个域只是文件头和文件名的校验和。