Chinaunix首页 | 论坛 | 博客
  • 博客访问: 92571
  • 博文数量: 34
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 275
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-13 23:05
文章分类

全部博文(34)

文章存档

2011年(1)

2010年(7)

2009年(26)

我的朋友

分类: LINUX

2009-06-25 21:52:23

认识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文件系统的支持,只是生成了romfs.ko模块(fs/romfs/romfs.ko),需要在启动系统后将其加载进内核才能使内核支持romfs文件系统。
有了内核对romfs文件系统的支持,就可以直接挂载romfs格式的映像了,挂载方法为:

niutao@niutao:~/kernel/romfs$ ls
hello.img
niutao@niutao:~/kernel/romfs$ file hello.img 
hello.img: romfs filesystem, version 1 208 bytes, named rom 49e05ac0.
niutao@niutao:~/kernel/romfs$ sudo mount -o loop hello.img /mnt
niutao@niutao:~/kernel/romfs$ cd /mnt/
niutao@niutao:/mnt$ ls
hello.c
niutao@niutao:/mnt$

可以看到使用mount命令将hello.img挂载到了/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 hello.img
niutao@niutao:~/kernel/romfs$ ls
hello.c hello.img

  可以看到生成了名为hello.img的romfs格式的映像。那么genromfs是将那个目录制作成了hello.img?将hello.img挂载之后,可以看到其内的文件为当前路径下的文件(hello.c和hello.img)。所以在没有指定源目录(要将那个目录制作成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 hello.img -d hello/
niutao@niutao:~/kernel/romfs$ ls
hello hello.img
niutao@niutao:~/kernel/romfs$

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

niutao@niutao:~/kernel/romfs$ ls
hello
niutao@niutao:~/kernel/romfs$ genromfs -f hello.img -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 hello.img
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 hello.img -d hello/
niutao@niutao:~/kernel/romfs$ ls
hello hello.img
niutao@niutao:~/kernel/romfs$ file hello.img 
hello.img: romfs filesystem, version 1 256 bytes, named rom 49e069d8.
niutao@niutao:~/kernel/romfs$ genromfs -f hello.img -d hello/ -V niutao
niutao@niutao:~/kernel/romfs$ file hello.img 
hello.img: 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 hello.img -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 hello.img -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 hello.img -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 hello.img -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。

下面主要介绍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.txt)的时候也是没弄明白。经过这些天的分析,现在终于弄明白了。如果你也在学习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结构不同的是,这个域只是文件头和文件名的校验和。

 前面说了我们使用genromfs工具可以生成romfs文件系统,那其生成的映像的格式是什么?这就要探究romfs文件系统的本质了。
1.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 rom.img -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$

对于上面生成的rom.img映像,在rom.img内最后一个文件为len.c,其文件头偏移为0x60,文件名为len.c(小于16字节),文件大小为44字节,由此我们可以计算出该rom.img可访问的大小为:
size = (0x60 + 16 + 16 + 44 ) / 0x10 * 0x10 + 0x10 = 0xb0
第一个16表示文件头(struct romfs_inode结构)的大小,第二个16表示文件名占用的长度。下面我们使用file命令验证计算是否正确:

niutao@niutao:~/romfs$ file rom.img
rom: romfs filesystem, version 1 176 bytes, named niutao.
niutao@niutao:~/romfs$ 

可以看到文件可访问大小正是b0字节(176 bytes)。当然最好还是直接查看romf.img超级结构中size的值(位于rom.img偏移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很短的使用,就造成了空间的浪费.

阅读(696) | 评论(0) | 转发(0) |
0

上一篇:BabyLinux

下一篇:理解Initramfs

给主人留下些什么吧!~~