ROMFS文件系统分析

2019-07-13 07:32发布

ROMFS文件系统分析

ROMFS是一种简单的只读文件系统,主要是用来当做初始文件系统来使用的,在嵌入式linux或是uclinux中通常使用这中文件系统来作为引导系统的文件系统,甚至uclinux有时就直接把ROMFS作为其根文件系统,而不是将其作为系统启动中的过渡文件系统。在前面我也分析过,linux操作系统启动中一个是要加载内核,另一个就是要加载一个用于系统简单初始化的文件系统。这个文件系统的格式也是经过了很多发展的。现在一般使用的是一中cpio的格式。在嵌入式系统中一般使用romfs+其它的可读文件系统。romfs由于它的小巧性(其内核编译只有4000字节),所以非常适合作为系统启动初始化的文件系统。本文就是对ROMFS文件系统进行结构上的分析。前面我也曾对其源代码结构进行了简单的分析。关于ROMFS最为权威的资料是内核源代码树下的“Documentation/filesystems/romfs.txt”。本文多数资料就是来自于该文件。

ROMFS文件系统的制作

一般我们可以使用一些工具来制作ROMFS的文件系统。制作好之后其实也就是一个二进制的文件。制作工具一般使用”genromfs“,这个工具在网上就可下载到,其源代码并不是很多,只有不到900行。
以下是genromfs工具所支持的参数: xux@zhwen:~/fs-sys$ genromfs -h genromfs 0.5.2 Usage: genromfs [OPTIONS] -f IMAGE Create a romfs filesystem image from a directory -f IMAGE Output the image into this file -d DIRECTORY Use this directory as source -v (Too) verbose operation -V VOLUME Use the specified volume name -a ALIGN Align regular file data to ALIGN bytes -A ALIGN,PATTERN Align all objects matching pattern to at least ALIGN bytes -x PATTERN Exclude all objects matching pattern -h Show this help Report bugs to chexum@shadow.banki.hu xux@zhwen:~/fs-sys$ 参数解释:
-f IMAGE     指定输出romfs映像的名字
-d DIRECTORY 指定源目录(将该目录制作成romfs文件系统)
-v           显示详细的创建过程
-V VOLUME    指定卷标
-a ALIGN     指定普通文件的对齐边界(默认为16字节)
-A ALIGN,PATTERN 匹配参数PATTERN的对象对齐在ALIGN边界上
-x PATTERN 不包括匹配PATTERN的对象。
-h 显示帮助文档。
以下是如何制作生成一个romfs的文件系统: xux@zhwen:~/fs-sys$ ls test xux@zhwen:~/fs-sys$ ls test/ test xux zhwen xux@zhwen:~/fs-sys$ genromfs -V "xromfs" -f romfs.img -d test xux@zhwen:~/fs-sys$ ls romfs.img test xux@zhwen:~/fs-sys$ file romfs.img romfs.img: romfs filesystem, version 1 592 bytes, named xromfs. xux@zhwen:~/fs-sys$ sudo mount romfs.img /mnt -o loop xux@zhwen:~/fs-sys$ ls /mnt/ test xux zhwen xux@zhwen:~/fs-sys$

ROMFS文件系统结构分析

ROMFS系统中最大文件的大小理论上可以达到4G,文件名的大小一般小于16字节,而且整个文件系统都是以16字节来对齐。
其结构如下: offset content +---+---+---+---+ 0 | - | r | o | m | +---+---+---+---+ The ASCII representation of those bytes 4 | 1 | f | s | - | / (i.e. "-rom1fs-") +---+---+---+---+ 8 | full size | The number of accessible bytes in this fs. +---+---+---+---+ 12 | checksum | The checksum of the FIRST 512 BYTES. +---+---+---+---+ 16 | volume name | The zero terminated name of the volume, : : padded to 16 byte boundary. +---+---+---+---+ xx | file | : headers :

File headers之前的字节由如下的数据结构来控制: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的是固定的值:“-rom1fs-”,由如下的宏定义说明:
include/linux/romfs_fs.h #define ROMSB_WORD0 __mk4('-','r','o','m') #define ROMSB_WORD1 __mk4('1','f','s','-') (2)而size是对整个文件系统的大小的说明。
(3)checksum是对前512个字节的校验和(如果小于512,就以实际大小计算)。
(4)name是当前这个文件系统的名称。

下面在来看文件的堆放格式

offset content +---+---+---+---+ | file header | +---+---+---+---+ | file date | +---+---+---+---+ | file header | +---+---+---+---+ | file date | | file header | +---+---+---+---+ | file date | +---+---+---+---+ | ……. | +---+---+---+---+

File header的格式如下

