想讓Linux內核代碼跑起來,得先搭建編譯和運行代碼的環境。
Linux代碼盡量在Linux環境下編譯,以減少不必要的麻煩,我選擇的是ubuntu-18.04:
1、linux源碼下載
我們依舊使用5.4版本的linux,其下載鏈接:https://codeload.github.com/torvalds/linux/tar.gz/refs/tags/v5.4
并將源碼放置如下文件,為了方便后續管理,我在gitee上創建了一個倉庫,為自己后續閱讀源碼添加注釋做準備。
/home/damon/00_code/02_gitee/linux_5.4/linux-5.4
2、編譯內核源碼
2.1 選擇編譯工具
ubuntu本身自帶gcc編譯器,不過是針對X86平臺的,我們現在要編譯ARM架構的代碼,也就是在X8平臺上編譯ARM架構的工具,叫做ARM GCC交叉編譯器。
我將其解壓放置在以下目錄:
/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf
2.2 編譯配置
基于ARM架構的配置,我們選擇*./arch/arm/configs/vexpress_defconfig*配置。
直接上編譯腳本:
注意事項:如果你的環境是第一次配置,第一次編譯的時候大概率會出錯,按照錯誤提示按照依賴工具即可繼續編譯。
# 根據個人存放的Linux源碼目錄,修改成自己的目錄路徑
cd /home/damon/00_code/02_gitee/linux_5.4/linux-5.4
# 清理工程
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- distclean
# 配置文件選擇:./arch/arm/configs/vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- vexpress_defconfig
# 打開圖形配置界面,我們選擇默認配置
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig
2.3 編譯內核
編譯內核時,執行如下命令:
# -j8,可以根據實際核數修改,可以提速編譯
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- -j8
編譯成功后,我們可以得到內核鏡像文件和設備樹文件。內核鏡像文件就是我們要跑代碼的執行文件,設備樹文件就是內核啟動后加載它并生成設備節點的文件。
得到的內核鏡像文件叫 zImage ,放在如下路徑:
/home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot
同時,對應vexpress_defconfig配置下生成的設備樹文件:vexpress-v2p-ca9.dtb
內核編譯好了,如何將它啟動呢?
3、如何啟動內核
一般地,學習嵌入式linux,手頭上要有一塊開發板,一個支持linux開發的開發板少則幾百塊,貴則上千塊。
如果你只是想玩一玩,看一看,完全沒必要花這些錢,那么沒有開發板該如何啟動Linux內核呢?
我們可以采用 qemu搭建運行環境 。
qemu是“Quick Emulation”的縮寫,是一個用C語言編寫的開源虛擬化軟件。它可以模擬硬件,在指定的硬件平臺上搭建運行開發環境。
我們暫時無需知道其工作原理,直接拿來安裝使用。
3.1 qemu安裝
安裝qemu工具
sudo apt-get install qemu
測試qemu是否安裝成功:
qemu起kernel
現在我們有了kernel鏡像文件和設備樹文件,我們按照qemu的指令啟動:
qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "console=ttyAMA0"
啟動日志:
省略......
CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.4.0+ #1
Hardware name: ARM-Versatile Express
[< 80110e90 >] (unwind_backtrace) from [< 8010c4a4 >] (show_stack+0x10/0x14)
[< 8010c4a4 >] (show_stack) from [< 80764c24 >] (dump_stack+0x90/0xa4)
[< 80764c24 >] (dump_stack) from [< 8012147c >] (panic+0x110/0x310)
[< 8012147c >] (panic) from [< 80a01584 >] (mount_block_root+0x204/0x2b4)
[< 80a01584 >] (mount_block_root) from [< 80a01758 >] (mount_root+0x124/0x148)
[< 80a01758 >] (mount_root) from [< 80a018d0 >] (prepare_namespace+0x154/0x198)
[< 80a018d0 >] (prepare_namespace) from [< 8077bd44 >] (kernel_init+0x8/0x110)
[< 8077bd44 >] (kernel_init) from [< 801010e8 >] (ret_from_fork+0x14/0x2c)
Exception stack(0x9e493fb0 to 0x9e493ff8)
3fa0: 00000000 00000000 00000000 00000000
3fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
3fe0: 00000000 00000000 00000000 00000000 00000013 00000000
---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
看最后一行:啟動到一半出錯了,Kernel Painc!!!
看到Panic,"慌不慌" ?不過kernel會將Panic的原因也打印出來:Unable to mount root fs
不能掛在到rootfs上,什么是rootfs呢?沒有它,我們該如何做一個呢?
4、 什么是rootfs?
root fs的中文名叫根文件系統。
文件我們好理解,比如我們常見的office文件、圖片、視頻、壓縮包等等都叫文件。
文件系統可以理解為能解析識別這些文件的文件。
昨天我老婆下載了一個壓縮包,解壓出來發現一個jar文件windows無法識別,這時候我們就要安裝JAVA JDK,我們可以粗略地將JAVA JDK叫做能解析jar文件的文件系統(這個比喻不太恰當)。
那什么是根呢?就是Linux內核啟動所掛載的第一個文件系統,所以這個根字也體現了它的重要性!
一句話總結:rootfs是Linux內核啟動掛載的第一個文件系統,系統引導啟動程序會在根文件系統掛載之后,從中把一些基本的初始化腳本和服務等加載到內存中去運行。
4.1 如何制作一個rootfs
制作rootfs我們要借用一個工具:BusyBox,忙碌盒子,這個盒子會提供大量Linux命令和工具的軟件。
BusyBox的官網地址為: https://busybox.net/ ,最新的版本已經達到1.36.0。
我們不追求最新,因為最新的往往會存在一些兼容性或未知BUG,使用我之前用過的1.32.0版本,下載鏈接:https://busybox.net/downloads/busybox-1.32.0.tar.bz2
解壓、配置、編譯BusyBox和編譯Linux的流程差不多,此處就直接貼腳本了:
cd /home/damon/00_code/02_gitee/busy_box
tar -vxjf busybox-1.32.0.tar.bz2
/home/damon/00_code/02_gitee/busy_box/busybox-1.32.0
# 手動修改CROSS_COMPILE和ARCH
make defconfig
make menuconfig
配置完后編譯
make
# 編譯好的東西,我把它安裝到 /home/damon/00_code/02_gitee/busy_box/rootfs目錄下
make install CONFIG_PREFIX=/home/damon/00_code/02_gitee/busy_box/rootfs
安裝完畢,文件目錄顯示如下:
可以看到rootfs中有3個文件夾和一個鏈接文件,具體作用先不展開,此時busybox的工作就完成了,但是此時的rootfs還不能使用,還缺少一些東西,接下來繼續補充。
cd /home/damon/00_code/02_gitee/busy_box/rootfs
# 創建lib目錄,并添加庫文件(這些庫文件都是從GCC中獲取的)
mkdir lib
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/lib
cp *so* /home/damon/00_code/02_gitee/busy_box/rootfs/lib/ -d
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/lib
cp *so* *.a /home/damon/00_code/02_gitee/busy_box/rootfs/lib/ -d
# 向usr/lib目錄添加庫文件
cd /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib
cp *so* *.a /home/damon/00_code/02_gitee/busy_box/rootfs/usr/lib/ -d
# 創建其他文件目錄
cd /home/damon/00_code/02_gitee/busy_box/rootfs
mkdir dev proc mnt sys tmp etc root
此時一個可以使用的rootfs便制作完成了,rootfs目錄如下圖所示:
4.2 rootfs如何使用呢?
rootfs一般放到SD卡或者磁盤中,讓內核去讀取啟動。我手頭上沒有SD卡,在PC上劃出一塊磁盤又比較浪費。我們就制作一個SD卡鏡像,它是為qemu創建的虛擬SD卡。執行腳本如下:
cd /home/damon/00_code/02_gitee
# 制作rootfs.ext4.img文件
dd if=/dev/zero of=rootfs.ext4.img bs=1M count=500
# 圖片截取的是32M,后面實際應用時,發現32M不夠用,就修改成了500M
# 格式化
mkfs.ext4 rootfs.ext4.img
# 將rootfs.ext4.img掛載到/mnt/rootfs
mkdir -p /mnt/rootfs
mount -t ext4 -o loop rootfs.ext4.img /mnt/rootfs
# 將rootfs內所有文件拷貝至rootfs.ext4.img
cp -a /home/damon/00_code/02_gitee/busy_box/rootfs/* /mnt/rootfs/
# 卸載
umount /mnt/rootfs
# 此時得到一個裝有rootfs的鏡像
/home/damon/00_code/02_gitee/rootfs.ext4.img
現在rootfs做好了,我們去修復上面的Panic.
5、 重新啟動kernel
我們在原有的qemu命令基礎上指定rootfs的鏡像文件,重新啟動:
qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -sd /home/damon/00_code/02_gitee/rootfs.ext4.img -append "root=/dev/mmcblk0 rw console=ttyAMA0"
啟動日志如下:
之前的kernel panic沒有了,但是又有新的告警了,一般錯誤是必須要修復的,而告警內容是可以忽略的,除非影響到了使用和性能。
當前告警在循環打印無法打開,/dev/tty2 /dev/tty3 /dev/tty4, 看到這個我們顯然知道是缺少串口設備,按照如下腳本添加,并更新鏡像文件。
sudo mknod dev/tty2 c 5 1
sudo mknod dev/tty3 c 5 1
sudo mknod dev/tty4 c 5 1
此時Linux操作系統起來了,完整的詳細的啟動日志如下:
damon@ubuntu:~/00_code/02_gitee$
damon@ubuntu:~/00_code/02_gitee$
damon@ubuntu:~/00_code/02_gitee$ qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/zImage -dtb /home/damon/00_code/02_gitee/linux_5.4/linux-5.4/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -sd /home/damon/00_code/02_gitee/rootfs.ext4.img -append "root=/dev/mmcblk0 rw console=ttyAMA0"
WARNING: Image format was not specified for '/home/damon/00_code/02_gitee/rootfs.ext4.img' and probing guessed raw.
Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
Specify the 'raw' format explicitly to remove the restrictions.
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument
Booting Linux on physical CPU 0x0
Linux version 5.4.0+ (damon@ubuntu) (gcc version 9.2.1 20191025 (GNU Toolchain for the A-profile Architecture 9.2-2019.12 (arm-9.10))) #1 SMP Sat Jan 14 15:12:10 CST 2023
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
OF: fdt: Machine model: V2P-CA9
Memory policy: Data cache writeback
Reserved memory: created DMA memory