1. 程式人生 > >tiny linux: 核心精簡的根檔案系統製作

tiny linux: 核心精簡的根檔案系統製作

tiny_linux要求實現以下兩點:

  1. 精簡linux核心映象,要求在支援TCP/IP資料傳輸的情況下,核心映象和正常執行所需記憶體能夠做到儘可能的小。

  2. 採用busybox製作根檔案系統,利用kernel mode linux補丁,使得busybox執行在核心態。

歡迎訪問tiny_linux, 這裡有編譯好的核心映象,根檔案系統映象以及相關指令碼,供大家使用。

涉及知識點

  • qemu使用
  • linux核心配置,編譯
  • 根檔案系統製作

在這裡記錄了我完成該專案的過程,希望對大家有用。

ps:

1. 關於根檔案的製作部分,對當初小白的我,非常感謝楊海宇同學的付出,讓自己能夠快速入門。

2. 核心原始碼Documentation

目錄下有關於linux權威的文件,裡面也有關於initrdinit的介紹,可以先讀讀這兩個文件,個人感覺很不錯。

成果

平臺 :  X86_64
linux:   4.0.4
優化前: bzImage=6.5M 記憶體=*M
優化後: bzImage=726K 記憶體=21.6M

目錄

Section 1:linux核心映象檔案



Linux核心本身的啟動又分為壓縮核心和非壓縮兩種。從Linux核心程式的結構上,具有如下的特點:
壓縮核心 = 解壓縮程式 + 壓縮後的核心映像
當壓縮核心執行後,將執行一段解壓縮程式,得到真正的核心映像,然後跳轉到核心映像執行。此時,Linux進入非壓縮核心的入口,在非壓縮的核心入口中,系統完成各種初始化任務後,跳轉到C語言的入口處執行。

這一步主要是通過編譯linux核心,獲取linux的壓縮核心映象bzImage

  1. 下載linux核心程式碼

    mkdir tiny_linux
    cd tiny_linux
    curl https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.0.4.tar.xz | tar xJf -
    
  2. 編譯linux核心

在核心程式碼根目錄的Makefile當中,可以發現有如下文字描述:

背景:Makefile Line 98

# kbuild supports saving output files in a separate directory.

# To locate output files in a separate directory two syntaxes are supported.

# In both cases the working directory must be the root of the kernel src.

# 1) O=

# Use “make O=dir/to/store/output/files/”

利用make O=命令,可以使輸出檔案與原始碼檔案分離,這樣使得我們能夠建立不同的輸出檔案,每個獨立的輸出檔案都可以有自己的專屬配置,這個在後期精簡config的過程當中,特別有用。

    cd tiny_linux
    mkdir obj
    cd linux-4.0.4
    make O=../obj/linux_0 x86_64_defconfig

背景:核心預設配置檔案
核心為很多平臺附帶了預設配置檔案,儲存在arch//configs目錄下,其中對應具體的架構,如x86、arm或者mips等。比如,對於x86架構,核心分別提供了32位和64位的配置檔案,即i386_defconfig和x86_64_defconfig;對於arm架構,核心提供瞭如NVIDA的Tegra平臺的預設配置tegra_defconfig,Samsung的S5PV210平臺的預設配置s5pv210_defconfig等。

linux_0目錄下,執行make menuconfig,就能夠對核心進行配置,執行make -jN,可以指定編譯過程中使用的執行緒數,N最好為機器並行支援的最大執行緒數

cd tiny_linux/obj/linux_0
make -j32 #依自己機器設定

如果編譯成功,你將看見如下資訊:

Setup is 13932 bytes (padded to 14336 bytes).
System is 6588 kB
CRC 6a1e6d04
Kernel: arch/x86/boot/bzImage is ready  (#1)

而這個bzImage正是我們需要的,此時bzImage大小6.5M

ls -lh arch/x86/boot/bzImage 
-rw-r--r-- 1 root root 6.5M Jun 14 10:28 arch/x86/boot/bzImage

在section 3我將詳細講述,如何精簡配置,精簡後的配置,可以使bzImage的大小降低到931K。

Section 2:根檔案系統映象檔案

背景: 根檔案系統
根檔案系統首先是一種檔案系統,但是相對於普通的檔案系統,它的特殊之處在於,它是核心啟動時所mount的第一個檔案系統,核心程式碼映像檔案儲存在根檔案系統中,而系統引導啟動程式會在根檔案系統掛載之後從中把一些基本的初始化指令碼和服務等載入到記憶體中去執行。

2.1 構建busybox

BusyBox是一個集成了一百多個最常用linux命令和工具的軟體。BusyBox 包含了一些簡單的工具,例如ls、cat和echo等等,還包含了一些更大、更復雜的工具,例grep、find、mount以及telnet。有些人將 BusyBox 稱為 Linux 工具裡的瑞士軍刀。簡單的說BusyBox就好像是個大工具箱,它整合壓縮了 Linux的許多工具和命令,也包含了 Android 系統的自帶的shell。

下載busybox原始碼,進行配置編譯

    cd tiny_linux
    curl http://busybox.net/downloads/busybox-1.23.2.tar.bz2 | tar xjf -
    mkdir obj/busybox
    cd busybox-1.23.2
    make O=../obj/busybox defconfig
    cd ../obj/busybox
    make menuconfig

修改配置,使用靜態編譯,如果不使用靜態編譯,程式執行期間需要進行動態載入,則需在根檔案系統中提供其所需的共享庫。

Location:                                                           
-> Busybox Settings                                        
   -> Build Options     
      [*] Build BusyBox as a static binary (no shared libs)         

使用make進行編譯,對於一些機器,可能會報如下的錯誤:

networking/lib.a(inetd.o): In function `unregister_rpc':
inetd.c:(.text.unregister_rpc+0x17): undefined reference to `pmap_unset'
networking/lib.a(inetd.o): In function `register_rpc':
inetd.c:(.text.register_rpc+0x56): undefined reference to `pmap_unset'
inetd.c:(.text.register_rpc+0x72): undefined reference to `pmap_set'
networking/lib.a(inetd.o): In function `prepare_socket_fd':
inetd.c:(.text.prepare_socket_fd+0x7f): undefined reference to `bindresvport'
collect2: ld returned 1 exit status
make[2]: *** [busybox_unstripped] Error 1
make[1]: *** [_all] Error 2
make: *** [all] Error 2

觀察上面的錯誤,可以發現問題出在inetd.c中有未定義的引用,在網上搜索一下答案,關閉配置當中的inet選項即可忽略該問題

    Location:                 
    -> Networking Utilities 
       [ ] inetd  

這時再執行make,就能生成busybox,執行make install生成_install目錄

2.2 根檔案系統製作



init程序是系統中所有程序的父程序,init程序繁衍出完成通常操作所需的子程序,這些操作包括:設定機器名、檢查和安裝磁碟及檔案系統、啟動系統日誌、配置網路介面並啟動網路和郵件服務,啟動列印服務等。Solaris中init程序的主要任務是按照inittab檔案所提供的資訊建立程序,由於進行系統初始化的那些程序都由init建立,所以init程序也稱為系統初始化程序。

拷貝busybox/_install中的內容,到ramdisk目錄

cd tiny_linux
mkdir obj/ramdisk
cd ramdisk
cp -r ../busybox/_install/* ./

此時ls -l,我們可以看見如下的內容:

total 12
drwxr-xr-x 2 root root 4096 Jun 14 12:32 bin
lrwxrwxrwx 1 root root   11 Jun 14 12:32 linuxrc -> bin/busybox
drwxr-xr-x 2 root root 4096 Jun 14 12:32 sbin
drwxr-xr-x 4 root root 4096 Jun 14 12:32 usr

是否發現它有點像linux系統的根目錄,事實上他們就是一樣的,因此我們需要補充上未建立的檔案目錄

mkdir -pv {etc,proc,sys,dev}



init程序是系統中所有程序的父程序,init程序繁衍出完成通常操作所需的子程序,這些操作包括:設定機器名、檢查和安裝磁碟及檔案系統、啟動系統日誌、配置網路介面並啟動網路和郵件服務,啟動列印服務等。由於進行系統初始化的那些程序都由init建立,所以init程序也稱為系統初始化程序。

建立inittab

cd ramdisk/etc
vim inittab

在inittab進行如下配置:

::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

建立rcS

cd ramdisk/etc
mkdir init.d
vim rcs

在rcS進行如下配置:

#!/bin/sh
mount proc
mount -o remount,rw /
mount -a

clear                               
echo "Booting Tiny Linux"

為rcS分配執行許可權

chmod +x rcS

建立fstab

cd ramdisk/etc
vim fstab

在fstab進行如下配置:

# /etc/fstab
proc            /proc        proc    defaults          0       0
sysfs           /sys         sysfs   defaults          0       0
devtmpfs        /dev         devtmpfs  defaults        0       0

生成ramdisk的映象檔案initramfs.cpio.gz

cd ramdisk
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../obj/initramfs.cpio.gz

到目前為止,我們就可以使用qemu進行模擬運行了

cd tiny_linux/obj
qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz

其中使用-append "console=ttyS0" -nographic能夠讓其在在文字模式下進行執行,ctrl+a, x退出qemu。

在虛擬的linux當中,你可以執行大部分常見的命令,比如:ls, top。當然也能建立檔案等,不過在你退出qemu的時候,這些改變並不會寫回到根檔案系統映象當中,原因很簡單,執行的時候,只是將該映象載入到記憶體當中執行,而這些所謂的改變,只是針對記憶體中已載入的根檔案系統進行的修改操作,而這些並不會影響保留在磁碟上的核心映象。

2.3 網路配置及測試

在etc/rcS當中,增加如下配置:

/sbin/ifconfig lo 127.0.0.1 up
/sbin/route add 127.0.0.1 lo &
ifconfig eth0 up
ip addr add 10.0.2.15/24 dev eth0
ip route add default via 10.0.2.2

通過該配置,就給模擬的linux設定IP地址等,使得tiny_linux與Host OS能夠進行通訊了。

2.3.1 百度網站測試

Qemu Networking官方說明文件

Note - if you are using the (default) SLiRP user networking, then ping (ICMP) will not work, though TCP and UDP will. Don’t try to use ping to test your QEMU network configuration!

從該文件得知,如果使用qemu預設的網路,將無法使用ping進行網路測試,因此我們採用wget進行網路測試,以百度網頁測試為例:

/ # wget www.baidu.com
wget: bad address 'www.baidu.com'

發現無法解析域名,估計是DNS沒有設定,為了簡便,直接網上搜索百度的IP地址進行測試:202.108.22.5

/ # wget 202.108.22.5
Connecting to 202.108.22.5 (202.108.22.5:80)
index.html      100% |*******************************| 92768   0:00:00 ETA

下載網頁成功,為確保下載的是百度搜索的首頁,可以使用vi index.html,檢視該html檔案,你將發現有很多關鍵字baidu

2.3.2 本機搭建網站測試

由於採用這種方式,並不太直觀,我們可以在Host OS中搭建一個http伺服器,然後使用相同方式連線

#建立http服務
python2 -m SimpleHTTPServer

#通過8000埠,下載host主機上的檔案
wget host_ip:8000/file

到這裡,我們已經成功製作了根檔案系統核心映象,並且能夠保證網路通訊。

Section 3:精簡bzImage

在前面兩個板塊當中,我們分別製作了bzImage以及根檔案系統映象,這個版塊,主要講述我對bzImage精簡的過程。在楊海宇同學的文件中,他提供了x86的配置選項,而在使用的過程當中,發現無法啟動,因此自己一步步對x86_64_defconfig進行了裁剪。

3.1 編譯選項優化

gcc在編譯的過程當中,我們知道有O1,O2,O3等優化,能夠一定程度的縮減程式最終的大小,因此,第一個想法是,從編譯選項上進行優化,那麼如何下手呢?最簡單暴力的方式,在Makefile當中搜尋’-O’選項,檢視linux核心編譯使用的如何優化的等級,然後你會發現:

ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS   += -Os $(call cc-disable-warning,maybe-uninitialized,)
else
KBUILD_CFLAGS   += -O2
endif

原來config當中已經提供了該編譯優化選項,在make menuconfig當中搜尋一下:optimize

Symbol: CC_OPTIMIZE_FOR_SIZE [=n]                            
Type  : boolean                                              
Prompt: Optimize for size                                    
Location:                                                    
    (1) -> General setup       
Defined at init/Kconfig:1290     

發現預設情況下,該優化選項是關閉的,果斷開啟這個選項。

3.2 替換壓縮方式

核心映象預設採用gzip的方式進行壓縮, 而lzma,XZ是壓縮比更加優越的壓縮方式,在這裡,我們對於解壓速度並不特別看重,因此可以選擇壓縮比最好的XZ,作為核心映象壓縮的方式。

To reduce the kernel image size itself is really important, but with
compression, smaller kernel image size can be gained and the effect is very
obvious, the current available kernel compression support include gzip,
bzip2, lzma, lzo and lately XZ embedded becomes available. To get smallest
size, lzma or XZ embedded may be the best choice, but to consider
decompression speed, lzo may be the choice
, the other two are more or less
in-between.
                     一一 Work on Tiny Linux Kernel

3.3 步步精簡config

看著config當中各種各樣的選項,想必你與我一樣,沒有想到linux核心當中竟然有這麼多可以配置的選項,而且對這些配置,都沒有太多的概念,當時我採用的方式:一個大塊大塊的關閉,簡單來說,在頂層配置當中,有諸如以下的選項:

 Processor type and features  --->
 Power management and ACPI options  --->
 Bus options (PCI etc.)  --->
 Executable file formats / Emulations  --->

稍微理解下表層的含義,關於處理器的配置,電源管理配置,匯流排配置等,對於感覺能夠一定關閉的,通通進行關閉。然後進行編譯,測試是否能夠啟動,能夠正確進行網路通訊。

最終你會發現,以下是你需要留下的選項,而其餘模組的選項,都能通通關閉。

  [*] 64-bit kernel  
  General setup  --->      
  Bus options (PCI etc.)  ---> 
  [*] Networking support  --->
  Device Drivers  --->   

接下來就是進一步精簡該選項當中的配置,圍繞著網路驅動,TCP/IP通訊支援,能夠在網路設定、驅動設定當中去掉絕大部分的選項。

  1. 在開啟某一選項的過程中,會出現一些自動開啟的選項,對於這些選項,也可以選擇性進行關閉。

  2. 對於allnoconfig預設開啟配置選項,也可以選擇性進行關閉。

3.4 最小配置

General setup

[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
    [*]   Support initial ramdisks compressed using gzip
[*] Optimize for size 
[*] Configure standard kernel features (expert users)  --->   

Bus options

[*] PCI support  

Networking support

[*] TCP/IP networking    

Device Drivers

[*] Network device support  --->  
    [*] Ethernet driver support  --->   
         [*] Intel devices
         [*] Intel(R) PRO/1000 Gigabit Ethernet support

Character devices  ---> 
    [*] Enable TTY 
        Serial drivers  ---> 
        [*] 8250/16550 and compatible serial support
        [ ]   Support 8250_core.* kernel options (DEPRECATED)
        [*]   Console on 8250/16550 and compatible serial port
        [*]   8250/16550 PCI device support
        (4)   Maximum number of 8250/16550 serial ports       
        (4)   Number of 8250/16550 serial ports to register at runtime 

File systems(可選)

如果想支援topps等相關命令,需要開啟該選項,開啟後,bzImage大小將由886K變為930K

File systems  --->   
    Pseudo filesystems  --->   
        [*] /proc file system support   

具體可以參考tiny_linx/configs/config_726K,通過該配置,編譯出來的bzImage大小隻有726K,使用qemu啟動的時候,注意需要採用-append "console=ttyS0" -nographic方式,才能正常載入。

Section 4:Kernel Mode Linux

Kernel Mode Linux 是一個讓使用者程式執行在核心模式下的技術。運行於核心模式下的應用可直接訪問核心地址空間,與核心模組不同的是,使用者程式跟一個正常程序一樣,可像一般應用一樣執行排程和paging。雖然看似危險,為確保核心的安全性,可通過靜態型別檢查,軟體故障隔離等方法來防範。

4.1 KML Patch

Kernel Mode Linux(KML)官網提供了KML的patch,通過給核心原始碼打上KML補丁,開啟Kernel Mode Linux選項,重新編譯核心,即可實現將使用者程序在核心態進行執行。

下載KML Patch,更新核心原始碼,如果對patch的使用不熟悉,可以參考這篇文章補丁(patch)的製作與應用

cd tiny_linux
curl http://web.yl.is.s.u-tokyo.ac.jp/~tosh/kml/kml/for4.x/kml_4.0_001.diff.gz | gunzip > kml.patch
cd linux-4.0.4
patch -p1 < ../kml.patch

會出現如下錯誤提示:

patching file CREDITS
patching file Documentation/00-INDEX
patching file Documentation/kml.txt
patching file MAINTAINERS
patching file Makefile
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED -- saving rejects to file Makefile.rej

檢視Makefile.rej

--- Makefile
+++ Makefile
@@ -1,7 +1,7 @@
 VERSION = 4
 PATCHLEVEL = 0
 SUBLEVEL = 0
-EXTRAVERSION =
+EXTRAVERSION = -kml
 NAME = Hurr durr I'ma sheep

 # *DOCUMENTATION*

在比對核心原始碼當中的Makefile檔案,發現出現了版本號不匹配的問題,一種方式是修改Makefile,將SUBLEVEl修改為0,另一種方式修改kml.patch,將SUBLEVEL修改為4。

patch -R -p1 < ../kml.patch   #取消之前打過的補丁
patch -p1    < ../kml.patch

執行make menuconfig, 勾選上對應的選項

Kernel Mode Linux  --->
[*] Kernel Mode Linux           
[*]   Check for chroot (NEW)      
    *** Safety check have not been implemented *** 

最後重新編譯核心即可。

4.2 核心態執行busybox

根據KML的使用說明,只需建立/trusted目錄,將需要執行的程式放在該目錄下即可,因此我們需要對之前製作的ramdisk進行修改:

cd ramdisk
mkdir trusted
mv bin/busybox /trusted
ln -s /trusted/busybox /bin/busybox

因為bin目錄下的其他命令均是符號連結在bin/busybox上,因此通過在bin目錄下建立連結到/trusted/busybox的符號連結busybox,以最小的修改代價,完成了ramdisk的修改。

4.3 核心態執行測試

  • time測試
    對於核心態執行的驗證,這裡花了不少時間,嘗試過使用time ./程式的方式,通過比較核心態時間和使用者態時間的變化,判斷程式是否執行在核心態,不過在測試過程中,發現均執行在使用者態,估計是方法哪裡需要改進,解決了之後,來更新。

  • 擷取暫存器值
    採用彙編的方式,獲取程序執行的許可權。Orz,又漲見識了。

    #include <stdio.h>
    #include <stdint.h>
    int main() {
        uint32_t cs;
        asm volatile("mov %%cs, %0" : "=r" (cs));
        printf("Privilege level: %x\n", cs & 0x3);
        return 0;
    }

參考資料

  • 楊海宇同學製作文件 詳細講述瞭如何進行根檔案系統製作,以及在這個過程中可能出現的問題。

  • Work_on_Tiny_Linux_Kernel 收集了很多關於Tiny_Linux_Kernel的資料,可以讀讀,看看大家做了哪些工作。

  • initrd文件init文件 是在核心原始碼Documentation目錄下的說明文件,非常有用,initrd.txt文件中還講述了使用loopback device方式製作根檔案系統。

  • QEMU命令引數說明 如果不熟悉-kernel, -initrd等相關qemu命令,可以參考該文章進行學習

  • 補丁和patch 如果不熟悉如何製作patch,打補丁,可以參考這篇文章