3. Busybox init进程:Linux系统启动流程

嵌入式Linux启动,说白了就是一段「从零到一」的过程。从Bootloader加载内核,到内核挂载根文件系统,再到用户空间的第一个进程——init。这个init进程,在Busybox的世界里,就是我们要聊的主角。

我个人习惯把启动流程分成三个阶段:硬件初始化、内核启动、用户空间初始化。今天重点讲第三个阶段,也就是Busybox init登场的地方。

3.1 Linux系统启动流程概览

先快速过一遍整体流程,这样你心里有个谱:

  1. Bootloader(如U-Boot):初始化硬件,加载内核到内存
  2. 内核解压与初始化:解压zImage,挂载根文件系统
  3. 执行init进程:内核启动完成后,在用户空间启动第一个进程

内核启动的最后一步,会尝试执行以下几个路径(按优先级):

/sbin/init
/etc/init
/bin/init
/bin/sh

嗯,这里要注意。如果这些路径都找不到,内核会直接panic。我在项目中遇到过这种情况——根文件系统里少了/bin/sh,结果系统启动到一半就卡死了。排查了半天才发现是文件系统裁剪得太狠。

警告: 裁剪根文件系统时,务必保留至少一个可用的shell路径,否则系统无法进入用户空间。

3.2 Busybox init的工作原理

Busybox init和传统的SysV init不太一样。SysV init用运行级别(runlevel)那一套,而Busybox init走的是「轻量级」路线。它只做三件事:

  • 读取配置文件 /etc/inittab
  • 按配置启动各个程序
  • 监控子进程状态

为什么会这么设计?你想想看,嵌入式设备资源有限,不需要那么复杂的运行级别切换。Busybox init把启动、重启、关机这些动作都简化了。

我记得有一次给一个IoT网关做系统,设备只有4MB Flash。用Busybox init代替SysV init,光这一项就省下了将近200KB的空间。对于嵌入式设备来说,这可不是小数目。

3.3 inittab文件详解

inittab是Busybox init的「剧本」。它告诉init进程:什么时候该做什么事。

文件格式很简单,每行一个条目:

id:runlevels:action:process

各字段含义:

字段 说明 示例
id 唯一标识符,1-4个字符 tty1, ::
runlevels Busybox init忽略此字段,留空即可 (空)
action 动作类型,决定何时执行 respawn, askfirst
process 要执行的程序路径 /sbin/getty 115200 ttyS0

action字段是核心,常用的有这些:

  • sysinit:系统初始化时执行一次,通常用来挂载文件系统、设置主机名
  • respawn:进程终止后自动重启,适合getty等需要常驻的服务
  • askfirst:类似respawn,但启动前会先打印「Press Enter to activate this console」
  • once:只执行一次,不等待完成
  • wait:执行一次,并等待进程结束
  • ctrlaltdel:按下Ctrl+Alt+Del时执行
  • shutdown:系统关机时执行
重点: sysinit动作只执行一次,且在所有其他动作之前执行。通常用来做环境初始化。

来看一个典型的inittab文件:

::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L ttyS0 115200 vt100
::respawn:/sbin/getty 38400 tty1
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

这个配置做了几件事:

  1. 启动时先执行 /etc/init.d/rcS 脚本(挂载文件系统、配置网络等)
  2. 在串口ttyS0和终端tty1上各启动一个登录会话
  3. 按下Ctrl+Alt+Del时重启系统
  4. 关机时卸载所有文件系统

我曾经在一个项目里踩过坑——inittab里忘了加shutdown条目。结果每次关机时文件系统都没卸载,导致ext4分区损坏。从那以后,我写inittab的第一件事就是把shutdown和ctrlaltdel加上。

小技巧: 调试阶段可以在inittab里加一个 ::respawn:/bin/sh,这样即使其他服务启动失败,也能得到一个shell来排查问题。

3.4 实战:编写一个最小化的inittab

假设你手头有一个只有2MB Flash的嵌入式设备,没有硬盘,没有网络。最小化的inittab可以写成这样:

::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -t sysfs sysfs /sys
::sysinit:/bin/mount -o remount,rw /
::respawn:/sbin/getty -L ttyS0 115200 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

这个配置只做了最核心的事:挂载虚拟文件系统、启动串口终端、处理重启和关机。没有网络服务,没有日志系统,但足够让设备跑起来。

我个人习惯在sysinit里加一个 /bin/hostname -F /etc/hostname,这样设备启动后就能看到主机名,方便区分多台设备。

3.5 常见问题与排查

Busybox init出问题时,通常表现为系统启动后没有控制台,或者进程反复重启。排查思路:

  • 检查inittab语法:每行格式是否正确?路径是否存在?
  • 检查文件权限:inittab的权限通常是644,但里面的可执行文件必须有执行权限
  • 检查依赖:比如getty需要/dev/ttyS0存在,否则会启动失败

我曾经遇到过一个诡异的问题——系统启动后串口没反应,但内核日志显示init已经执行了。查了半天发现是inittab里写的是 ttyS0,但内核注册的设备名是 ttyAMA0。嗯,不同芯片的串口设备名不一样,这个坑踩过一次就记住了。

注意: 串口设备名因平台而异。x86上通常是ttyS0,ARM上可能是ttyAMA0或ttyS0,RISC-V上可能是ttySIF0。写inittab前先确认内核的设备名。

总结一下:Busybox init虽然简单,但足够灵活。只要理解inittab的action机制,就能控制嵌入式系统的启动行为。下一章我们会聊到Buildroot如何自动生成这些配置,让你从手动编写中解放出来。