記一次構建基於arm的linux根文件系統的曲折歷程

1. 前言

        要研究操作系統,移植linux到嵌入式設備上運行是很好的實踐方式,可以大大的加深對操作知識的理解,計算機是一門理論與實踐緊密結合的學科,光有理論是不行的,還得多實踐。但是,這要求比較多的相關知識,以及對linux各方面都要比較熟悉,對於新手或者從別的開發領域轉過來的人來說,可能會遇到比較多的問題,有時候好像還無從下手,也無法從身邊朋友得到幫助,這時候就考驗你的信息檢索能力,耐心和恆心,以及分析能力了。我把這次移植根文件系統的經歷寫出來,希望共同愛好共同學習的朋友少走彎路。另外順便說一下,別寄希望從開發板廠家得到多少幫助,他們只要自己的硬件能跑自己移植的系統沒有問題,就對你提的相關問題裝死,或者不回覆,或者讓你發郵件,總之就是搞你幾次沒耐心自動退卻。其實這些廠家的技術支持能力也非常一般,他們的資料很多還是七八前的,現在都2020年了。

2.  構建過程

2.1 編譯環境:

  • 開發板的芯片:Exynos4412  arm Cortex A9
  • 編譯主機:CentOS7,CentOS8,Redhat7 (x86_64)
  • 交叉編譯環境:

在CentOS7,Redhat7 (x86_64) 下:

gcc version 4.9.1 20140710 (prerelease) (Linaro GCC 4.9-2014.07)

在CentOS8 下:

gcc version 7.5.0 (Linaro GCC 7.5-2019.12)

其中:RedHat編譯內核和交叉工具最爲順利,原因大家懂的。

  • 內核版本:linux-5.6.8 (linux-3.0以上都行,不過更高的使用設備樹)
  • 文件系統源碼:busybox-1.31.1
  • 生成ext4文件工具:make_ext4fs

2.2 構建過程

正常的編譯busybox的步驟:

(1) 打開Makefile文件,找到ARCH,修改爲ARCH=arm

(2)  make menuconfig (這裏選擇動態編譯)

進入Settings-> --- Build Options

  • 去掉靜態編譯選項,這時候就看到動態編譯的行選項了:

[ ] Build static binary (no shared libs)    前面的*去掉

[*] Build shared libbusybox   選上*

以下這3項的*去掉

[] Pull in all external references into libbusybox                                                                                

[]Produce a binary for each applet, linked against libbusybox                                                                                  

[] Produce additional busybox binary linked against libbusybox

  • 編譯前綴,即交叉編譯工具的前綴寫上自己的交叉編譯前綴:

Cross compiler prefix 這裏寫上自己的交叉編譯前綴,我的是:

arm-linux-gnueabihf-

  • --- Installation Options ("make install" behavior) 

這裏可以使用默認,也可以配置,這裏使用默認的./_install

(3) 編譯,安裝,最後_install目錄下的內容就是我們需要的。

(4) 進去_install,按照常夫的配置etc,創建其它目錄,這裏不詳述,可以參看下面文章:

https://blog.csdn.net/zxy131072/article/details/86081954

(5) 使用make_ext4fs工具製作生成ext4文件系統

make_ext4fs -s -l 314572800 -L linux system.img _install

(6) 燒寫文件系統

板子啓動,然後fastboot 0進入boot模式,主機命令行輸入命令

Fastboot flash system system.img

燒寫成功,fastboot reboot 重新啓動

2.3 一切看起來都挺順理成章,然後各種問題就來了,進入折騰過程,以下問題不分先後。

2.3.1 ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)

這個錯誤讓人很難捉摸,搜也搜不到解決方法,網上也是衆說紛雲,但是都不能解決我的問題,有點無從下手的感覺。再次回到錯誤本身上,顯示是掛不上根文件系統,又是未知塊,那麼是掛了沒掛上,還是根本沒有去掛呢?看樣子又像是掛了沒有掛上,爲什麼又沒有掛上?串口日誌也看不出個所以然,那就還是回到啓動參數上來。存細對比了廠家自己的系統,啓動參數是這樣的:

root=/dev/mmcblk1p2 rw console=ttySAC2,115200 init=/linuxrc rootwait

而我使用uboot自帶的啓動參數,啓動參數是這樣的:

root=/dev/mmcblk1p2 rw console=ttySAC2,115200 init=/linuxrc earlyprintk

區別就在這個rootwait,諮詢了廠家,廠家說啓動參數不用在內核配置,使用默認的就行,這不是坑死人嗎?一般誰會想到是這個細節?存細查了這個參數,其說明爲:

如果加上這個參數的話,kernel就會調用rootwait_setup,等待emmc驅動加載完成,如果沒有加載完成的話,直接加載文件系統,很不容易成功。

好了,加上rootwait後,錯誤就不一樣了,說明這個參數的確是在生效。

2.3.2 Waiting for root device /dev/mmcblk1p2...

在2.3.1 這個問題解決後,又冒出這個錯誤來了,就是一直卡在這裏不動了,應該就是等待內核初始化emmc驅動。對比了下廠家自帶的啓動信息,有類似如下一些信息:

[1.596206] mmc0: SDHCI controller on samsung-hsmmc [12530000.sdhci] using ADMA

[ 1.602787] Synopsys Designware Multimedia Card Interface Driver

[ 1.608785] dwmmc_exynos 12550000.mmc: IDMAC supports 32-bit address mode.

[1.614932] dwmmc_exynos 12550000.mmc: Using internal DMA controller.

[ 1.621328] dwmmc_exynos 12550000.mmc: Version ID is 240a

[1.626739] dwmmc_exynos 12550000.mmc: DW MMC controller at irq 113,32 bit host data width,128 deep fifo

[1.636236] mmc_host mmc1: card is polling.

[ 1.652610] mmc_host mmc1: Bus speed (slot 0) = 50000000Hz (slot req 400000Hz, actual 396825HZ div = 63)

而我自己編譯的內核啓動串口打印卻看不到這些信息,明明在內核中選中了emmc區動:<M> MMC/SD/SDIO card support

編譯後還是不行,但是廠家提供的內核卻可以,反覆對比測度之後,發現了一個容易忽略掉的細節:內核默認的選擇中<M>,表示只編譯模塊,並不編譯進內核,要改成選*才編譯進內核,從配置項的最外層菜單就要選擇*,否則子菜單選不了*,就這麼一個小細節,足可以考驗你的耐心。重新編譯內核之後,這個錯誤消失了。

2.3.3 EXT2-fs (mmcblk1p2): error: couldn't mount because of unsupported optional features (44)

看樣子是文件類型識別不了,我的文件系統是ext4,而這裏用ext2去匹配。網上有人說是按順序加載,有的說是先按ext2加載,再按ext3加載 ,最後纔是ext4。但是我的都不是這些原因,既然認爲是ext2類型,那我在啓動參數裏面明確指定文件系統類型:rootfstype=ext4重啓動啓動,然後,新的錯誤又出現了(2.3.4)。

2.3.4 VFS: Cannot open root device "mmcblk1p2" or unknown-block(179,2): error -19

Please append a correct "root=" boot option; here are the available partitions:

[ 2.314855] b300 7634944 mmcblk1

[ 2.314858] driver: mmcblk

[ 2.321623] b301 5172205 mmcblk1p1 00000000-01

[ 2.321625]

[ 2.328392] b302 1052353 mmcblk1p2 00000000-02

[ 2.328393]

[ 2.335160] b303 1052353 mmcblk1p3 00000000-03

[ 2.335162]

[ 2.341933] b304 313467 mmcblk1p4 00000000-04

 

查看錯誤碼的定義,在這兒:

在Linux源碼目錄/include/uapi/asm-generic的errno-base.h

19表示No such device。

結合前面的類型修改,還是文件類型這兒出了問題,19表示沒有這樣的設備,應該是內核沒有識別到文件系統。經過反反覆覆的對比測試,問題又是出在內核編譯選項這兒:

File systems-><M> The Extended 4 (ext4) filesystem  

這裏問題和上面一樣,忽略了一個關鍵細節,默認是M,應該選擇*才能編譯進內核。就這一個小細節,能折磨人到崩潰。選擇*後重新編譯後,錯誤消失。

2.3.5 Run /linuxrc as init process

[ 2.360489] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000004

[ 2.366672] CPU: 2 PID: 1 Comm: linuxrc Not tainted 4.20.16 #12

這個在網上查到原因了,是內核中浮點配置沒有選擇*,默認是M,編譯進內核中:

Floating point emulation ->

[*] VFP-format floating point maths

[*]   Advanced SIMD (NEON) Extension support

[*]     Support for NEON in kernel mode

詳細說明在這篇文章中,分析的相當好:

https://blog.csdn.net/chuanzhilong/article/details/52901973

2.3.6 end Kernel panic - not syncing: Requested init /linuxrc failed (error -13)

13對應的錯誤是:Permission denied,也就是說沒有權限,這就有點無從下手的感覺,首先確定是文件系統的權限,可是全都改成了777的權限,內核裏面沒有關於這個文件權限設置的地方呀。但是對比了廠家給的文件系統和製作工具make_ext4fs,用這個工具製作的文件可以,換成我的這個工具卻不可能,最後懷疑問題出在這個make_ext4fs工具上面,因爲參數都是一樣的。也就是說,廠家在編譯這個工具的時候,改過相關的代碼,用file查看了一下這個make_ext4fs,居然是32位的,也太懶了,居然不提供64位的,所以這個工具在centos7上還可以運行,cetnos8和readhat7上都跑不起來,這坑比較多呀。

file make_ext4fs

make_ext4fs: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, stripped

找到這麼一篇文章:

https://blog.csdn.net/adaptiver/article/details/8595591

這裏, 作者說了權限的問題,說了兩種方法,一種是修改代碼重新編譯,一種是去-a參數,我先試第一種,這種雖然麻煩,但是最爲有效。在網上下載了一份make_ext4fs的源代碼,編譯生成。再次生成文件系統,還是一樣的錯誤,正當準備修改源代碼時,想到用去掉-a的方式試一下。即

make_ext4fs -s -l 314572800 -a root -L linux system.img _install

去掉-a

make_ext4fs -s -l 314572800 -L linux system.img _install

這樣系統使用文件的默認權限,這樣居然就可以了,這個坑埋得比較深。

2.3.7 /linuxrc: error while loading shared libraries: libm.so.6: cannot open shared object file: No such file or directory

        這個錯誤真的是搞得人心力交瘁,因爲常見的方法都沒有效,解決不了問題,沒有直接或者相似的方法可以解決。首先這個文件肯定是在/lib這個目錄下,多次確認過,把編譯庫全部都複製到lib下,也修改了權限爲777,不管怎麼試,問題依舊,下面開始各種嘗試。

(1) 按照傳統方法,在etc下建立ld.so.conf文件,寫入lib庫路徑,/lib,再到最外層執行ldconfig –r _install,讓人意想不到的是,又跳出這個錯誤出來了:

dconfig: /lib/ld-2.30.so is for unknown machine 40.

ldconfig: /lib/ld-linux-armhf.so.3 is for unknown machine 40.

ldconfig: /lib/libanl-2.30.so is for unknown machine 40.

後面一大推的unknown machine 40錯誤,省略掉。查了一下,說是高版本的linux不支持交叉編譯的文件執行ldconfig命令,這是一個改出來的bug,然後將文件系統複製到redhat5上去執行,果然不在報這個錯,坑真是多,我不明白高版本的linux怎麼還改出bug來了?從分析來看,確實是一個bug,至於怎麼修復不去探討,比較麻煩。這個錯消滅之後,問題依舊,也就是說通過配置讓系統找到默認的庫這條路走不通,所有這些方法都沒有卵用。

(2) 現在可以確定的是系統找不到依懶的庫,怎麼才能讓編譯的ld-linux.so這個程序能找到相應的庫,我記得在舊版本的內核中,不用配置它默認就能找到,也許新版本的gcc作了改動。現在又搜到下面這篇文章,試試效果如何:

https://blog.csdn.net/remme123/article/details/9250765

注意這樣一句話:

由於在init進程啓動前,需要加載相應的動態庫,動態庫的加載和初始化任務都是由ld-linux.so.3來完成,而此時由於init進程還未啓動,所以添加的環境變量全部無效,只有默認環境變量有效,即正常情況下默認庫搜索路徑應該爲 /lib:/usr/lib

經過第(1)步的嘗試,證實確實是這樣,什麼環境變量什麼配置完全無效。這裏要糾正一下的是,默認的環境變量也無效。也就是默認的統統無效。好吧,就認爲問題是這樣,看看問題怎麼解決:

解決辦法:重新編譯glibc。找到elf/Makefile文件,定位到"gen_trusted_dirs.awk"上一行,修改爲"echo '/lib:/usr/lib'"

問題追蹤:ld-linux.so.3->_dl_map_object(elf/dl-load.c)->SYSTEM_DIRS(elf/trusted_dirs.h)->elf/Makefile

按照以上的方法,重新編譯glibc庫,查看了生成的trusted_dirs.h文件,其內容如下:

      1 #define SYSTEM_DIRS \

      2   "/lib:/usr/lib/"

      3

      4 #define SYSTEM_DIRS_LEN \

      5   14

      6

      7 #define SYSTEM_DIRS_MAX_LEN     14

      8 #define DL_DST_LIB "lib"

看來路徑是對的,沒問題,問題似乎往正確的方向發展,安裝後重新編譯busybox-1.31.1,然後再次燒錄文件系統。讓人失望的是,串口打印出來的信息顯示問題依舊。現在進入第3步嘗試。

(3) 既然這些方式都不行,那就把glibc的源代碼打開來瞧瞧,裏面是怎麼加載文件庫的。用Visual Studio Code加載glibc源文件,找到dl-load.c文件看看,找到函數_dl_map_object這個函數,最明顯示這兒一句註釋和代碼:

      /* First try the DT_RPATH of the dependent object that caused NAME

         to be loaded.  Then that object's dependent, and on up.  */

      for (l = loader; l; l = l->l_loader)

        if (cache_rpath (l, &l->l_rpath_dirs, DT_RPATH, "RPATH"))

          {

        fd = open_path (name, namelen, mode & __RTLD_SECURE,

                &l->l_rpath_dirs,

                &realname, &fb, loader, LA_SER_RUNPATH,

                &found_other_class);

        if (fd != -1)

          break;

        did_main_map |= l == main_map;

          }

有rpath這樣的字眼,讓人聯想到了常常被人忽略的rpath參數,查了一下,有這麼一句說明:

-rpath: “運行的時候,去找的目錄。運行的時候,要找 .so 文件,會從這個選項裏指定的地方去找。對於交叉編譯,只有配合 --sysroot 選項才能起作用。加上這個選項後,庫的路徑會寫入生成的可執行文件中,執行時直接去這個路徑下尋找。

現在對busybox配置作以下更改:

Settings->

()path to sysroot 設置成/

() Additional LDFLAGS 設置爲:-Wl,-rpath=/lib

編譯,sysroot這兒報錯了,那就去掉sysroot參數,編譯,這下能通過了。

再次製作根文件系統,燒錄,重啓,果然是這兒的問題,這次終於可以進入系統,輸入linux命令了。

事實是,只設置rpath參數,不用設置sysroot也是可以的。

3 後記

        這次解決問題的經歷是曲折的,耗費了很多的休息時間,查閱了大量的中外貼子,問題原因都是形形色色,可能由於國內從業人員少的原因,分享也比較少,更多的要靠自己冷靜的分析、思考、實踐,反反覆覆嘗試。原有致力於技術研究,願意深耕細作的同行和愛好者們,大家共勉吧,學習是辛苦的,沒有捷徑可走,彎道超不了車。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章