http://xianzilu.spaces.live.com/blog/cns!4201FDC93932DDAF!290.entry
本文引用地址:
http://www.eepw.com.cn/article/201611/317986.htm關於根文件系統的製作,網絡上有很多文章,大多數都只講到建幾個目錄,然後用Busybox做個Shell,有很多關鍵的東西沒有說。經過很長時間的摸爬滾打,我終於能夠白手起家建立一個根文件系統了。其實我也不懂得原理,只是告訴大家我的作法,其中也不免有錯誤,歡迎大家指正。
首先介紹根文件系統的組成:目錄、Shell、庫、腳本,一個個來。
根文件系統要包含這些必須有的目錄:/dev、/bin、/usr、/sbin、/lib、/etc、/proc、/sys
/dev是devfs(設備文件系統)或者udev的掛在點所在。在使用devfs的內核裡如果沒有/dev,根本見不到Shell啟動的信息,因為內核找不到/dev/console;在使用udev的系統裡,也事先需要在/dev下建立console和null這兩個節點。關於devfs和udev的區別,網上很多文章說。當然如果你的內核已經不支持devfs了(2.6.12以後),可以使用純純的靜態節點。也就是用mknod人工生成。
/bin、/usr/bin、/usr/sbin、/sbin是編譯Busybox這個Shell時候就有的,用於存放二進位可執行文件,就不多解釋了。
/lib用於存放動態連結庫。網上很多文章都說靜態編譯Busybox,可以省去建庫的麻煩過程。這樣做只能讓Busybox啟動,我們自己寫的,或者是編譯的軟體包還是需要動態庫的。除非全部靜態編譯,你可以試試,一個Hello world就要幾百k。關於庫的內容後面仔細說。
/etc是用來存放初始化腳本和其他配置文件的。關於初始化腳本的內容後面仔細說。
/proc是用來掛載存放系統信息虛擬文件系統——「proc文件系統」,「proc文件系統」在內核裡面可以選。如果沒有「proc文件系統」,很多Shell自己的命令就沒有辦法運行,比如ifconfig。「proc文件系統」不像devfs可以自動掛載,它需要使用初始化腳本掛載。另外,udev也需要「proc文件系統」的支持。
/sys用於掛載「sysfs文件系統」,「sysfs文件系統」在內核裡面可以選。目前我認為它就是給udev提供支持的,。「sysfs文件系統」也需要使用初始化腳本掛載。
另外還可以有/tmp、/mnt、/swp、/var這樣的不是嵌入式系統必須的目錄,在說完Shell的製作之後,我再談建立目錄的事情。
Shell很簡單,就是Busybox,上網下載一個來:http://www.busybox.net/downloads/。說Busybox和arm-linux-gcc有兼容性問題,不過我覺得那是比較低版本的時代問題了,我用Busybox 1.8.2和arm-linux-gcc 3.4.1/3.3.2都可以。解壓縮以後找到Makefile裡面的ARCH和CROSS_COMPILE,改成:
ARCH ?= arm
CROSS_COMPILE ?= /usr/local/arm/3.4.1/bin/arm-linux-
當然CROSS_COMPILE由你自己的編譯器位置決定,然後
# make menuconfig
# make
# make install
注意配置的時候把一些uCLinux Only的東西去掉,不然會錯;配置的時候還可以修改安裝位置,默認是在Busybox下的「_install」。
之後就可以在Busybox生成的Shell基礎上建立根文件系統了,我就用命令來說吧,Busybox在/home/lxz/busybox,根文件系統在/home/lxz/rootfs
# mkdir /home/lxz/rootfs
# cd /home/lxz/busybox/_install
# cp -r ./ /home/lxz/rootfs
# cd /home/lxz/rootfs
# mkdir dev
# mkdir etc
# mkdir lib
# mkdir proc
# mkdir sys
# mkdir tmp
如果不用devfs,下面的命令是必須的。必須以root用戶執行(用su命令可以切換為root,切換後用exit命令可以返回普通用戶):
# cd /home/lxz/rootfs/dev
# mknod -m 660 console c 5 1
# mknod -m 660 null c 1 3
如果不使用devfs沒有這兩個靜態節點,console的提示根本就看不到,出現的現象可能是內核提示Free init memory: XXK之後,Warning: Unable to find a initial console之類的,具體的單詞記得不是很準確。我沒有試過使用udev的時候沒有這兩個靜態節點的情況,反正放了也不影響把/dev掛載為tmpfs。
如果使用udev,還需要把udevd、udevstart、udevadmin這三個文件放到/sbin裡面(我使用udev-117,網上介紹較多的udev-100有9個文件要放)。
庫可是一件非常麻煩的事請。我建議初學者拷貝買的開發板裡面帶的文件系統的庫,如果開發板的文件系統是映像,只需要把映像掛載在某個目錄下就可以訪問,假設映像叫做rootfs.cramfs,可以這樣
# mkdir /home/lxz/evb_rootfs
(切換為root用戶)
# mount -o loop rootfs.cramfs /home/lxz/evb_rootfs
(可以切換為普通用戶)
# cd /home/lxz/evb_rootfs/lib
# cp -r ./ /home/lxz/rootfs/lib
一般開發板裡都會帶有很多庫,但是總體積卻比較大。可以刪掉一些不用的庫來減小體積,但是,,我也不知道那些庫具體含有什麼函數,什麼情況刪什麼;也許以後我會把這部分補上。如果覺得庫體積太大,也可以自己編譯glibc或者uclibc,但是這是非常繁瑣的事請——目前我認為庫應該和編譯器arm-linux-gcc一起製作。有個傻瓜式的方案是使用cross-tool,下載地址:http://www.kegel.com/crosstool/。雖然cross-tool是用來製作交叉編譯器的,但是其過程中生成的glibc卻可以作為副產品為我們所用。cross-tool的使用可以看我之前的這篇文章http://xianzilu.spaces.live.com/blog/cns!4201FDC93932DDAF!274.entry。在成功製作了交叉編譯器之後,就可以從cross-tool的目錄裡把glibc取出來,假設cross-tool的路徑是/home/lxz/cross-tool,編譯出的編譯器叫做arm-linux-gnu-gcc,gcc版本3.4.5,glibc版本2.3.6,想要把glibc庫拷貝到/home/lxz/glibc,下面的操作還是用命令來說明。
# cd /home/lxz/cross-tool/build/arm-linux-gnu-gcc/gcc-3.4.5-glibc-2.3.6/build-glibc
# ../glibc-2.3.6/configure --prefix=/home/lxz/glibc
# make install
等候安裝結束
# cd /home/lxz/glibc
# cp -r lib /home/lxz/rootfs
這樣就把glibc的大部隊拷貝好了,但是這樣還缺兩個庫,我們繼續
# cd /home/lxz/cross-tool/build/arm-linux-gnu-gcc/gcc-3.4.5-glibc-2.3.6/build-gcc/gcc
# cp libgcc_s.so* /home/lxz/rootfs/lib
還缺少一個libtermcap庫,這個就稍微有些麻煩。libtermcap-2.0.8-35-armv4l源碼包的下載地址是http://www.netwinder.org/mirror/pub/netwinder/SRPMS/nw/9/libtermcap-2.0.8-35.src.rpm,你也可以在這裡http://www.netwinder.org/allsrpms.html找到其他版本的。假設libtermcap-2.0.8-35.src.rpm下載到了/home/lxz/libtermcap,下面繼續用命令說明。
# cd /home/lxz/libtermcap
# rpm2cpio libtermcap-2.0.8-35.src.rpm | cpio -ivd
# tar xvjf termcap-2.0.8.tar.bz2
接下來要打13個補丁,很汗啊,請一定按照下面的順序來打補丁
# patch -p0 -i termcap-2.0.8-shared.patch
# patch -p0 -i termcap-2.0.8-setuid.patch
# patch -p0 -i termcap-2.0.8-instnoroot.patch
# patch -p0 -i termcap-2.0.8-compat21.patch
# patch -p0 -i termcap-2.0.8-xref.patch
# patch -p0 -i termcap-2.0.8-fix-tc.patch
# patch -p0 -i termcap-2.0.8-ignore-p.patch
# patch -p0 -i termcap-buffer.patch
# patch -p0 -i termcap-2.0.8-bufsize.patch
# patch -p0 -i termcap-2.0.8-colon.patch
# patch -p0 -i libtermcap-aaargh.patch
# patch -p0 -i termcap-2.0.8-glibc22.patch
# patch -p0 -i libtermcap-2.0.8-ia64.patch
然後到/home/lxz/libtermcap/termcap-2.0.8裡,找到Makefile,修改其中的CC和AR,
CC = /usr/local/arm/3.4.1/bin/arm-linux-gcc
AR = /usr/local/arm/3.4.1/bin/arm-linux-ar
當然,你的編譯器在哪裡就改成相應的內容。如果嫌麻煩,可以從本站資料頁面下載我已經打好補丁,修改好Makefile的包,地址http://cosine.oicp.net/project/common/termcap-2.0.8.tar.bz2。需要注意的是,這個包裡CC = arm-linux-gcc、AR = arm-linux-ar,請設置好預設路徑。然後就可以編譯了:
# cd /home/lxz/libtermcap/termcap-2.0.8
# make
# ln -s libtermcap.so.2.0.8 libtermcap.so.2
# cp libtermcap.so* /home/lxz/rootfs/lib
這樣,Shell啟動所需要的基本庫就都備齊了。但是,這些庫裡面還含有調試信息,體積稍大,可以把這些信息去掉(當然不去掉也沒有什麼影響)。
# cd /home/lxz/rootfs/lib
# arm-linux-strip *.so*
至此,庫就製作好了。
有了以上的東西,Shell還是不能正常工作。可能會是內核提示Free init memory: XXK之後就什麼輸出也沒有了,這時候向終端敲入文字,可以顯示;就是沒有終端提示符,不理會輸入的命令。這是因為初始化腳本沒有啟動Shell。下面介紹這些腳本。
首先是/etc/inittab。內核啟動、根文件系統掛載之後所必須的一個文件,其中列舉了Shell和整個系統初始化、關閉所需的命令。如果你想讓Shell出現,那麼只需要加入這麼一行「::askfirst:/bin/ash」。當然如果在編譯Busybox的時候選擇的shell不是「ash」,而是hush、lash、msh之類,那就改成相應的東西。除了啟動Shell,inittab還幹了很多事情,我就用我的inittab來說明了。注意,在編譯Busybox的時候要選上touch、syslogd、klogd等命令。
# Startup the system
null::sysinit:/bin/mount -o remount,rw /
null::sysinit:/bin/mount -t proc proc /proc
null::sysinit:/bin/mount -a
null::sysinit:/bin/hostname -F /etc/hostname
# Now run any rc scripts
::sysinit:/etc/init.d/rcS
# Now invoke shell
::askfirst:/bin/ash
# Logging junk
null::sysinit:/bin/touch /var/log/messages
null::respawn:/sbin/syslogd -n -m 0
null::respawn:/sbin/klogd -n
# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot
# Stuff to do before rebooting
null::shutdown:/usr/bin/killall klogd
null::shutdown:/usr/bin/killall syslogd
null::shutdown:/bin/umount -a -r
null::shutdown:/sbin/swapoff -a
好,把上面這些儲存為inittab,啟動系統。應該出現兩個提示,沒有/etc/fstab和/etc/init.d/rcS。目前我的理解/etc/fstab是用來執行mount -a命令的,裡面是文件系統的掛載表。還是用我的fstab來說明。
#
還有/etc/init.d/rcS,這在PC上是用來執行/etc/init.d下所有初始化腳本的一個腳本,,請原諒我不懂得怎麼寫這種腳本,對於嵌入式系統,根本不需要這麼複雜,直接寫在/etc/init.d/rcS裡面了。還是用我的/etc/init.d/rcS來說明,其中啟動udev的那些指令對於使用靜態設備節點和devfs的系統不適用。
# Start udev
/bin/mount -t tmpfs tmpfs /dev
/sbin/udevd --daemon
/sbin/udevstart
# Configure net interface
/sbin/ifconfig lo 127.0.0.1 up
/sbin/route add -net 127.0.0.0 netmask 255.0.0.0 lo
/sbin/ifconfig eth0 192.168.2.25 netmask 255.255.255.0
/sbin/route add default gw 192.168.2.1
如果用的是udev,還必須有udev的配置腳本。這個寫起來有些麻煩,我們可以直接用udev自己帶的那些腳本,位置在udev目錄下的etc/udev/udev.conf和etc/udev/rules.d裡面的文件。把這些放到根文件系統中去,etc/udev/udev.conf變為根文件系統的/etc/udev/udev.conf,etc/udev/rules.d裡面的文件變為/etc/udev/rules.d裡面的文件。
至此,一個可用的最簡單文件系統就完成了。之後,可以使用mkcramfs、mkyaffs之類的工具製作文件映像,這我就不多說了。需要注意的是,cramfs文件系統是只讀的,就算用的是initrd加載的cramfs也一樣是只讀的,這對根文件系統的/var目錄有一定的影響。除了本文中說的辦法,還可以用buildroot這個工具來建,但是現在我還在嘗試中,以後再說。如果你嫌上面的這一切都很麻煩,可以從本站資料站下載我給Linux 2.6.19內核做的最簡文件系統(1.5M),地址是http://cosine.oicp.net/project/at91rm9200/root.img,cramfs格式映像。