2012年2月26日 星期日

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

沒有留言: