2012年2月27日 星期一

2012年2月26日 星期日

qemu 命令

qemu -kernel arch/i386/boot/bzImage -initrd ../hello2-initramfs/my_initramfs.cpio.gz -hda /dev/zero

Kernel, BusyBox. Ramdisk


Linux Kernel編譯+BusyBox+Ramdisk 筆記

wget http://www.busybox.net/downloads/busybox-snapshot.tar.bz2 <-- busybox檔案

Step1:編譯Kernel
抓下新版Kernel到/usr/src
#tar -jxvf linux-2.6.23.12.tar.bz2
#ln -s linux-2.6.23.12 linux
#cd linux
#make menuconfig
#make bzImage
將bzImage 複製到/boot/


Step2:建立映像檔以及掛載映象
#cd root
#mkdir eb
#cd eb
#dd if=/dev/zero of=initrd.img bs=1k count=8192
#/sbin/mke2fs -F -v -m0 initrd.img
#mkdir ramfs
#mount -o loop initrd.img ramfs/

Step3:編譯BusyBox
#cd /usr/src/busybox
#make menuconfig (以下為參考)

Busybox Settings ->
Build Options ->
[*] Build BusyBox as a static binary (no shared libs)
( 將busybox編譯為靜態連接 )

Init Utilities ->
[*] init
[*] Support reading an inittab file
( 支援init讀取/etc/inittab配置 )

Shells ->
Choose your default shell (ash) >
[*] ash

Coreutils ->
[*] cp
[*] cat
[*] ls
[*] mkdir
[*] echo
[*] env
[*] mv
[*] pwd
[*] rm
[*] touch

Editors ->
[*] vi

Linux System Utilities ->
[*] mount
[*] umount
[*] Support loopback mounts
[*] Support for the old /etc/mtab file

Networking Utilities ->
[*] inetd
( 支援inetd超級伺服器 )
#make PREFIX=/root/eb/ramfs/ all install
PREFIX指明安裝路徑,就是我們根系統路徑


Step4:建立FileSystem
#cd /usr/src/busybox/_install/
#cp -ar * /root/eb/ramfs
#cd /root/eb/ramfs
#mkdir dev etc lib proc tmp var(bin,sbin,usr已有)
#chmod 1777 tmp
#mkdir usr/lib(bin,sbin已有)
#mkdir var/lib var/lock var/log var/run var/tmp
#chmod 1777 var/tmp
#cd /dev
#cp -ar zero loop* tty? tty sda sda? fd random ram* null console /root/eb/ramfs/dev
#cd /lib
#for file in libc libcrypt libdl libm libpthread libresolv libutil; do cp -a /lib/$file-*.so /root/eb/ramfs/lib/; cp -d /lib/$file.so.[*0-9] /root/eb/ramfs/lib; done
#cp -d ld*.so* /root/eb/ramfs/lib
#cd /root/eb/ramfs/etc
#mkdir init.d
#cd init.d
#nano -w rcS

#!/bin/sh
#mount -n -o remount,rw /

#chmod 755 rcS
#cd /root/eb
#umount /root/eb/ramfs


Step5:修改開機檔
#gzip -9 initrd.img
#cp initrd.img.gz /boot
修改/boot/grub/menu.lst加入

title Embedded Linux
root (hd0,3) <-- 看自己的磁區決定
kernel /boot/bzImage root=/dev/ram ramdisk_size=16000
initrd /boot/initrd.img.gz
quiet

==========================================

initrd觀念整理

昨天在看以前自己寫initrd ramdisk時
發現寫的不夠完整
讓在下覺得總是有不通的地方
雖然對embedded system來說
kernel image與root file system所佔空間要最佳化沒錯
但跟initrd有什麼關係呢

我們會希望compile出來的kernel image愈小愈好
但硬體更新速度太快
總不能每天都在為了少數的硬體一直rebuild kernel吧
(喵的,每天是都很閒喔...話說我每天好像都真的很閒XD)
因此initrd的目的就是把一些常用的硬體驅動(USB, SCSI,ATA等)
包進來,使kernel image盡量維持一致性
另一方面,initrd又可彈性修改內容

接下來我們將談到使用initrd時
system boot-up operation是如何進行的
在此之前
我們先要根據linux man page
提到/dev/initrd這個special file

在系統啟動時
最先執行的程式就是bootloader
而bootloader還沒load kernel image時
會initialize一塊read-only的block device
也就是RAM disk
提供給initrd image(當作/dev/initrd)

而因為initrd特性
使系統在boot up時分成兩個phase:
第一階段bootloader把kernel image和initrd image
load 到memory中,當然initrd一樣被當成RAM disk
在kernel啟動後需要執行程式因此在這裡要
mount initial root file system(from initrd)
第二階段initial rootfs開始執行額外的driver或module
一旦基本程序處理完
這暫時的initial rootfs就要umount
而mount “真正的” root file system(可能來自其他的device)
再由“真正的” rootfs接替繼續boot up sequence

好了
最後就是條列式的整理使用initrd時boot-up operation:
(由於本人懶得翻譯,再說英文也許比較好理解...)
1. The boot loader loads the kernel program and /dev/initrd's contents into memory.

2. On kernel startup, the kernel uncompresses and copies the contents of the device /dev/initrd onto device /dev/ram0 and then frees the memory used by /dev/initrd.
(之前一直搞不懂為啥free memory了還有辦法存取,原來...)

3. The kernel then read-write mounts device /dev/ram0 as the initial root file system.

4. If the indicated normal root file system is also the initial root file-system (e.g. /dev/ram0 )
then the kernel skips to the last step for the usual boot sequence.
(也許embedded system是這樣子沒錯...)

5. If the executable file /linuxrc is present in the initial root file-system,
/linuxrc is executed with UID 0. (The file /linuxrc must have executable permission.
The file /linuxrc can be any valid executable, including a shell script.)
(2.6以後若是cpio格式,則是執行/sbin/init)

6. If /linuxrc is not executed or when /linuxrc terminates, the normal root file system is mounted. (If /linuxrc exits with any file-systems mounted on the initial root file-system, then the behavior of the kernel is UNSPECIFIED. See the NOTES section for the current kernel behavior.)

7. If the normal root file has directory /initrd, device /dev/ram0 is moved from / to /initrd. Otherwise if directory /initrd does not exist device /dev/ram0 is unmounted. (When moved from / to /initrd, /dev/ram0 is not unmounted and therefore processes can remain running from /dev/ram0. If directory /initrd does not exist on the normal root file-system and any processes remain running from /dev/ram0 when /linuxrc exits, the behavior of the kernel is UNSPECIFIED. See the NOTES section for the current kernel behavior.)

8. The usual boot sequence (e.g. invocation of /sbin/init) is performed on the normal root file system.

看網路上的高手是說,2.6以後若採用cpio格式
一旦/init執行後,initrd的任務就到此結束
然後將控制權交給kernel了
所以處理過程中不會有kernel插手進來
我猜也許6. 7.就不用理他了

終於寫完這篇了
有錯的話...會改...改天再說

references:
http://linux.die.net/man/4/initrd



Initrd ramdisk

執行QEMU時,會有這個選項
-initrd file

在一般嵌入式系統中,
記憶體空間是很有限的,
而我們知道kernel image和root file system必須放在flash ROM
為了把flash ROM成本壓低,
kernel image和rfs的使用空間勢必要最佳化到最小

於是使用initrd的時機就來了
initrd包含最基本的root file system (temp)以及常見的硬體模組
通常initrd會被壓成gzip,
在開機的時候bootloader會告訴kernel initrd在哪裡,
接著kernel 會決定initrd的格式

一般initrd會包成image,
則他會被掛載到/dev/ram0 (此driver被static link到kernel內),
當成initial root file system

之後此rfs的/sbin/init被執行(uid 0),
而init會mount 真正的root file system (from different devices)
由真正的root file system來執行他自己的/sbin/init,
開始跑一般的boot程序
而initrd file system會被unmount

另外,從Wiki可以知道
kernel與initrd可以放在
  • root file system
  • 一個小的 ext2 或 FAT 格式的分割區(通常掛載到/boot)
  • TFTP server 內(系統必須使用網路開機 NFS)
通常是在開機時機去決定哪些driver要被載入
一般的做法是把常見的硬體driver放在initrd,
接著call hotplug,
去載入偵測到的硬體其對應的驅動程式

reference:
http://lxr.linux.no/linux/Documentation/initrd.txt



Intro. of bare-level applications

bare的意思是空的,光禿禿的,沒有裝飾過的

所謂的bare-level就是沒有OS support或沒有firmware
舉例來說,BIOS就是bare-level的程式

通常這一類程式的主要工作除了要初始化硬體外,
比較重要的是,
高階語言(阿...對於ASM來說,C就是high level lang.)的環境建置,
以及OS的載入和啟動

然而,嵌入式系統並沒有類似PC的BIOS
因此這些任務全靠bootloader來完成
且每個嵌入式系統的硬體設定不一定相同
bootloader實作方法就需要非常客製化

目前最常見的bootloader大概有以下幾個:
U-Boot - 支援多種CPU (ARM, PowerPC, MIPS, SPARC, ...)
Vivi - ARM
Yamon - MIPS

接下來把bootloader的觀念重點整理一下
Sequence:
  1. Hardware initialization
  2. Program execution context environment construction
  3. Operating system loading & start
1.
1)CPU Init (for ARM)
  • Switch SVC mode
  • Disable IRQ/FIQ
  • Disable I/D Cache
  • Disable MMU
2)Config. memory controller
  • Refresh timing setup
3)remapping
  • Using SRAM
2.
bootloader will copy itself (.text and .data) to SDRAM
high-level language for run-time environment
needs stack, heap, bss(un-initialized global variables),
then switch to startup of C

3.
1)kernel packing
  • vmlinux(ELF object) -> objcopy -> image (binary) ->
  • gzip -> compressed binary kernel -> bootable kernel image (vmlinuz)
  • vmlinuz usually includes head.O, decompression.O, and kernelname.O
2)kernel decompression
  • vmlinuz is copied from flash into addr. 0x8000 placed in SDRAM
  • pass system parameters to linux kernel by ATAG table
  • allocate ATAG table and registers by C code
--
thanks to sing's materials XD



preliminary

reference URL: http://en.wikipedia.org/wiki/Embedded_Linux

顧名思義
想要在embedded system上跑linux
那kernel就一定要重新compile成target
所需要的模組即可

make menuconfig就是做這件事

根據網路上某篇NCTU的paper
embedded linux可被壓縮的部分可分成四種

1. Kernel
2. Daemons
3. Libraries
4. AP&Utilities

把kernel編譯好之後
接下來就是產生root filesystem
busybox是目前最普遍使用的工具
root filesystem塞了/bin、/etc、/proc等
開機時所需要的服務(init)
是最占空間的來源
怎麼把rfs縮小是個大問題

busybox真是個好人...

==============================================================


一直以来,都认为nash仅仅是作为initrd.img当中加载驱动所使用的命令解释器而已,并没有对initrd.img当中的linuxrc脚本作一个深入的了解。例如,Redhat ES3当中的initrd.img内容(各系统略有不同)如下:
#!/bin/nash
echo "Loading scsi_mod.o module"
insmod /lib/scsi_mod.o
echo "Loading sd_mod.o module"
insmod /lib/sd_mod.o
echo "Loading libata.o module"
insmod /lib/libata.o
echo "Loading ata_piix.o module"
insmod /lib/ata_piix.o
echo "Loading jbd.o module"
insmod /lib/jbd.o
echo "Loading ext3.o module"
insmod /lib/ext3.o
echo Mounting /proc filesystem
mount -t proc /proc /proc
echo Creating block devices
mkdevices /dev
echo Creating root device
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount -o defaults --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc


前面是加载驱动部分,而后面是什么用处,就没有深究了。
而我自己用的虚拟机里通常会把驱动和文件系统支持编译到内核,也压根用不着initrd.img,所以把这个宝矿放了过去。


今天几个学生在Redhat 9的基础上升级内核到2.6.19,并且是驱动、文件系统用模块方式支持,终于出了问题。该来的还是要来的,应了那句话“出来混,迟早都是要还的”。
学生们把前面的硬盘驱动、文件系统驱动都替换成2.6的模块,后半部分在我的误导下删除(当然没有删除的也多半没有成功),结果死活不能进入系统,报无法挂载根文件系统的kernel panic错误。
查了半天,发现所有的驱动都加载正常,即便是在linuxrc当中加入bash获得一个shell,检查设备和文件系统的状况也都是正常,可就是提示找不到根文件系统。


开始引起我注意的是,通常在硬盘驱动和文件系统支持都完备的情况下它并没有提示说VFS的root=xxxx错误,而直接提示的“VFS: Unable to mount root fs on (0, 0)”。
这意味着它并未识别在grub.conf当中传递给kernel的root=/dev/xxx参数,因为Linux的设备主设备号是不会为0的。


我曾怀疑root=参数没有被接受,但却注意到“Please append a correct "root=" boot option”的提示。
为了找出问题,我在linuxrc中用bash取得了一个shell,然后挂载proc文件系统后检查/proc/sys/kernel /real_root_dev,发现其值为0。
这说明kernel得到的真实root文件系统的设备号不正确(当然有例外,注意后面的解释)。我试着把正确的设备值(例如/dev/sda2用0x802)写入,退出bash之后启动果然正常了。这说明kernel解析参数root=/dev/xxx出现了错误。


经过核对内核代码,发现kernel确实从root=这个参数传递中获得真实的根文件系统的位置,但是这些/dev/xxx会被转换成内核可识别的设备表示形式。
转换的过程中内核会试图检查传递进来的/dev/xxx设备是否存在,而这一步是kernel初始化过程中完成的。当我们把硬盘驱动编译为模块时,kernel初始化过程中硬盘尚不能被识别(要等到kernel初始化完,使用initrd.img才能加载硬盘驱动),于是真实根文件系统的设备号就不能被正确转换,使得其保留为初始值0。


那对于使用硬盘驱动的情况下如何解决这个问题呢?那就是initrd.img后半部分的工作。
在加载完硬盘驱动后,脚本会用mkrootdev生成设备 /dev/root,通过man nash你会发现这个命令的作用是根据内核传递参数当中的root=来创建对应该设备的节点,节点名就是/dev/root。
之后它会把这个设备挂载到 /sysroot这个位置,然后用pivot_root这个根交换的命令把真正运行的根文件系统切换到/sysroot之下,而运行linuxrc的 initrd的文件系统将被挂载到真正的根系统下的initrd目录。


至于“echo 0x0100 >  /proc/sys/kernel/real-root-dev”这个命令,看起来是告诉内核真正的文件系统是/dev/ram0,其实是利用内核判断使用/dev/ram0为根文件系统时不再mount其他设备作为根文件系统的分支,作了一个小小的技巧骗过内核。


整个脚本的关键在于mkrootdev这个命令。它不仅能够根据root=/dev/xxx来生成对应的设备节点,还能够在碰到root=LABEL= /的情况下探测所有的硬盘分区,以便找到对应着卷标为/的分区
这也解开了我一直没弄清楚的为什么root=LABEL=xxx的参数有些环境可以用而有些却不行的谜团。
这个LABEL=/的解析根本不是内核完成的,而是initrd.img当中linuxrc脚本的mkrootdev命令来完成的
脚本的第二个关键在于它负责完成了本是由内核做的挂载真实根文件系统的动作,并对当前根(即initrd使用的内存文件系统)和真实根文件系统进行根切换,又利用欺骗让内核误认为当前使用/dev/ram0作为根文件系统可以不再挂载真实的根文件系统,可谓用心良苦啊 ^_^


当然,如果完全不用initrd.img的机制,你会发现/proc/sys/kernel/real_root_dev的值也是0。这并不是内核也弄错了,而是只有在使用initrd.img机制时,real_root_dev才反映了内核中记录的真实根文件系统的值,虽然它不一定准确。


