4. Linux内核配置与编译
根文件系统,说白了就是Linux启动后看到的/目录下那一整套东西。没有它,内核启动到一半就会panic——因为它找不到init进程。这一章我们就来亲手把它搭出来。
4.1 Busybox:一个程序撑起一个用户空间
Busybox是什么?我习惯叫它“瑞士军刀”。它把上百个常用Linux命令(ls、cp、sh、mount……)塞进一个可执行文件里,通过符号链接或硬链接来区分调用哪个命令。
核心原理:Busybox的main函数会检查argv[0],根据程序名跳转到对应的命令实现。所以一个二进制文件就能干所有事。
4.1.1 下载与配置
从官网或镜像站拉一份源码,我一般用长期支持版本:
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
配置方式有三种:
| 配置命令 | 适用场景 | 说明 |
|---|---|---|
make defconfig |
快速起步 | 启用大部分通用功能,适合初学者 |
make menuconfig |
定制裁剪 | 图形界面,逐项勾选,我项目里常用这个 |
make allyesconfig |
全功能测试 | 打开所有选项,镜像会很大,不推荐生产用 |
我个人习惯先用defconfig打底,再用menuconfig微调。比如关掉不需要的vi、telnetd,打开mdev(设备热插拔支持)。
小技巧:交叉编译时记得设置CROSS_COMPILE环境变量,比如export CROSS_COMPILE=arm-linux-gnueabihf-。否则编译出来的是x86版本,放到ARM板上跑不了。
4.1.2 编译与安装
配置好之后,编译安装一气呵成:
make -j$(nproc)
make install CONFIG_PREFIX=/path/to/rootfs
CONFIG_PREFIX指定安装目标目录。执行完后,那个目录下就会出现bin/、sbin/、usr/等文件夹,里面全是Busybox的符号链接。
注意:make install不会自动创建dev/、proc/、sys/等目录,这些需要手动补上。我曾经第一次做根文件系统时忘了建dev/,结果启动后控制台没输出,排查了半天才发现是/dev/console不存在。
4.2 根文件系统目录结构:不只是放文件
Linux根文件系统的目录结构有FHS(Filesystem Hierarchy Standard)规范,但嵌入式系统可以适当精简。下面是我常用的最小化结构:
| 目录 | 用途 | 是否必须 |
|---|---|---|
/bin |
用户命令(ls, cp, sh…) | 是 |
/sbin |
系统管理命令(init, mount, ifconfig…) | 是 |
/etc |
配置文件(inittab, fstab, passwd…) | 是 |
/dev |
设备节点 | 是 |
/proc |
进程信息虚拟文件系统 | 是 |
/sys |
内核对象虚拟文件系统 | 是 |
/tmp |
临时文件 | 建议 |
/var |
可变数据(日志、锁文件) | 建议 |
/mnt |
挂载点 | 可选 |
/usr |
用户程序和数据 | 可选 |
创建这些目录很简单:
mkdir -p rootfs/{bin,sbin,etc,dev,proc,sys,tmp,var,mnt,usr/{bin,sbin,lib}}
4.3 init进程与inittab:系统启动的“总指挥”
内核启动的最后一步是执行/sbin/init。这个init程序(Busybox内置的)会读取/etc/inittab,按里面的规则启动各种服务。
4.3.1 inittab文件格式
每一行代表一个动作,格式是:
id:runlevels:action:process
- id:控制台设备名,比如
ttyS0,可以留空 - runlevels:运行级别,Busybox忽略此项,通常留空
- action:执行时机,常见的有
sysinit、respawn、askfirst、once、shutdown - process:要执行的程序或脚本
下面是一个典型的最小化inittab:
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
::askfirst:/bin/sh
::shutdown:/bin/umount -a -r
解释一下:
sysinit:系统初始化时运行/etc/init.d/rcS脚本respawn:启动串口登录,进程退出后自动重启askfirst:在控制台显示“Please press Enter to activate this console”,按回车后进入shellshutdown:关机时卸载所有文件系统
经验之谈:我一般把askfirst放在最后作为保底shell,这样即使其他服务起不来,至少还能进控制台排查问题。
4.3.2 rcS初始化脚本
/etc/init.d/rcS是一个shell脚本,负责挂载虚拟文件系统、配置网络、启动服务等。一个简单的版本:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
mdev -s
ifconfig lo 127.0.0.1
echo "System ready."
mdev -s会根据/sys中的设备信息自动创建/dev下的节点。这比手动创建成千上万个设备节点靠谱多了。
4.4 制作根文件系统镜像
根文件系统可以放在多种介质上:initramfs(内存盘)、SD卡分区、NFS网络挂载、Flash文件系统(jffs2、ubifs)等。这里我以最常用的initramfs和ext4镜像为例。
4.4.1 制作initramfs(cpio格式)
initramfs是内核直接支持的压缩内存文件系统。制作步骤:
cd /path/to/rootfs
find . | cpio -H newc -o | gzip > ../rootfs.cpio.gz
然后把rootfs.cpio.gz作为内核的initramfs源,编译进内核或由bootloader加载。
调试技巧:内核启动参数加rdinit=/sbin/init可以指定initramfs里的init路径。如果启动卡住,加console=ttyS0,115200 initcall_debug看详细日志。
4.4.2 制作ext4根文件系统镜像
适合放在SD卡或eMMC上:
dd if=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 rootfs.ext4
mount -o loop rootfs.ext4 /mnt
cp -a /path/to/rootfs/* /mnt/
umount /mnt
这样就得到一个64MB的ext4镜像,可以直接dd到分区上。
4.4.3 目录结构总览
下面这张SVG图展示了根文件系统的核心组成和启动流程:
4.5 避坑指南
做根文件系统时,有几个坑我踩过不止一次:
- 设备节点缺失:至少要有
/dev/console和/dev/null,否则控制台没输出。可以用mknod手动创建,或者靠mdev自动生成。 - inittab格式错误:Busybox的inittab解析比较严格,多一个空格或少一个冒号都可能导致init拒绝执行。我曾经因为行尾多了个空格,折腾了一下午。
- rcS没有执行权限:记得
chmod +x /etc/init.d/rcS,否则init会报错“can't execute '/etc/init.d/rcS'”。 - 动态链接库缺失:如果Busybox编译成动态链接,根文件系统里必须包含
ld-linux.so.*和libc.so.*。用arm-linux-gnueabihf-readelf -d busybox可以查看依赖。
重要提醒:生产环境中,根文件系统不要用initramfs存放可写数据,因为它是内存盘,掉电丢失。日志、配置修改等应该挂载持久化分区(比如/data或/overlay)。
好了,到这一步,你已经有了一个可以启动的最小化根文件系统。把它和内核、设备树打包在一起,就能看到熟悉的Linux登录提示符了。