1、文件系统
文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)分区上的文件的存储方法和数据结构,即在存储设备上组织文件的方法。
看了这个概念如果有些懵,下面用两个例子来说明。
a、ext2文件系统
一个分区格式化成ext2文件系统后,分区里会有三种块inode、block、super block(当然还有其他类型的块)。
Super block: 记录此文件系统的整体信息,包括inode/block的总量、使用量,以及文件系统的格式和相关信息等;
Inode: 记录文件的属性,一个文件占用一个inode,同时记录此文件的数据所在的block号码;
Block:实际记录文件的内容,若文件太大时会占用多个block。
由于每个inode于block都有编号,而每个文件都会占用一个inode,inode内记录有文件数据放置的block号码。因此,我们可以知道的是,如果能够找到文件的inode的话,那么自然就会知道这个文件所放置数据的block号码,当然也就能够读出该文件的实际数据了。这是个比较有效率的做法,因为如此一来我们的磁盘就能够在短时间内读取出全部的数据,读写性能比较好。
(图1-1)
b、Fat文件系统
我们惯用的U盘(闪存),使用的文件系统一般为FAT格式。FAT这种格式的文件系统并没有inode存在,所以FAT没有办法将这个文件的所有block在一开始就全部读取出来。每个block号码都记录在前一个block当中,读取它的方式如图1-2所示。
(图1-2)
通过以上两个例子的说明,我们可以知道,文件系统决定了数据在磁盘上的存储和访问方式。不同的存储介质有着不同的特性,需要选择适当的文件系统。Norflash一般使用jffs2,Nandflash早期使用的是yaffs2,现在一般使用ubifs,因为随着技术的进步Nandflash的容量越来越大,yaffs2已经hold不住了。而mmc我们现在使用的是ext4文件系统。需要了解更多不同文件系统的特性,可以阅读本文的最后一章,记录了常见文件系统的优缺点。
2、根文件系统
根文件系统是一种特殊的文件系统,特殊就在于它必须有目录结构。
Linux启动时,第一个必须挂载的是根文件系统,若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。
根文件系统之所以在前面加一个”根“,说明它是加载其它文件系统的”根“,既然是根的话,那么如果没有这个根,其它的文件系统也就没有办法进行加载的。它包含系统引导和使其他文件系统得以挂载(mount)所必要的文件。根文件系统包括Linux启动时所必须的目录和关键性的文件,例如Linux启动时都需要有init目录下的相关文件,在 Linux挂载分区时Linux一定会找/etc/fstab这个挂载文件等,根文件系统中还包括了许多的应用程序bin目录等,任何包括这些Linux 系统启动所必须的文件都可以成为根文件系统。
二、linux文件系统结构
(1)Linux支持多种文件系统,包括ext2、ext3、vfat、ntfs、iso9660、jffs、romfs和nfs等,为了对各类文件系统进行统一管理,Linux引入了虚拟文件系统VFS(Virtual File System),为各类文件系统提供一个统一的操作界面和应用编程接口。
(2)在嵌入式Linux下,MTD(Memory Technology Device,存储技术设备)为底层硬件(闪存)和上层(文件系统)之间提供一个统一的抽象接口,即Flash的文件系统都是基于MTD驱动层的。
(3)使用MTD驱动程序的主要优点在于,它是专门针对各种非易失性存储器(以闪存为主)而设计的,因而它对Flash有更好的支持、管理和基于扇区的擦除、读/写操作接口。
三、ramdisk文件系统介绍
在 linux 系统中 ,ramdisk 有二种 , 一种(initrd)就是可以格式化并加载, 在 linux 内核 2.0/2.2 就已经支持 , 其不足之处是大小固定 ; 另一种(initramfs)是2.4 的内核才支持 , 通过ramfs 来实现 , 他不能被格式化 , 但用起来方便 , 其大小随所需要的空间增加或减少 , 是目前 linux 常用的 ramdisk 技术。我们这里讲的ramdisk实际上就是initrd,想了解更多initrd和initramfs的区别看我的另一篇文章《initramfs的介绍与制作》。
RamDisk实际上是从内存中划出一部分作为一个分区使用,换句话说,就是把内存一部分当做硬盘使用,你可以向里边存文件。
RamDisk并非一个实际的文件系统,而是一种将实际的文件系统装入内存的机制,并且可以作为根文件系统。实际上它使用的文件系统是ext2。
那么为什么要用RamDisk呢?a) 假设有几个文件要频繁的使用,你如果将它们加到内存当中,程序运行速度会大副提高,因为内存的读写速度远高于硬盘。况且内存价格低廉,一台PC有128M或256M已不是什么新鲜事。划出部分内存提高整体性能不亚于更换新的CPU。b) ramdisk是基于内存的文件系统,具有断电不保存的特性,假设你对文件系统做了什么破坏导致系统崩溃,只要重新上电即可恢复。
四、ramdisk文件系统的制作
1、自己建立根文件系统
(1)创建根文件系统目录
mkdir rootfs
cd rootfs
mkdir root dev etc boot tmp var sys proc lib mnt home usr
mkdir etc/init.d etc/rc.d etc/sysconfig
mkdir usr/sbin usr/bin usr/lib usr/modules
mkdir var/lib var/lock var/run var/tmp
sudo mknod -m 600 dev/console c 5 1
sudo mknod -m 600 dev/null c 1 3
写成一个脚本,避免每次都要一行行敲,如下
(2)拷贝交叉编译工具里面的库
比如我使用的交叉编译工具是arm-none-linux-gnueabi-gcc,先找到该工具的安装目录,使用which is命令
root@silent:/home/mkrootfs# which is arm-none-linux-gnueabi-gcc
/root/CodeSourcery/Sourcery_G++_Lite/bin/arm-none-linux-gnueabi-gcc
然后找到相应的动态库,拷贝到我们的lib目录。必须加上-d参数,才能把连接拷贝过来。
cp /root/CodeSourcery/Sourcery_G++_Lite/arm-none-linux-gnueabi/libc/lib/* /home/mkrootfs/rootfs/lib -rfd
(3)建立etc目录下的配置文件
a、拷贝主机etc目录下的passwd、group、shadow文件到rootfs/etc目录下。
b、创建etc/mdev.conf内容为空。
c、etc/sysconfig目录下新建文件HOSTNAME(主机名),内容为“jimmy”。
d、编辑etc/inittab文件,内容如下:
::sysinit:/etc/init.d/rcS #rcS作为系统初始化文件
console::askfirst:-/bin/sh #在串口启动一个登录会话
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r #告诉init在关机时运行umount命令卸载 所有的文件系统,如果卸载失败,试图以只读方式重新挂载。
::shutdown:/sbin/swapoff -a
注释:由于默认的内核命令行上有init=/linuxrc, 因此,在文件系统被挂载后,运行的第一个程序是根目录下的linuxrc。这是一个指向/bin/busybox的链接,也就是说,系统起来后运行的第一个程序也就是busybox本身。
这种情况下,busybox首先将试图解析/etc/inittab来获取进一步的初始化配置信息。
若没有/etc/inittab这个配置文件,根据busybox的逻辑,它将生成默认的配置。(参考busybox源代码init/init.c中的parse_inittab()函数。其中,该函数里最重要的一个语句,就是new_init_action(SYSINIT, INIT_SCRIPT, “”), 也就决定了接下去初始化的脚本是INIT_SCRIPT所定义的值。这个宏的默认值是“/etc/init.d/rcS”)。
e、编辑etc/init.d/rcS文件,并chmod +x 修改文件权限
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin #启动环境,设置必要的环境变量
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
echo “———-mount all—————-“
mount -a #读取/etc/fstab,加载文件系统
#echo /sbin/mdev>/proc/sys/kernel/hotplug
#mdev –s
echo “****************Hello jimmy*********************”
echo “Kernel version:linux-3.18.28”
echo “***********************************************”
/bin/hostname -F /etc/sysconfig/HOSTNAME #设置机器名字
f、编辑etc/fstab
#device mount-point type option dump fsck
proc /proc proc defaults 0 0
none /tmp ramfs defaults 0 0
#mdev /dev ramfs defaults 0 0
sysfs /sys sysfs defaults 0 0
g、编辑etc/profile
USER=”id -un” #id -un是打印输出当前的用户名,例如:root
LOGNAME=
USERPS1=′[\u@\h
PWD]#’ #“[用户名@主机名 目录]#”, PS1的值即进入shell后的默认
PATH=$PATH
HOSTNAME=’/bin/hostname’ #通过/bin/hostname获取主机名
export USER LOGNAME PS1 PATH
echo “—–/etc/profile——-”
注释:
该文件在进入shell的时候调用,也就是说这是用户相关的.
与环境变量相关的文件可能还会有/etc/bashrc等,不过这是shell变量,是局部的,对于特定的shell起作用。/etc/profile是全局的,适用于所有的shell。
注意:若PS1的按如上设置后,进入控制台后,显示的还是原来的字符,则很有可能是busybox的配置问题:
make menuconfig
busybox settings->busybox library tuning->username completion、fancy shell prompts选上。
重新make即可。
(4)交叉编译busybox
a、首先到busybox官网下载最新的busybox。网址https://busybox.net/
b、修改busybox源码根目录下的Makefile:
CROSS_COMPILE ?=arm-none-linux-gnueabi-
ARCH ?=arm
c、make distclean (清除之前生成的文件)
d、make defconfig (使用默认配置)
e、make (编译)
f、make CONFIG_PREFIX=/home/mkrootfs/rootfs install
执行完后在rootfs 目录下会生成目录bin、sbin 、usr/bin,usr/sbin和文件 linuxrc 的内容。
(5)编译安装内核模块
make modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
make modules_install INSTALL_MOD_PATH=/home/mkrootfs/rootfs
(6)使用工具制作ramdisk文件系统
下载地址:http://genext2fs.sourceforge.net
genext2fs -b 4096 -d rootfs ramdisk
-b是指制作的ramdisk大小为4096K字节 -
d是指要制作成ramdisk的根文件系统目录
最后的ramdisk是制作出来的ramdisk的名字,当然可以改名了。
gzip -9 -f ramdisk
将该ramdisk以最优方式压缩为ramdisk.gz
2、在一个已经建好的文件系统上进行修改
1、解压
#cd ramdisk.gz所在目录
#gunzip ramdisk.gz
在解压后,得到文件系统镜象文件ramdisk, 覆盖了原来的压缩文件
2、镜象文件挂载
镜象文件必须经过挂载后才能看到文件系统中的各个目录和详细内容
#mkdir /mnt/loop
/mnt/loop 是文件系统要挂载到的目录
#mount –o loop ramdisk /mnt/loop
3、在挂载目录/mnt/loop下对文件系统进行操作
#cd /mnt/loop
根据需要增加、删减或是修改文件系统的内容
4、卸装文件系统
跳到/mnt/loop目录外,否则无法卸装,提示busy
#cd ramdisk所在目录
#umount /mnt/loop
5、压缩文件系统
#gzip –v9 ramdisk /*生成ramdisk.gz压缩文件
五、ramdisk作为根文件系统的配置
1、uboot支持ramdisk文件系统
uboot下需要对bootargs参数进行设置,分为两种情况
a、使用mkimage将zImage和ramdisk压缩成了一个文件
bootargs=console=ttyS2,57600n8 rw mem=110M@0xc0000000 root=/dev/ram
b、zImage和ramdisk是分开的
bootargs=initrd=0xc2000000,0x4000000 root=/dev/ram init=/linuxrc console=ttyS2,57600n8 rw mem=110M@0xc0000000
意思为从ramdisk启动,ramdisk压缩文件起始地址在内存地址0xc2000000处,文件大小为0x4000000。这边内存的总大小是110M,ramdisk的大小是64M,发现系统启动时卡在
Uncompressing Linux… done, booting the kernel.
找了老半天发现是因为ramdisk设置太大了,改成52M以下就能正常启动。没搞明白为什么,估计是不能大于内存的一半。
2、linux支持ramdisk文件系统
(1)配置
a、General Setup —> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
b、Device Drivers —> [*] Block devices —> <*> RAM block device support
(1) Default number of RAM disks
(65536) Default RAM disk size(kbytes)
c、File systems —><*>Second extended fs support
[*] Ext2 extended attributes
[*] Ext2 POSIX Access Control Lists
[*] Ext2 Security Labels
d、修改.config中CONFIG_CMDLINE的定义(若uboot下已经对bootargs做了配置,这步可省略)
CONFIG_CMDLINE=”initrd=0xc2000000,0x4000000 root=/dev/ram rw init=/linuxrc console=ttyS2,57600n8 rw mem=110M@0xc0000000
(2)制作
a、zImage和ramdisk是分开的
按照前面的步骤制作好ramdisk.gz,然后使用如下命令制作uImage
mkimage -A arm -O linux -T kernel -C none -a 0xc0008000 -e 0xc0008000 -n “Linux kernel Image” -d zImage uImage
b、zImage和ramdisk压缩成一个文件
gzip -c9 zImage > zImage.gz
mkimage -A arm -O linux -T multi -C gzip -a 0xc0008000 -e 0xc0008000 -n “Linux kernel Image” -d zImage.gz:ramdisk.gz firmware.ext2
mkimage只对两个参数进行说明。
-a指的是加载地址
-e指的是入口地址
使用mkimage生成内核镜像文件时,会在内核的前头加上了64byte的信息,供建立tag之用。bootm命令(bootm xxxx)会首先判断xxxx这个地址与-a指定的加载地址是否相同。
如果不同的话会从这个地址开始提取出这个64byte的头部,对其进行分析,然后把去掉头部的内核复制到-a指定的load地址中去运行之
如果相同的话那就让其原封不同的放在那,但-e指定的入口地址会推后64byte,以跳过这64byte的头部。
我们来看看这两个地址的不同情况:
1) mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -n linux-2.6.18.8 -d zImage uImage
这种情况 ,只能把 uImage download到 30008000的位置上,否则 从0x30008040是启动不了的.
原因:如果将uImage(加了头的镜像文件)下载到不同于指定加载地址的地方,则会进行上面的操作,将去掉头部的内核拷贝到指定的加载地址,此时加载地址和入口地址需要是相同的,因为已经没有镜像头了,所以此时入口地址也应该为30008000,而不应该再加上64个字节.
2) mkimage -A arm -O linux -T kernel -C none -a 30008000 -e 30008000 -n linux-2.6.18.8 -d zImage uImage
这种情况download地址随便. 还是按上面说的,因为将加载地址和入口地址设置成同样的地址,在下载到任意地址时,将去掉头部的内核镜像拷贝到指定加载地址后,可以直接从加载地址开始启动。
3、如何启动系统
a、zImage和ramdisk是分开的
按照上述,在uboot下配置好参数bootargs后,执行以下命令即可。
tftp 0xc2000000 ramdisk.gz
tftp 0xc0700000 uImage
bootm 0xc0700000
b、zImage和ramdisk压缩成一个文件
按照上述,在uboot下配置好参数bootargs后,执行以下命令即可。
tftp 0xc0700000 firmware.ext2
bootm 0xc0700000
六、常见文件系统介绍
(1)flash介绍
不同的flash要选择相应的文件系统才能提高读写效率。要想知道flash适用的文件系统,必须先了解flash的特性。
Flash(闪存)作为嵌入式系统的主要存储媒介,有其自身的特性。Flash的写入操作只能把对应位置的1修改为0,而不能把0修改为1(擦除Flash就是把对应存储块的内容恢复为1)。因此,一般情况下,向Flash写入内容时,需要先擦除对应的存储区间,这种擦除是以块(block)为单位进行的。
闪存主要有NOR和NAND两种技术。norFlash存储器的擦写次数是有限的,NAND闪存则有特殊的硬件接口和读写时序。因此,必须针对Flash的硬件特性设计符合应用要求的文件系统。
一块Flash芯片可以被划分为多个分区,各分区可以采用不同的文件系统;两块Flash芯片也可以合并为一个分区使用,采用一个文件系统。即文件系统是针对于存储器分区而言的,而非存储芯片。
(2)几种常见的文件系统
A、JFFS2日志闪存文件系统版本2 (Journalling Flash FileSystem v2)
JFFS文件系统最早是由瑞典Axis Communications公司基于Linux2.0的内核为嵌入式系统开发的文件系统。JFFS2是RedHat公司基于JFFS开发的闪存文件系统,最初是针对RedHat公司的嵌入式产品eCos开发的嵌入式文件系统,所以JFFS2也可以用在Linux, uCLinux中。
主要用于NOR型闪存,基于MTD驱动层。
优点:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃/掉电安全保护,提供“写平衡”支持等。
缺点:主要是当文件系统已满或接近满时,因为垃圾收集的关系而使jffs2的运行速度大大放慢。
目前jffs3正在开发中。关于jffs系列文件系统的使用详细文档,可参考MTD补丁包中mtd-jffs-HOWTO.txt。
jffsx不适合用于NAND闪存,主要是因为NAND闪存的容量一般较大,这样导致jffs为维护日志节点所占用的内存空间迅速增大,另外,jffsx文件系统在挂载时需要扫描整个FLASH的内容,以找出所有的日志节点,建立文件结构,对于大容量的NAND闪存会耗费大量时间。
B、yaffs:Yet Another Flash File System
yaffs/yaffs2是专为嵌入式系统使用NAND型闪存而设计的一种日志型文件系统。与jffs2相比,它减少了一些功能(例如不支持数据压缩),所以速度更快,挂载时间很短,对内存的占用较小。
另外,它还是跨平台的文件系统,除了Linux和eCos,还支持WinCE, pSOS和ThreadX等。
yaffs/yaffs2自带NAND芯片的驱动,并且为嵌入式系统提供了直接访问文件系统的API,用户可以不使用Linux中的MTD与VFS,直接对文件系统操作。当然,yaffs也可与MTD驱动程序配合使用。
yaffs与yaffs2的主要区别:yaffs仅支持小页(512 Bytes) NAND闪存,yaffs2则可支持大页(2KB) NAND闪存。同时,yaffs2在内存空间占用、垃圾回收速度、读/写速度等方面均有大幅提升。
C、nfs网络文件系统
NFS是由Sun开发并发展起来的一项在不同机器、不同操作系统之间通过网络共享文件的技术。
在嵌入式Linux系统的开发调试阶段,可以利用该技术在主机上建立基于NFS的根文件系统,挂载到嵌入式设备,可以很方便地修改根文件系统的内容。
D、ubifs(UnsortedBlock Image File System,UBIFS)无排序区块图像文件系统
UBIFS可以认为是JFFS2文件系统的下一代。最早在2006年由IBM与Nokia的工程师ThomasGleixner,ArtemBityutskiy所设计,专门为了解决MTD(MemoryTechnology Device)设备所遇到的瓶颈。由于NandFlash容量的暴涨,YAFFS等皆无法再去控制NandFlash的空间。UBIFS通过子系统UBI处理与MTDdevice之间的动作。
UBIFS在设计与性能上均较YAFFS2、JFFS2更适合NAND FLASH。UBIFS支持write-back,其写入的数据会被cache,直到有必要写入时才写到flash,大大地降低分散小区块数量并提高I/O效率。UBIFSUBIFS文件系统目录存储在flash上,UBIFSmount时不需要scan整个flash的数据来重新创建文件目录。支持on-the-flight压缩文件数据,而且可选择性压缩部份文件。另外UBIFS使用日志(journal),可减少对flashindex的更新频率。
E、ext4文件系统
ext4和yaffs2是日志类型的文件系统。日志文件系统就是一种具有故障恢复能力的文件系统,它利用日志来记录尚未提交到文件系统的修改,以防止元数据破坏。
一般来说,ext2或者ext4的文件系统不适用于nand flash,因为nand flash的读写特性,一般使用专为nand flash开发的文件系统,如yaffs2,jffs2,ubifs等。