最后,总结一下:
1、当硬盘驱动以模块形式提供时root=xxx传递给内核的参数可能不会直接起作用,内核在检查这个参数时可能会发现这个设备(因为没有加载驱动而)不存在,因此导致内核没有接受root=xxx的参数。
2、在这种情况下,initrd.img机制的作用不单单是加载驱动这么简单,还肩负着把正确的真实根的设备位置告知内核的艰巨任务。不过实现的方法有两种:
  2.1 把正确的根设备号写入/proc/sys/kernel/real_root_dev,或者
  2.2 自己挂载真正的根设备,并进行根交换把根系统切换到真实的根系统上,还要阻止内核去挂载它认为的真实根系统
  对于2.2,挂载真正的根系统用nash解释器的内置命令mkrootdev完成,它可以根据传递给kernel的参数(估计读了/proc /cmdline)获得/dev/xxx的字符串,或者得到LABEL=xxx的字符串后查询各个分区的卷标来得到对应的真实根设备号。
3、如果没有使用initrd.img的机制,/proc/sys/kernel/real_root_dev的值没有任何处理,因为它只有在调用initrd.img的时候才会被同步于内核中的操作对象。


    另外,redhat 9所带的nash解释器(版本3.4.42)在处理mkrootdev的流程上是有bug的。如果传给kernel的参数使用root=/dev/xxx 的形式,那么nash会从/proc/sys/kernel/real_root_dev里获取用户想使用的真实根文件系统位置,显然这在我们上面的分析当中是0,也就是错误的;而如果参数传递是root=LABEL=/的形式,那么nash的处理走的另外一个分支,它会通过解析所有分区的卷标来查找所要的真实根文件系统,并不再查询/proc/sys/kernel/real_root_dev,这倒让我们可以得到正确的结果。bug呀bug。
    不过FC6所带的nash 5.1.19已经完全不是这个流程了,应当不存在这样的bug。
==============================================================


[教學] linuxrc 實作剖析
« 於: 2005-08-15 16:14 »
linuxrc 實作剖析
本篇背景知識:linuxrc, busybox, initrd, bootloader(lilo or grub)

linuxrc 有何特異功能,請自行研究,本篇只說明如何應用。
linuxrc 通常在 initrd.img 內出現,由於此時所有檔案系統皆未掛載,
所以相關的 shell、binary、library 都要先包在裡面,甚至一些 kernel
可能沒有編譯進去的 modules 都要放在 initrd.img內。

如果你只使用一個 initrd.img 當成檔案系統的全部,沒有要把 root /
改成硬碟的 partition 的話,那就不需要 linuxrc,而且,我看 2.4.20
的 soruce,有一段是判斷,如果你的 root 是 ramdisk ,就不執行 linuxrc。

本篇測試的 kernel 都是 2.4.20 ,所以不同的版本,可能稍有不同。

先來看看一段很簡單的 linuxrc (本內容抄自 mdk 10.0)
代碼: [選擇]

#!/bin/nash
insmod /lib/jbd.ko
insmod /lib/ext3.ko
mount -t proc /proc /proc
mkdevices /dev
mount -t sysfs none /sys
mkrootdev /dev/root
umount /sys
echo 0x0100 > /proc/sys/kernel/real-root-dev
mount -o defaults --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
handledevfs
umount /initrd/proc

nash是很小的 shell ,剛好把 linuxrc 需要的功能都放進去,
所以我們就選擇它了。
代碼: [選擇]
echo 0x0100 > /proc/sys/kernel/real-root-dev
是告訴 kernel ,我們的 root 是掛在第0個 ramdisk 上,
可以查一下
代碼: [選擇]
ll /dev/rd/0
brw-------  1 root root 1, 0  1月  1  1970 /dev/rd/0

若是 /dev/hda8 則是 0x308,查一下
代碼: [選擇]
ll hda8
brw-r-----  1 root root 3, 8  9月  9  2002 hda8
那個 3 及 8 就是你在 mknod /dev/hda8 b 3 8 中的 3 及 8,
不過,記得要轉成十六進位就是。
在 bootloader 中的 root=/dev/xxx 會傳到 /dev/root,
所以你要把 bootloader 內的 root=/dev/xxx 視為你的 root ,
就要用 mount 把它 mount 成 root 就成了。
再來是很多人的疑問,root 不是早被 initrd 掛用了嗎?沒錯,
所以我們要用 pivot_root 來改一下,
代碼: [選擇]
pivot_root /sysroot /sysroot/initrd
這個的意思是,原來的 root / 改由 /sysroot 來做 root / ,
而目前的 initrd 就改成掛在 /sysroot/initrd 底下,
這邊注意一下,initrd.img 內的 /sysroot, 及一般硬碟內的 /initrd
都是寫死的目錄,所以你把它們砍了,就開不了機唷!