offset content +---+---+---+---+ 0 | next filehdr | X | The offset of the next file header +---+---+---+---+ (zero if no more files) 4 | spec.info | Info for directories/hard links/devices +---+---+---+---+ 8 | size | The size of this file in bytes +---+---+---+---+ 12 | checksum | Covering the meta data, including the file +---+---+---+---+ name, and padding 16 | file name | The zero terminated name of the file, : : padded to 16 byte boundary 在内核源代码中如下:include/linux/romfs_fs.h /* On disk inode */ struct romfs_inode { __be32 next; /* low 4 bits see ROMFH_ */ __be32 spec; __be32 size; __be32 checksum; char name[0]; }; (1)其中next的前面28位是指向下一个文件的地址,应为整个文件系统以16字节对齐,所以任何一个文件的起始地址的最后4位始终为“0”。
而这最后的4位并没就此浪费,而是进行了新的利用--指定文件的类型和是否可执行。在linux下文件的类型分为:目录,一般文件,链接文件,管道文件,设备文件等。所以这4位中的最高一位使用来表示该文件是否可执行,而其余三位使用来表示该文件的类型。
include/linux/romfs_fs.h #define ROMFH_TYPE 7 #define ROMFH_HRD 0 #define ROMFH_DIR 1 #define ROMFH_REG 2 #define ROMFH_SYM 3 #define ROMFH_BLK 4 #define ROMFH_CHR 5 #define ROMFH_SCK 6 #define ROMFH_FIF 7 #define ROMFH_EXEC 8 (2)spec这个字段存放的是目录/硬链接/设备文件的相关信息:
这个域是文件类型相关的,也就是说对于不同的文件类型,这个域表示的含义是不一样的。下面是具体的说明;来自“Documentation/filesystems/romfs.txt”。 mapping spec.info means 0 hard link link destination [file header] 1 directory first file's header 2 regular file unused, must be zero [MBZ] 3 symbolic link unused, MBZ (file data is the link content) 4 block device 16/16 bits major/minor number 5 char device - " - 6 socket unused, MBZ 7 fifo unused, MBZ (3)size是这个文件的大小。
(4)checksum这个域只是文件头和文件名的校验和。
(5)name是文件的名称。

认识romfs文件系统

1.1 什么是romfs

       romfs是一个只读文件系统,主要用在 mainly for initial RAM disks of installation disks.使用romfs文件系统可以构造出一个最小的内核,并且很节省内存。相比而言,早期的minixxiafs(现在已经过时)文件系统如果编译为模块的形式则大小超过20000字节(在x86机器上大小为38502字节),而romfs却小于一页(在linux系统中,一页大小为PAGE_OFFSET,一般为4K),大约4000字节(在x86机器上大小为10479字节)。在相同的条件下,msdos文件系统模块大约30K(并且不支持设备节点和符号链接,在x86机器上大小为12K)。ntfsnfsroot文件系统模块大约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 显示帮助文档。 下面一一解释每个参数的含义。

1.5.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.imgromfs格式的映像。那么genromfs是将那个目录制作成了hello.img?将hello.img挂载之后,可以看到其内的文件为当前路径下的文件(hello.chello.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参数指定。

1.5.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$

1.5.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.cshownode函数。 2.第二列为文件或者目录名。 3.第三列为前一个为文件或者目录所在设备的设备号,后一个为文件或者目录的节点号(这两个对应struct stat结构体的st_dev项和st_ino项)。 4.第四列为文件或者目录的属性(对应struct stat结构体的st_mode项)。 5.第五列为文件或者目录的大小,对于目录为0,文件则为文件以字节为单位的大小。 6.第六列为文件或者目录在生成的romfs格式映像文件中的偏移。 7.第七列表示该文件或者目录是一个硬链接,其指向的位置。这里需要注意一个细节:对于顶极目录下的目录,..目录是一个硬链接,除此之外,所有的目录都是目录(也就是说此第七列对于顶极目录下的目录就不存在)。而对于顶极目录下的子目录内的目录,则都是硬链接。这个在往后还会继续从代码的角度分析为什么会是这样(见对genromfs工具的分析)。

1.5.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$

1.5.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参数。

1.5.6  -A参数

       genromfs -A ALIGN,PATTERN 匹配PATTERN的对象对齐在ALIGN边界上。给出的对齐边界值必须大于16并且为16的倍数。和-a不同的是,-A参数可以指定某个文件或者某些文件对其在ALIGN边界上。例如: 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$ 指定目录"."对齐在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$ 指定所有的.c文件对其在64字节边界上。

1.5.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文件系统结构很简单,由两部分组成,超级块、文件信息。超级块占文件系统的开始32字节;
接着是32字节的文件头,接着是该文件的数据。每个文件头都是32字节,16字节边缘对齐。
a.超级块:
   0~7前八个字节是romfs文件系统标示,如” -rom1fs-”;8~11共4字节是romfs文件系统的大小,由此理解
romfs文件系统最大是4GB。接着4字节12~15是checksum.接着16个字节16~31是卷名,卷名是以空字符
结束的字符串,可在生成romfs文件系统时指定。
b.文件信息
    文件信息由32的字节文件头和文件数据组成。文件头组成如下:4字节下一文件头偏移量,4字节
spec.info,4字节文件大小,4字节checkshum,16字节文件名。
    每个文件头32个字节长,对齐于16字节边界。文件头前4字节是下一个文件头的偏移量和文件模式
信息。把4字节的最低4位置为0既是下一个文件头偏移量。文件头偏移量是从文件系统开始偏移。低
4位是文件模式信息,0~3位指示文件类型,第4位指示该文件是否可执行。文件类型如下:  
值     文件类型     Spec.info意义
0     硬链接(hard link)     link destination [file header]
1     目录     该目录下第一个文件头偏移量
2     一般文件     无意义,全0
3     符号连接(symbolic link)     无意义
4     块设备     16位主设备好和16位次设备号。
5     字符设备     16位主设备好和16位次设备号。
6     套接字文件     无意义
7     管道文件     无意义

c.简单例子 
 
   下面是一个romfs文件系统的开始部分内容。前8字节'2D 72 6F 6D 31 66 73 26’ 即是romfs文件系统标示”-romfs-”。'00 00 52 40’是文件系统大小,
即21056字节。'A0 30 35 C9’是checksum。接下来16字节卷名'52 4F 4D 64 69 73 6B 00 。。。’,即
”ROMdisk”。接下来的32字节就是第一个文件头。
'00 00 00 49 00 00 00 20 00 00 00 00 D1 FF FF 97 2E 00 00 …’. ’00 00 00 49’是下一文件偏移量,即
'00 00 00 40’是下一个文件头的位置;低4位是9即表示本文件是目录,是可以执行的。接下来的4
字节’00 00 00 20’,是目录的第一个文件头位置,即20h,指向了本身。接下来的4字节文件程度
'00 00 00 00’,表示该文件程度是0.接下来16字节文件名'2E 00 00 00…’,文件名为”.”。下一文件
头在40h,即’00 00 00 60 00 00 00 20 00 00 00 00 D1 D1 FF 80 2E 2E 00 00 …’。前4字节’00 00 00 60’
表示该文件是一个硬件链接,下一个文件头在60h。硬件链接指向的文件头在'00 00 00 20’,文件
大小是0,文件名是“..”。60h偏移量的文件头信息
'00 00 4D 2A 00 00 00 00 00 00 4C 9C 36 99 F4 C5 64 65 71 75 65 00 00 …’。表示下一个文件头偏移
量4D20h,当前文件是可执行文件,文件类型是一般文件;文件大小是19612字节;文件名
是“deque”。
制作romfs文件系统映像
genromfs是制作romfs文件系统映像的工具。把需要制作映像的文件放在一个目录下,如romfs目录下。
执行命令:genromfs –v –V “ROMdisk” –f romfs.img –d ./romfs。-f指定生成的镜像名,-d指定需要生成
镜像的目录名。
Romfs文件系统测试
可以把romfs文件系统镜像链接到c代码中,然后在程序中直接操作romfs文件中的数据。把镜像文件链
接成.o文件。ld –r –b binary romfs.img –o romfs.o. romf.o引出符号
_binary_romfs_img_start和_binary_romfs_img_end,这两个符号是字符数组类型,_binary_romfs_img_start
是romfs文件系统数据开始指针,_binary_romfs_img_end是结束指针。通过这个两个符号就可以对文件
系统数据进行操作。例如以下代码
extern D_INT8 _binary_romfs_img_start[];
extern D_INT8 _binary_romfs_img_end[];
/**根据文件名返回该文件的文件头在文件系统中的偏移量**/
D_INT8 *srv_get_romfs_file_head(D_INT8* filename, D_UINT8* start_head, D_UINT16 len, D_UINT32 os)
{
   D_UINT32 offset=0;
   D_UINT32 tmp;
   D_UINT16 i;
 
   offset = os;
   while( _binary_romfs_img_start+offset < _binary_romfs_img_end )
   {
        //printf(”filename=%s, curr=%s ”, filename,start_head+offset+16);
        if( len == strlen(start_head+offset+16) && memcmp(filename, start_head+offset+16, len) == 0 )
                         return start_head+offset;
              tmp = 0;
           for(i=0; i< 4; i++)
        {
           tmp <<= 8;
           tmp += start_head[offset+i];
        }
        offset = tmp;
        offset &= 0xfffffff0;
        if( offset == 0 )
          break;
   }
   return NULL;
}
把romfs.o文件和相应的代码编译,通过_binary_romfs_img_start就可以读取romfs映像中的文件数据。