分类: 嵌入式
2015-02-06 11:26:34
CramFS在Linux嵌入式环境的应用
一,前言
相信从事过嵌入式Linux环境开发的读者们,都会面临到要如何利用最少的系统资源,来建构一个完整嵌入式环境的问题,通常会采取的方式不外乎把Linux Kernel依所要执行的目的环境量身打造,建构一个符合该硬件平台的Linux Kernel,避免不必要的核心功能与驱动程序。
此外,我们还能动手的就是缩减动态函式库的大小,依据目标系统执行文件呼叫的动态函式库来进行整理,过滤用不到的函式库。甚至可以进一步的过滤掉函式库中用不到的函式本身。
透过这些方式,我们可以得到一个精简的Linux执行环境,不过我们还必须考虑到的部分就是,嵌入式环境中Linux执行环境所使用内存储存媒体。在Linux嵌入式环境中,许多人会采用的方式就是透过RAMDISK来储存档案系统的内容,所谓的RAMDISK就是在开机时,我们把一部份的内存虚拟成磁盘,并且把之前所准备好的档案系统映像文件解压缩到该RAMDISK环境中。
利用RAMDISK,我们必须要耗用部分的内存做为储存的媒介,对于嵌入式的环境中,这就象征可以使用的内存资源减少了。举个例子来说,如果档案系统的压缩率为50%,我们在一个8MB的Flash上面放置我们的执行环境,其中包括了Embedded QT、SendMail Server、Apache Server......等,假设在不压缩的情况下需要16MB的储存空间才能安置所有的执行环境,经过压缩后恰好可以放入一个8MB的Flash上。当我们启动这个嵌入式的Linux环境后,Linux核心必须配置一个16MB的RAMDISK内存空间来储存原本8MB Flash上面解压缩出来的执行环境。我们在开机完成后,便可以在这16MB 的RAMDISK环境里进行我们的工作,可是我们会注意到的一点就是我们总共耗费了16 MB的动态内存与8MB的Flash空间。而且这两个储存媒体其实所要储存的数据都是一样的,只不过一个经过压缩,而一个是解压缩后的环境。
因为有了这样的问题存在,所以在嵌入式Linux上面对于压缩式的档案系统有了它存在的必要性,因此CramFS的出现正好可以解决这样的问题。
CramFS是Linus Torvalds在Transmeta任职时,所参与开发的档案系统,笔者在本篇文章中所采用的Linux Kernel版本为2.4.3,把Linux Kernel原始码解开后,各位可以在”linux/fs/cramfs”中找到CramFS的原始码。不过目前CramFS为一个只读的档案系统,也就是说使用CramFS的话,如果我们的嵌入式环境需要储存暂时性的数据,就必须另外保留一个Flash空间做为储存数据之用。
在我们采用了CramFS之后,如同之前RAMDISK所举的例子,我们可以把原本的执行环境压缩到8MB的Flash中,如果我们现在要浏览目录或是要读取Flash中的档案时,CramFS档案系统会动态的去算出压缩后的数据所储存的位置,在实时的解压缩到内存中,对于使用者来说,使用CramFS与RAMDISK是感觉不出使用上的差异性,在笔者的测试过程中﹝PII 350的机器﹞几乎感觉不出系统在解压缩上的延迟。
相信各位读者到此,应该对于RAMDISK与CramFS两者的不同有所了解,如下图﹝一﹞所示就是一个RAMDISK环境的启动流程,我们可以在图的左边看到实体的内存中,必须要划分出一块区域做为RAMDISK虚拟磁盘装置所需的内存空间,而剩下的物理内存才是Linux Kernel与使用者程序所能使用的部分。
图﹝一﹞,RAMDISK的启动流程
如下图﹝二﹞所示,就是一个CramFS档案系统映像文件的结构,首先我们可以看到最前面就是CramFS的Superblock,大小共76 bytes。之后便是CramFS的inode结构,最需要注意的一点就是每个cramfs_inode的结构大小为12 bytes,而每个cramfs_inode所代表的文件名称直接就会接在cramfs_inode的后面,以0x00结尾。并且 ”cramfs_inode + 文件名称”的长度必须为4的倍数,如果不足的部份就会补0,如果长度恰好为4的倍数,那就不补0直接连接下一个cramfs_inode。
如此 ”cramfs_inode + 文件名称”+ ”cramfs_inode + 文件名称” + ”cramfs_inode + 文件名称”........,的方式就构成了CramFS档案系统映像文件的目录结构。
图﹝二﹞CramFS档案系统映像文件的结构
安装完Syslinux与CramFS档案系统映像文件的扇区分割内容如下图﹝三﹞所示,
图﹝三﹞,使用Syslinux与CramFS映像文件的扇区分割内容
CramFS在系统的架构
如下图﹝四﹞所示,CramFS在开机的过程中,在VFS档案系统启动后就会跟着被初始化,其中CramFS档案系统的初始化进入点为init_cramfs_fs﹝包含在 linux/fs/cramfs/inode.c的档案中﹞
图﹝四﹞,CramFS档案系统初始化的流程
CramFS初始化是透过呼叫函式init_cramfs_fs﹝﹞来进行的,我们可以由图中看到在函式init_cramfs_fs﹝﹞的最后,会呼叫register_filesystem﹝﹞对系统进行注册,以便于在遇到档案系统型态为 “cramfs”的档案系统时,可以由系统交给CramFS档案系统来进行处理。
图﹝五﹞,Mount CramFS的流程
在函式cramfs_read_super﹝﹞中,会呼叫函式cramfs_read﹝﹞把Superblock读取到内存中,并且进行Superblock扇区的型态确认,例如
//确认Superblock的参数magic是否为 0x28cd3d45,
//若非则结束函式,并传回NULL
if (super.magic != CRAMFS_MAGIC)
{
printk("wrong magic\n");
goto out;
}
//确认Superblock的参数signature是否为 "Compressed ROMFS",
//若非则结束函式,并传回NULL
if (memcmp(super.signature, CRAMFS_SIGNATURE, sizeof(super.signature)))
{
printk("wrong signature\n");
goto out;
}
//参数flags默认值为0,而CRAMFS_SUPPORTED_FLAGS值为0xff,
//在&运算后,若为1则结束函式,并传回NULL
if (super.flags & ~CRAMFS_SUPPORTED_FLAGS)
{
printk("unsupported filesystem features\n");
goto out;
}
函式cramfs_read_super﹝﹞的最后,会呼叫函式get_cramfs_inode﹝﹞,取得CramFS档案系统根目录的信息。
如果我们在CramFS的档案系统中,浏览一个目录的内容,CramFS档案系统会直接去读取Directory Structure扇区中的内容,并且把完整的目录结构秀出来,主要的原因是在于档案的实体数据本身会透过CramFS来压缩,可是文件名称与目录的架构,是不会透过CramFS档案系统压缩的,所以说这些查询目录的过程,就无须透过解压缩的流程,可以直接透过查询Directory Structure扇区的内容,来得到我们所要的结果
如下图﹝六﹞所示,
图﹝六﹞,查询CramFS档案系统的目录内容
笔者在此把搜寻目录的流程大略说明一下,首先我们可以由Superblock取得根目录﹝”/”﹞的cramfs_inode,例如﹕根目录的offset为19,
mode:41ffh uid:0h size:204 gid:0h namelen:0 offset:19 根目录
也就是说这个根目录底下的档案或是目录的数据会放在由CramFS扇区起始往后偏移19*4=76 bytes的位置。接下来,我们偏移到76 bytes的位置,依序把”cramfs_inode + name”的结构读取出来,得到如下的结果
mode:41edh uid:0h size:0 gid:0h namelen:3 offset:0 lost+found
mode:41edh uid:0h size:1208 gid:0h namelen:1 offset:70 bin
mode:45edh uid:0h size:3536 gid:f6h namelen:1 offset:372 dev
mode:45edh uid:0h size:264 gid:f6h namelen:1 offset:1256 etc
mode:41edh uid:0h size:184 gid:0h namelen:1 offset:1334 lib
mode:a1ffh uid:0h size:9 gid:0h namelen:2 offset:163264 linuxrc
mode:45edh uid:0h size:0 gid:f6h namelen:1 offset:0 proc
mode:45edh uid:0h size:176 gid:f6h namelen:1 offset:1380 sbin
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 tmp
mode:45edh uid:0h size:0 gid:f6h namelen:1 offset:0 usr
mode:41edh uid:0h size:0 gid:0h namelen:1 offset:0 var
其中,mode的值可以用来判断目前的cramfs_inode是为目录或是档案型态。
如果说我们现在要查看etc目录下的所有档案或是目录名称,因为”etc”cramfs_inode的offset值为1256,所以etc目录底下的数据会存放在距离CramFS扇区起始位置偏移1256*4=5024 bytes的cramfs_inode。所以我们现在由CramFS扇区起始位置偏移5024bytes,得到如下的结果
mode:45edh uid:0h size:48 gid:f6h namelen:1 offset:1322 rc.d
mode:81a4h uid:0h size:376 gid:f6h namelen:2 offset:12288 inittab
mode:81edh uid:0h size:21 gid:f6h namelen:2 offset:12331 passwd
mode:81edh uid:0h size:13 gid:f6h namelen:2 offset:12339 group
mode:81a4h uid:0h size:437 gid:f6h namelen:2 offset:12345 profile
mode:81a4h uid:0h size:97 gid:f6h namelen:3 offset:12411 protocols
mode:81a4h uid:0h size:11349 gid:f6h namelen:2 offset:12435 services
mode:81a4h uid:0h size:20 gid:f6h namelen:2 offset:13602 hosts
mode:81a4h uid:0h size:26 gid:f6h namelen:3 offset:13610 host.conf
所以啰,透过这样的方式,我们就可以把CramFS档案系统映像文件的目录内容解读出来啰。不论是目录的内容或是档案压缩过的数据储存位置,都可以经由Offset值来推算出来,并且读取解压缩到内存中。
CramFS档案系统预设是每次都会解压缩4Kbytes的数据到Linux Cache Memory中。所以说,如果读者去观察CramFS的读取运作时,会发现只有第一次档案被读取时才会动态的去解压缩,第二次与第二次以后的档案读取动作就会直接去该档案目前所对应到的Linux Cache Memory来读取,而不会再去解压缩,耗费系统运算资源。这样的运作原理,与我们一般使用的Linux 档案系统﹝例如﹕Ext2﹞是一致的,透过一个Cache的机制,让目前被读取的档案不必要每次都从磁盘驱动器中读取出来,浪费许多磁盘驱动器搜寻的时间,把目前使用的资料暂存在Cache中,可以增加每一次读取档案的速度。如果在CramFS档案系统中,档案大小超过 4Kbytes的话,就会分多次来解压缩。
如下图﹝七﹞所示,在Linux的环境下,解决读取大型档案的方式为,当使用者开启一个大型档案时,系统并不会一口气就把该文件的内容读取到内存中,所采取的方式是当使用者读取到档案的某个位置时,在依据该档案目前所读取内容储存的扇区,来动态的从磁盘系统中读取出来,加载到内存中。
图﹝七﹞,CramFS档案系统读取档案内容的示意图
这样的方式好处就是,开启大型档案时不会一下子就耗用掉过多的内存空间,而可以针对目前实际已经读取的部分才配置内存,尚未读取的部份就不配置内存,以节省内存的资源。CramFS档案系统会动态的去把档案每个区块,依目前读取的进度与位置,来动态的解压缩到内存中.