如果我們不管 bootloader 傳入 root= 是何方神聖,我們就是要指定
/dev/hda8,也可以這麼做:
代碼: [選擇]

#!/bin/nash
mount -t proc /proc /proc
echo 0x308 > /proc/sys/kernel/real-root-dev

因為 kernel 預設會去跑 /sbin/init ,你都告訴它是 /dev/hda8 了,
它就會自己去抓來跑。

再來就是比較複雜一點的,NFS boot,由於比較複雜些,我們改用 busybox
的 sh 來用,當然,這個 initrd.img 的大小也會大很多。
代碼: [選擇]

#!/bin/sh
mount -t proc proc /proc
echo 0x100 > /proc/sys/kernel/real-root-dev
insmod mii
insmod via-rhine
insmod sunrpc
insmod lockd
insmod nfs
ifconfig eth0 192.168.0.88
route add default gw 192.168.0.1
[ -x /sbin/portmap ] && /sbin/portmap
mount -t nfs 192.168.0.2:/nfs /sysroot
killall portmap
pivot_root /sysroot /sysroot/initrd

其中 192.168.0.88 是本地 ip,192.168.0.1 是指 router,
192.168.0.2是指 NFS server 及其開於 /nfs 的目錄。

這個 NFS boot 的做法和第一個很像,只是要事先準備好連線工作,
最後把把 root 切換過去就成了。
要注意一點,這些工作都要在 linuxrc 內做,如果你留在 rc.sysinit 來
做就來不及了,會啟動不了唷!切記!切記!

再來,變化一下,如果你的 root file system 是一個 ext2 image file
放在 /dev/hda1 的 /root_fs.img ,也可以直接拿來開機,
代碼: [選擇]

#!/bin/sh
mount -t proc proc /proc
echo 0x100 > /proc/sys/kernel/real-root-dev
mkdir /mnt
mount /dev/hda1 /mnt
mknod /dev/loop0 b 7 0
losetup /dev/loop0 /mnt/root_fs.img
mount /dev/loop0 /sysroot
pivot_root /sysroot /sysroot/initrd

