想让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