Linux Filesystem

 Linux支持多種文件系統,包括ext2ext3vfatntfsiso9660jffsromfsnfs等,為了對各類文件系統進行統一管理,Linux引入了虛擬文件系統VFS(Virtual File System),為各類文件系統提供一個統一的操作界面和應用編程接口。
    Linux下的文件系統結構如下:
    Linux啟動時,第一個必須掛載的是根文件系統;若系統不能從指定設備上掛載根文件系統,則系統會出錯而退出啟動。之后可以自動或手動掛載其他的文件系統。因此,一個系統中可以同時存在不同的文件系統。
    不同的文件系統類型有不同的特點,因而根據存儲設備的硬件特性、系統需求等有不同的應用場合。在嵌入式Linux應用中,主要的存儲設備為RAM(DRAM, SDRAM)ROM(常採用FLASH存儲器),常用的基於存儲設備的文件系統類型包括:jffs2, yaffs, cramfs, romfs, ramdisk, ramfs/tmpfs等。

    1. 基於FLASH的文件系統
     Flash(閃存)作為嵌入式系統的主要存儲媒介,有其自身的特性。Flash的寫入操作只能把對應位置的1修改為0,而不能把0修改為1(擦除Flash就是把對應存儲塊的內容恢復為1),因此,一般情況下,向Flash寫入內容時,需要先擦除對應的存儲區間,這種擦除是以塊(block)為單位進行的。
     閃存主要有NORNAND兩種技術(簡單比較見附錄)Flash存儲器的擦寫次數是有限的,NAND閃存還有特殊的硬件接口和讀寫時序。因此,必須針對Flash的硬件特性設計符合應用要求的文件系統;傳統的文件系統如ext2等,用作Flash的文件系統會有諸多弊端。
     在嵌入式Linux下,MTD(Memory Technology Device,存儲技術設備)為底層硬件(閃存)和上層(文件系統)之間提供一個統一的抽象接口,即Flash的文件系統都是基於MTD驅動層的(參見上面的Linux下的文件系統結構圖)。使用MTD驅動程序的主要優點在於,它是專門針對各種非易失性存儲器(以閃存為主)而設計的,因而它對Flash有更好的支持、管理和基於扇區的擦除、讀/寫操作接口。
     順便一提,一塊Flash芯片可以被划分為多個分區,各分區可以採用不同的文件系統;兩塊Flash芯片也可以合並為一個分區使用,採用一個文件系統。即文件系統是針對於存儲器分區而言的,而非存儲芯片。
     (1) jffs2
     JFFS文件系統最早是由瑞典Axis Communications公司基於Linux2.0的內核為嵌入式系統開發的文件系統。JFFS2RedHat公司基於JFFS開發的閃存文件系統,最初是針對RedHat公司的嵌入式產品eCos開發的嵌入式文件系統,所以JFFS2也可以用在Linux, uCLinux中。
     Jffs2: 日志閃存文件系統版本2 (Journalling Flash FileSystem v2)
     主要用於NOR型閃存,基於MTD驅動層,特點是:可讀寫的、支持數據壓縮的、基於哈希表的日志型文件系統,並提供了崩潰/掉電安全保護,提供“寫平衡”支持等。缺點主要是當文件系統已滿或接近滿時,因為垃圾收集的關系而使jffs2的運行速度大大放慢。
     目前jffs3正在開發中。關於jffs系列文件系統的使用詳細文檔,可參考MTD補丁包中mtd-jffs-HOWTO.txt
     jffsx不適合用於NAND閃存主要是因為NAND閃存的容量一般較大,這樣導致jffs為維護日志節點所占用的內存空間迅速增大,另外,jffsx文件系統在掛載時需要掃描整個FLASH的內容,以找出所有的日志節點,建立文件結構,對於大容量的NAND閃存會耗費大量時間。

     (2) yaffsYet Another Flash File System
     yaffs/yaffs2是專為嵌入式系統使用NAND型閃存而設計的一種日志型文件系統。與jffs2相比,它減少了一些功能(例如不支持數據壓縮),所以速度更快,掛載時間很短,對內存的占用較小。另外,它還是跨平台的文件系統,除了LinuxeCos,還支持WinCE, pSOSThreadX等。
     yaffs/yaffs2自帶NAND芯片的驅動,並且為嵌入式系統提供了直接訪問文件系統的API,用戶可以不使用Linux中的MTDVFS,直接對文件系統操作。當然,yaffs也可與MTD驅動程序配合使用。
     yaffsyaffs2的主要區別在於,前者僅支持小頁(512 Bytes) NAND閃存,后者則可支持大頁(2KB) NAND閃存。同時,yaffs2在內存空間占用、垃圾回收速度、讀/寫速度等方面均有大幅提昇。

     (3) CramfsCompressed ROM File System
     CramfsLinux的創始人 Linus Torvalds參與開發的一種只讀的壓縮文件系統。它也基於MTD驅動程序。
     cramfs文件系統中,每一頁(4KB)被單獨壓縮,可以隨機頁訪問,其壓縮比高達2:1,為嵌入式系統節省大量的Flash存儲空間,使系統可通過更低容量的FLASH存儲相同的文件,從而降低系統成本。
     Cramfs文件系統以壓縮方式存儲,在運行時解壓縮,所以不支持應用程序以XIP方式運行,所有的應用程序要求被拷到RAM里去運行,但這並不代表比Ramfs需求的RAM空間要大一點,因為Cramfs是採用分頁壓縮的方式存放檔案,在讀取檔案時,不會一下子就耗用過多的內存空間,只針對目前實際讀取的部分分配內存,尚沒有讀取的部分不分配內存空間,當我們讀取的檔案不在內存時,Cramfs文件系統自動計算壓縮后的資料所存的位置,再即時解壓縮到RAM中。
     另外,它的速度快,效率高,其只讀的特點有利於保護文件系統免受破坏,提高了系統的可靠性。
     由於以上特性,Cramfs在嵌入式系統中應用廣泛。
     但是它的只讀屬性同時又是它的一大缺陷,使得用戶無法對其內容對進擴充。 
     Cramfs映像通常是放在Flash中,但是也能放在別的文件系統里,使用loopback 設備可以把它安裝別的文件系統里。

     (4) Romfs
     傳統型的Romfs文件系統是一種簡單的、緊湊的、只讀的文件系統,不支持動態擦寫保存,按順序存放數據,因而支持應用程序以XIP(eXecute In Place,片內運行)方式運行,在系統運行時,節省RAM空間。uClinux系統通常採用Romfs文件系統。

     其他文件系統:fat/fat32也可用於實際嵌入式系統的擴展存儲器(例如PDA, Smartphone, 數碼相機等的SD),這主要是為了更好的與最流行的Windows桌面操作系統相兼容。ext2也可以作為嵌入式Linux的文件系統,不過將它用於FLASH閃存會有諸多弊端。

     2. 基於RAM的文件系統
     (1) Ramdisk
     Ramdisk是將一部分固定大小的內存當作分區來使用。它並非一個實際的文件系統,而是一種將實際的文件系統裝入內存的機制,並且可以作為根文件系統。將一些經常被訪問而又不會更改的文件(如只讀的根文件系統)通過Ramdisk放在內存中,可以明顯地提高系統的性能。
     Linux的啟動階段,initrd提供了一套機制,可以將內核映像和根文件系統一起載入內存。

     (2)ramfs/tmpfs
     RamfsLinus Torvalds開發的一種基於內存的文件系統,工作於虛擬文件系統(VFS)層,不能格式化,可以創建多個,在創建時可以指定其最大能使用的內存大小。(實際上,VFS本質上可看成一種內存文件系統,它統一了文件在內核中的表示方式,並對磁盤文件系統進行緩沖。)
     Ramfs/tmpfs文件系統把所有的文件都放在RAM中,所以讀/寫操作發生在RAM中,可以用ramfs/tmpfs來存儲一些臨時性或經常要修改的數據,例如/tmp/var目錄,這樣既避免了對Flash存儲器的讀寫損耗,也提高了數據讀寫速度。
     Ramfs/tmpfs相對於傳統的Ramdisk的不同之處主要在於:不能格式化,文件系統大小可隨所含文件內容大小變化。
     Tmpfs的一個缺點是當系統重新引導時會丟失所有數據。

     3. 網絡文件系統NFS (Network File System)
     NFS是由Sun開發並發展起來的一項在不同機器、不同操作系統之間通過網絡共享文件的技術。在嵌入式Linux系統的開發調試階段,可以利用該技術在主機上建立基於NFS的根文件系統,掛載到嵌入式設備,可以很方便地修改根文件系統的內容。

     以上討論的都是基於存儲設備的文件系統(memory-based file system),它們都可用作Linux的根文件系統。實際上,Linux還支持邏輯的或偽文件系統(logical or pseudo file system),例如procfs(proc文件系統),用於獲取系統信息,以及devfs(設備文件系統)sysfs,用於維護設備文件。
附錄:NOR閃存與NAND閃存比較
NOR FLASH
NAND FLASH
接口時序同SRAM,易使用
地址/數據線復用,數據位較窄
讀取速度較快
讀取速度較慢
擦除速度慢,以64-128KB的塊為單位
擦除速度快,以832KB的塊為單位
寫入速度慢(因為一般要先擦除)
寫入速度快
隨機存取速度較快,支持XIP(eXecute In Place,芯片內執行),適用於代碼存儲。在嵌入式系統中,常用於存放引導程序、根文件系統等。
順序讀取速度較快,隨機存取速度慢,適用於數據存儲(如大容量的多媒體應用)。在嵌入式系統中,常用於存放用戶文件系統等。
單片容量較小,132MB
單片容量較大,8128MB,提高了單元密度
最大擦寫次數10萬次
最大擦寫次數100萬-1000萬次