Buildroot 开机自启动:让你的 ARM64 程序随 Linux 启动运行
每次启动 QEMU,都要登录系统,再手动输入/usr/bin/debug_demo。学习时这样做没问题,但如果这是一个采集数据、监测状态的设备,难道每次开机都要有人敲命令吗?
这一篇,让程序随 Linux 启动自动运行,并且能查看状态、读取日志、停止和重新启动。继续使用前四篇的 Ubuntu、QEMU 和 Buildroot 环境,不需要开发板。
我们不把命令临时塞进旧镜像,而是让 Buildroot 软件包同时安装程序和启动脚本。这样,重新构建镜像后,自启动能力也能保留下来。
一、这次要完成什么
前四篇依次解决了启动 ARM64 Linux、远程调试程序、添加自定义软件包,以及保存 defconfig 并在新目录重建系统。
如果还没有建立外部软件包目录,请先完成第三篇《告别手动复制:在 Buildroot 中添加自己的 ARM64 软件包》,再按第四篇《Buildroot 配置怎么备份?用 defconfig 在新目录重建 ARM64 系统》准备新输出目录。本篇从这套已构建成功的环境继续,不是从空目录开始搭建。
本篇接着完成下面的流程:
Buildroot 安装程序和启动脚本 -> 生成 rootfs 镜像 -> QEMU 启动 Linux -> init 调用启动脚本 -> debug_demo 在后台定期写入日志最终检查四件事:
- 开机后不手动启动应用,就能查到服务进程及其日志。
- 再执行一次
start,不会重复启动实例。 - 可以执行
stop、start、restart和status。 - 正常重启系统后,程序再次自动运行。
开机自启动不等于进程守护。本文的脚本负责启动和停止,不会在程序崩溃后自动拉起它,也不提供健康检查或故障恢复。
二、沿用新输出目录,先备份文件
| 项目 | 本文使用的环境 |
|---|---|
| 开发主机 | Ubuntu 20.04 x86_64 虚拟机 |
| Buildroot | 2025.02.8,源码位于~/buildroot-2025.02.8 |
| 输出目录 | 上篇已构建的~/buildroot-output-arm64-demo |
| 外部目录 | ~/buildroot-external-demo,标识为ARM64_DEMO |
| 自定义包 | debug-demo |
| 目标程序 | /usr/bin/debug_demo |
| 本篇启动方式 | BusyBox init 与/etc/init.d/脚本 |
默认在 Ubuntu 主机执行命令。只有明确标注“QEMU 目标端”的命令才在 ARM64 Linux 内执行。不要把这些 Bash 命令直接复制到 Windows PowerShell。
先关闭仍在使用该镜像的 QEMU:在目标端执行poweroff,系统关机后若 QEMU 未退出,再按Ctrl+A,松开后按X。生成镜像时不要让 QEMU 同时写入它,也不要从主机挂载正在使用的镜像。
在 Ubuntu 终端重新设置变量,不依赖上次终端留下的环境:
exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"cd~/buildroot-2025.02.8printf'DEMO_OUT=%s\n'"$DEMO_OUT"test-f"$DEMO_OUT/.config"echo$?grep-E'^BR2_(INIT_BUSYBOX|PACKAGE_BUSYBOX|PACKAGE_DEBUG_DEMO)='"$DEMO_OUT/.config"预期文件检查返回0,三个配置项均为y。如果目录不存在,先完成上一篇的新目录构建;如果使用的是 systemd 或其他 init,请先停止套用本篇步骤,不要直接切换 init 后继续增量构建。
备份将要修改的外部文件和当前配置:
BACKUP_DIR="$HOME/debug-demo-before-autostart-$(date+%Y%m%d-%H%M%S)"mkdir-p"$BACKUP_DIR"cp-a"$HOME/buildroot-external-demo""$BACKUP_DIR/"cp-p"$DEMO_OUT/.config""$BACKUP_DIR/buildroot.config"printf'Backup: %s\n'"$BACKUP_DIR"下面会覆盖示例源码和包规则。已有其他改动的读者应先比较后合并,不要直接覆盖。无需执行make clean、make distclean,也不要重新加载官方 defconfig。
三、先弄清是谁启动了应用
Ubuntu 主机的服务管理方式,不代表 QEMU 内的目标系统也一样。本篇只针对已经确认的 BusyBox init,启动链路如下:
Linux 内核 -> /sbin/init(PID 1,BusyBox init) -> /etc/inittab -> /etc/init.d/rcS -> /etc/init.d/S99debug-demo startBuildroot 2025.02.8 的默认 BusyBoxinittab会在系统初始化时执行rcS。rcS按文件名顺序处理/etc/init.d/S??*,对本文这种不带.sh后缀的脚本传入start参数。正常关机时,rcK反向遍历这些脚本并传入stop。
S99debug-demo中的99让它排在较后的启动位置,但编号不是依赖管理:它不保证网络已经连通,也不保证某个外设就绪。本例不依赖网络或硬件。
先检查上篇生成的目标文件,确认没有被自定义 overlay 替换成另一套启动流程:
grep-n'/etc/init.d/rc[SK]'"$DEMO_OUT/target/etc/inittab"cat"$DEMO_OUT/target/etc/init.d/rcS"cat"$DEMO_OUT/target/etc/init.d/rcK"grep-E'^CONFIG_(START_STOP_DAEMON|FEATURE_START_STOP_DAEMON_FANCY|READLINK|SLEEP)=y$'\"$DEMO_OUT"/build/busybox-*/.config最后一项预期能找到四个已启用的 BusyBox 配置:启动管理工具、支持后台启动和写 PID 文件的扩展选项,以及脚本使用的readlink、sleep。如果构建目录已清理,先恢复构建;如果选项缺失,先补齐并保存自定义 BusyBox 配置、重建验证,不要假设主机上的同名命令能替代目标端工具。
本次检查 BusyBox 构建配置的实际输出如下,四个必需选项均已启用:
CONFIG_READLINK=y CONFIG_SLEEP=y CONFIG_START_STOP_DAEMON=y CONFIG_FEATURE_START_STOP_DAEMON_FANCY=y四、让原程序增加持续运行模式
原来的程序算完数组总和就退出。这样的程序也可以开机执行,但不方便观察持续运行、停止和重启。
这次保留无参数时的v2输出,新增--service模式:每两秒写一条日志,收到SIGTERM或SIGINT后退出。程序本身不 fork,后台运行交给启动工具处理。
在Ubuntu 主机执行完整代码块:
cd~/buildroot-external-democat>src/debug-demo/debug_demo.c<<'EOF' #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> static volatile sig_atomic_t running = 1; static void handle_signal(int signal_number) { (void)signal_number; running = 0; } int compute_total(const int *values, int count) { int total = 0; for (int index = 0; index < count; ++index) { total += values[index]; } return total; } int main(int argc, char *argv[]) { const int values[] = {10, 20, 30, 40}; int result = compute_total(values, 4); if (argc == 1) { printf("debug-demo package v2: total = %d\n", result); return 0; } if (argc != 2 || strcmp(argv[1], "--service") != 0) { fprintf(stderr, "Usage: %s [--service]\n", argv[0]); return 1; } struct sigaction action = {0}; action.sa_handler = handle_signal; sigemptyset(&action.sa_mask); if (sigaction(SIGTERM, &action, NULL) != 0 || sigaction(SIGINT, &action, NULL) != 0) { perror("sigaction"); return 1; } FILE *log_file = fopen("/var/log/debug-demo.log", "a"); if (log_file == NULL) { perror("open log"); return 1; } unsigned long tick = 0; while (running) { if (fprintf(log_file, "debug-demo service: pid=%ld tick=%lu total=%d\n", (long)getpid(), tick++, result) < 0 || fflush(log_file) != 0) { fclose(log_file); return 1; } sleep(2); } if (fprintf(log_file, "debug-demo service: pid=%ld stopped\n", (long)getpid()) < 0) { fclose(log_file); return 1; } return fclose(log_file) == 0 ? 0 : 1; } EOF这里有三个设计点:
- 信号处理函数只修改退出标志,不在其中调用
printf或操作日志。 - 每条日志后调用
fflush,把 C 库缓冲区中的内容交给系统,便于及时查看;它不等于保证掉电后数据仍在磁盘上。 - 服务直接打开日志文件,不依赖后台启动后的标准输出。BusyBox 的后台启动会处理标准输入输出,不能简单假定在启动命令后加重定向就一定能收集应用日志。
日志路径先固定为/var/log/debug-demo.log,本篇按 root 运行。它是短时间实验方案,没有日志轮转,不能无限运行;默认文件系统中该路径还可能位于易失存储,重启后日志不一定保留。生产环境应另外设计日志限额、权限和持久化。
五、编写可启动、停止和查状态的脚本
本篇只新增一个外部文件:package/debug-demo/S99debug-demo。它最终由软件包安装到目标系统的/etc/init.d/。
仍在Ubuntu 主机的~/buildroot-external-demo中执行:
cat>package/debug-demo/S99debug-demo<<'EOF' #!/bin/sh DAEMON=/usr/bin/debug_demo PIDFILE=/var/run/debug-demo.pid is_running() { [ -r "$PIDFILE" ] || return 1 PID=$(cat "$PIDFILE") || return 1 case "$PID" in ''|*[!0-9]*) return 1 ;; esac [ "$PID" -gt 1 ] || return 1 kill -0 "$PID" 2>/dev/null || return 1 [ "$(readlink "/proc/$PID/exe")" = "$DAEMON" ] } start() { if is_running; then echo "debug-demo already running (pid $PID)" return 0 fi [ -x "$DAEMON" ] || return 1 mkdir -p /var/run /var/log || return 1 rm -f "$PIDFILE" || return 1 start-stop-daemon -S -b -m -p "$PIDFILE" -x "$DAEMON" -- --service || return 1 sleep 1 if is_running; then echo "debug-demo started (pid $PID)" return 0 fi echo "debug-demo failed to start; check executable, arguments and log directory" >&2 return 1 } stop() { if ! is_running; then rm -f "$PIDFILE" || return 1 echo "debug-demo not running" return 0 fi start-stop-daemon -K -p "$PIDFILE" -x "$DAEMON" -s TERM || return 1 attempts=0 while is_running; do if [ "$attempts" -ge 5 ]; then echo "debug-demo did not stop; keeping PID file" >&2 return 1 fi sleep 1 attempts=$((attempts + 1)) done rm -f "$PIDFILE" || return 1 echo "debug-demo stopped" } case "$1" in start) start ;; stop) stop ;; restart) stop && start ;; status) if is_running; then echo "debug-demo running (pid $PID)" else echo "debug-demo not running" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status}" >&2 exit 1 ;; esac EOFchmod0755 package/debug-demo/S99debug-demosh-npackage/debug-demo/S99debug-demoecho$?语法检查预期返回0。它只验证 shell 语法,不能证明 ARM64 系统上的启动流程已经成功。文件应使用 LF 换行,不要把脚本保存成 Windows 的 CRLF 格式。
start-stop-daemon是启动和停止进程的工具,名字里有 daemon,并不代表它会常驻监控程序:
| 参数 | 本文中的作用 |
|---|---|
-S/-K | 启动 / 向匹配进程发信号 |
-b | 后台启动,避免常驻程序阻塞rcS |
-m -p | 启动时创建指定的 PID 文件 |
-x | 指定可执行文件;停止时与 PID 一起用于匹配 |
-s TERM | 发送正常终止信号,让应用处理退出 |
-- --service | 将--service传给应用,不再作为工具选项解析 |
PID 是进程编号。只看到 PID 文件存在,并不能说明程序还活着,所以脚本同时检查进程及/proc/<PID>/exe。停止后最多等待约五秒;超时就报错并保留 PID 文件,不直接强制杀死进程。
这个脚本的边界:它面向单人、顺序操作的实验,启动后的一秒检查只是存活检查,不是业务就绪通知。它没有并发控制锁,也不能消除所有 PID 复用竞争;不要同时调用多个start/restart,不要手工删除运行中服务的 PID 文件,也不要绕过脚本另开--service实例。
六、让软件包安装启动脚本
不改外部目录的external.desc、顶层Config.in或external.mk,也不新增第二个软件包。
在原规则中增加DEBUG_DEMO_INSTALL_INIT_SYSV。下面给出完整文件,方便与第三篇对照;define内的命令保持 Tab 缩进:
cat>package/debug-demo/debug-demo.mk<<'EOF' DEBUG_DEMO_VERSION = 1.0 DEBUG_DEMO_SITE = $(BR2_EXTERNAL_ARM64_DEMO_PATH)/src/debug-demo DEBUG_DEMO_SITE_METHOD = local define DEBUG_DEMO_BUILD_CMDS $(TARGET_CC) $(TARGET_CFLAGS) -Wall -Wextra -O0 -g -gdwarf-4 \ -fno-omit-frame-pointer $(@D)/debug_demo.c \ -o $(@D)/debug_demo $(TARGET_LDFLAGS) endef define DEBUG_DEMO_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/debug_demo \ $(TARGET_DIR)/usr/bin/debug_demo endef define DEBUG_DEMO_INSTALL_INIT_SYSV $(INSTALL) -D -m 0755 $(DEBUG_DEMO_PKGDIR)/S99debug-demo \ $(TARGET_DIR)/etc/init.d/S99debug-demo endef $(eval $(generic-package)) EOF虽然接口名带SYSV,它也适用于 Buildroot 的 BusyBox init。Buildroot 根据所选 init 执行对应安装接口,并不是只认 Linux 主机上的服务管理器。
这里沿用前文的调试编译参数,-O0会覆盖前面$(TARGET_CFLAGS)中的优化级别。如果其中启用了_FORTIFY_SOURCE,可能出现_FORTIFY_SOURCE requires compiling with optimization (-O)告警,表示该程序的这项加固未生效,不等于本次编译失败;仍需检查构建返回码及目标端运行结果。本文保留这组已实测参数,不把它作为生产配置推荐。用于生产环境时,应重新评估优化和加固选项,并重新构建、验证,而不是仅屏蔽告警。
$(DEBUG_DEMO_PKGDIR)指向包规则所在目录,里面有启动脚本;$(@D)指向应用源码同步后的构建目录。两者用途不同。启动脚本不需要复制到src/debug-demo/,也不需要再放进 overlay。
本次没有改变 Buildroot 配置选择,原来的BR2_PACKAGE_DEBUG_DEMO=y仍然有效,因此不必为了新增脚本重新保存 defconfig。但必须保存更新后的源码、包规则及启动脚本;defconfig 本身不包含它们。
七、重建软件包,再生成镜像
回到Ubuntu 主机的 Buildroot 源码目录:
cd~/buildroot-2025.02.8makeO="${DEMO_OUT:?请先设置 DEMO_OUT}"\BR2_EXTERNAL="$HOME/buildroot-external-demo"debug-demo-rebuildV=1echo$?确认返回0后,再生成镜像:
makeO="${DEMO_OUT:?请先设置 DEMO_OUT}"\BR2_EXTERNAL="$HOME/buildroot-external-demo"-j2echo$?${DEMO_OUT:?...}会在变量未设置或为空时阻止命令执行,避免把空值传给O=。每个echo $?都必须紧接着被检查的命令执行;有非零返回码就先处理错误,不要继续启动旧镜像。
本例使用本地源码包,debug-demo-rebuild会重新同步源码、编译和安装。后面的完整make才负责更新 rootfs 镜像,不能省略。
检查产物:
file"$DEMO_OUT/target/usr/bin/debug_demo"ls-l"$DEMO_OUT/target/etc/init.d/S99debug-demo"sh-n"$DEMO_OUT/target/etc/init.d/S99debug-demo"echo$?cmp"$HOME/buildroot-external-demo/package/debug-demo/S99debug-demo"\"$DEMO_OUT/target/etc/init.d/S99debug-demo"echo$?ls-lh"$DEMO_OUT/images/Image""$DEMO_OUT/images/rootfs.ext2"本次检查结果如下:程序为 ARM64 ELF 文件,启动脚本具有可执行权限;sh -n和cmp均返回0,说明脚本通过语法检查,且安装后的内容与外部目录中的源文件一致。下面保留实际终端记录:
embed@embedtest:~/buildroot-2025.02.8$ file "$DEMO_OUT/target/usr/bin/debug_demo" /home/embed/buildroot-output-arm64-demo/target/usr/bin/debug_demo: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 6.12.0, stripped embed@embedtest:~/buildroot-2025.02.8$ ls -l "$DEMO_OUT/target/etc/init.d/S99debug-demo" -rwxr-xr-x 1 embed embed 1731 Oct 17 04:42 /home/embed/buildroot-output-arm64-demo/target/etc/init.d/S99debug-demo embed@embedtest:~/buildroot-2025.02.8$ sh -n "$DEMO_OUT/target/etc/init.d/S99debug-demo" embed@embedtest:~/buildroot-2025.02.8$ echo $? 0 embed@embedtest:~/buildroot-2025.02.8$ cmp "$HOME/buildroot-external-demo/package/debug-demo/S99debug-demo" \ > "$DEMO_OUT/target/etc/init.d/S99debug-demo" embed@embedtest:~/buildroot-2025.02.8$ echo $? 0 embed@embedtest:~/buildroot-2025.02.8$ ls -lh "$DEMO_OUT/images/Image" "$DEMO_OUT/images/rootfs.ext2" -rw-r--r-- 1 embed embed 12M Oct 15 20:36 /home/embed/buildroot-output-arm64-demo/images/Image -rw-r--r-- 1 embed embed 60M Oct 17 04:48 /home/embed/buildroot-output-arm64-demo/images/rootfs.ext2stripped表示目标程序的调试符号已被剥除,不影响正常运行。rootfs 镜像的修改时间已更新,而内核Image仍保留此前的时间,这是正常的:本次只修改应用和启动脚本,不需要重新编译内核。
以上记录确认了主机端产物,但未包含完整make的返回码。target/是构建过程中的目标目录,镜像时间更新也不能单独证明其中的内容正确;是否能够开机自动运行,仍需启动 QEMU 验证。
如果脚本比较不一致,检查已有 overlay 或后处理脚本是否覆盖了/etc/init.d/S99debug-demo。也不要让 overlay 中的旧程序覆盖/usr/bin/debug_demo。
八、启动 QEMU,先验证自动运行
确认没有其他 QEMU 使用同一镜像或占用本机端口5000。在Ubuntu 主机执行:
/usr/bin/qemu-system-aarch64\-Mvirt\-cpucortex-a53\-smp2\-m1G\-nographic\-kernel"${DEMO_OUT:?请先设置 DEMO_OUT}/images/Image"\-append"rootwait root=/dev/vda console=ttyAMA0"\-drivefile="$DEMO_OUT/images/rootfs.ext2",if=none,format=raw,id=hd0\-devicevirtio-blk-device,drive=hd0\-netdevuser,id=net0,hostfwd=tcp:127.0.0.1:5000-:2345\-devicevirtio-net-device,netdev=net0端口转发只是保留前文的调试入口,本篇启动服务不依赖它。无需运行 gdbserver。
以root登录后,先不要执行start,也不要手动运行--service。直接在QEMU 目标端检查:
uname-mreadlink /proc/1/exe start-stop-daemon--help/etc/init.d/S99debug-demo statusecho$?cat/var/run/debug-demo.pidps|grep'[d]ebug_demo'tail-n5/var/log/debug-demo.log本次登录后按上述步骤检查,实际记录如下。为便于阅读,省略了较长的start-stop-daemon --help输出:本次目标端为 BusyBox v1.37.0,帮助信息确认支持脚本使用的-S、-K、-b、-m、-p、-x和-s选项。
# uname -m aarch64 # readlink /proc/1/exe /bin/busybox # /etc/init.d/S99debug-demo status debug-demo running (pid 121) # echo $? 0 # cat /var/run/debug-demo.pid 121 # ps | grep '[d]ebug_demo' 121 root /usr/bin/debug_demo --service # tail -n 5 /var/log/debug-demo.log debug-demo service: pid=121 tick=12 total=100 debug-demo service: pid=121 tick=13 total=100 debug-demo service: pid=121 tick=14 total=100 debug-demo service: pid=121 tick=15 total=100 debug-demo service: pid=121 tick=16 total=100这份记录确认了以下结果:
- 系统架构为
aarch64,PID 1 运行的是/bin/busybox。 - 服务状态查询返回
0,状态输出、PID 文件和进程列表中的 PID 均为121。 - 进程以
/usr/bin/debug_demo --service运行,列表中显示一个服务实例;对应日志的tick从12连续递增到16,求和结果均为100。
结合本节登录后未手动启动应用的操作过程,这说明本次开机后服务已自动运行并多次写入日志。PID 和 tick 是本次运行值,不是其他实验必须一致的固定值。
这次记录只展示了一次tail的结果。过几秒再次执行tail -n 5 /var/log/debug-demo.log,还应确认当前 PID 的tick继续增长,以验证后续仍在运行。重复启动、停止和重启行为继续在下一节检查,不能由这份记录一并判定通过。
九、验证重复启动、停止、重启和兼容性
以下命令都在QEMU 目标端执行,每组执行后先核对结果,再继续。
1. 重复 start,不增加实例
before_pid=$(cat/var/run/debug-demo.pid)/etc/init.d/S99debug-demo startecho$?after_pid=$(cat/var/run/debug-demo.pid)test"$before_pid"="$after_pid"echo$?ps|grep'[d]ebug_demo'本次重复执行start的实际记录如下:
# before_pid=$(cat /var/run/debug-demo.pid) # /etc/init.d/S99debug-demo start debug-demo already running (pid 121) # echo $? 0 # after_pid=$(cat /var/run/debug-demo.pid) # test "$before_pid" = "$after_pid" # echo $? 0 # ps | grep '[d]ebug_demo' 121 root /usr/bin/debug_demo --service脚本提示服务已运行并返回0,启动前后的 PID 比较也返回0,进程列表仍只有 PID 为121的一个服务实例。这说明本次顺序重复启动检查通过,没有产生额外实例。PID 相同与实例数量需要一起检查;这个结果不代表同时调用多个start的并发场景也已验证。
2. stop 后,进程退出且日志停止增长
/etc/init.d/S99debug-demo stopecho$?/etc/init.d/S99debug-demo statusecho$?test!-e/var/run/debug-demo.pidecho$?ps|grep'[d]ebug_demo'tail-n5/var/log/debug-demo.log本次停止服务的实际记录如下:
# /etc/init.d/S99debug-demo stop stopped /usr/bin/debug_demo (pid 121) debug-demo stopped # echo $? 0 # /etc/init.d/S99debug-demo status debug-demo not running # echo $? 1 # test ! -e /var/run/debug-demo.pid # echo $? 0 # ps | grep '[d]ebug_demo' # tail -n 5 /var/log/debug-demo.log debug-demo service: pid=121 tick=136 total=100 debug-demo service: pid=121 tick=137 total=100 debug-demo service: pid=121 tick=138 total=100 debug-demo service: pid=121 tick=139 total=100 debug-demo service: pid=121 stopped停止命令返回0,随后状态查询显示未运行,PID 文件不存在的检查返回0,进程查询也没有输出。日志最后一行为 PID121的stopped,说明程序执行了退出日志逻辑,结合进程检查可确认本次服务已停止。
这份记录只展示了停止后的一次日志查看。继续下一步启动服务前,过几秒再执行tail -n 5 /var/log/debug-demo.log,确认末尾仍为同一条stopped,补充验证停止后日志不再增长。
此处status返回1是脚本定义的“未运行”,不是停止失败。它只是本例约定,不是完整的 LSB 服务状态码实现。
3. 再次 start,并测试 restart
/etc/init.d/S99debug-demo startecho$?/etc/init.d/S99debug-demo restartecho$?/etc/init.d/S99debug-demo statusecho$?cat/var/run/debug-demo.pidps|grep'[d]ebug_demo'tail-n8/var/log/debug-demo.log本次再次启动并执行restart的实际记录如下:
# /etc/init.d/S99debug-demo start debug-demo started (pid 161) # echo $? 0 # /etc/init.d/S99debug-demo restart stopped /usr/bin/debug_demo (pid 161) debug-demo stopped debug-demo started (pid 175) # echo $? 0 # /etc/init.d/S99debug-demo status debug-demo running (pid 175) # echo $? 0 # cat /var/run/debug-demo.pid 175 # ps | grep '[d]ebug_demo' 175 root /usr/bin/debug_demo --service # tail -n 8 /var/log/debug-demo.log debug-demo service: pid=121 tick=137 total=100 debug-demo service: pid=121 tick=138 total=100 debug-demo service: pid=121 tick=139 total=100 debug-demo service: pid=121 stopped debug-demo service: pid=161 tick=0 total=100 debug-demo service: pid=161 stopped debug-demo service: pid=175 tick=0 total=100 debug-demo service: pid=175 tick=1 total=100start、restart和随后的status均返回0。再次启动时的 PID 为161;执行restart后,该实例停止,新实例以 PID175运行。状态输出、PID 文件和进程列表一致,最后只显示一个服务实例。
日志记录了 PID161从tick=0开始写入并停止,以及 PID175从tick=0重新计数并增长到1,确认本次再次启动和服务重启检查通过。前面的 PID121记录是追加保留的历史日志,不代表旧进程仍在运行。PID 以实际运行结果为准,不要求与本文一致;这里验证的是应用服务重启,系统重启后自动运行仍需第五小节验证。
4. 不带参数仍保留原来的功能
/usr/bin/debug_demoecho$?本次不带参数运行的实际结果如下:
# /usr/bin/debug_demo debug-demo package v2: total = 100 # echo $? 0程序仍输出原来的v2求和结果,并以状态码0退出,说明新增--service模式后,无参数运行方式仍然可用。这次运行是短暂的前台求和程序,不是额外的常驻服务。
5. 正常重启后再次自动运行
先保留本轮日志与终端记录,再在QEMU 目标端的#提示符下执行,不要在 Ubuntu 主机执行:
reboot第一步:根据终端画面,判断是否需要重新启动 QEMU。
- 如果出现新的 Linux 启动信息,随后显示
login:,说明目标系统已在原 QEMU 中重新启动。输入root登录,直接进入第二步,不要再执行 QEMU 启动命令。 - 如果 QEMU 已退出,回到 Ubuntu 的
embed@embedtest:...$提示符,才在Ubuntu 主机执行下面的命令,重新启动同一镜像。若此前使用了其他输出目录,请把DEMO_OUT设为此前实际使用的目录。
exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"/usr/bin/qemu-system-aarch64\-Mvirt\-cpucortex-a53\-smp2\-m1G\-nographic\-kernel"${DEMO_OUT:?请先设置 DEMO_OUT}/images/Image"\-append"rootwait root=/dev/vda console=ttyAMA0"\-drivefile="$DEMO_OUT/images/rootfs.ext2",if=none,format=raw,id=hd0\-devicevirtio-blk-device,drive=hd0\-netdevuser,id=net0,hostfwd=tcp:127.0.0.1:5000-:2345\-devicevirtio-net-device,netdev=net0启动后在login:处输入root。如果画面停住,既没有登录提示,也没有回到 Ubuntu 提示符,先保留最后的输出排查,不要另开一个使用同一可写镜像的 QEMU。这一步不需要重新编译或生成镜像。
第二步:登录后直接检查,不手动启动应用。
不要执行/etc/init.d/S99debug-demo start,也不要执行/usr/bin/debug_demo --service,否则无法用这次检查判断是否为开机自动运行。在QEMU 目标端执行:
/etc/init.d/S99debug-demo statusecho$?cat/var/run/debug-demo.pidps|grep'[d]ebug_demo'tail-n5/var/log/debug-demo.log本次重启后登录,未手动启动应用,第二步的实际检查结果如下:
# /etc/init.d/S99debug-demo status debug-demo running (pid 121) # echo $? 0 # cat /var/run/debug-demo.pid 121 # ps | grep '[d]ebug_demo' 121 root /usr/bin/debug_demo --service # tail -n 5 /var/log/debug-demo.log debug-demo service: pid=121 tick=114 total=100 debug-demo service: pid=121 tick=115 total=100 debug-demo service: pid=121 tick=116 total=100 debug-demo service: pid=121 tick=117 total=100 debug-demo service: pid=121 tick=118 total=100状态查询返回0,状态输出、PID 文件和进程列表中的 PID 均为121,且只显示一个服务实例。对应日志包含连续的tick=114至118,求和结果均为100。结合本次重启后未手动启动应用的操作过程,说明服务已再次自动运行;下一步继续通过间隔查看日志,确认它仍在输出。
本次 PID 与首次开机时同为121是正常现象:系统重新启动后可能分配相同的进程编号,不能只靠 PID 是否变化判断系统是否重启。
第三步:间隔查看日志,确认程序仍在运行。
过几秒后,在QEMU 目标端再次执行:
tail-n5/var/log/debug-demo.log比较两次输出,确认当前服务 PID 对应的tick继续增长,total仍为100。保存重启过程、状态检查和两次日志输出,作为本项实测记录;若服务未运行,先保留失败输出,不要手动启动后将其记为自启动验证通过。
本次间隔查看日志的实际结果如下:
# tail -n 5 /var/log/debug-demo.log debug-demo service: pid=121 tick=152 total=100 debug-demo service: pid=121 tick=153 total=100 debug-demo service: pid=121 tick=154 total=100 debug-demo service: pid=121 tick=155 total=100 debug-demo service: pid=121 tick=156 total=100与第二步相比,日志中的 PID 仍为121,最新tick从118增长到156,total始终为100。结合重启后未手动启动应用、状态查询返回0及进程检查结果,本次系统重启后自动运行和日志继续增长的验证通过;这不代表已完成长期稳定性测试。
默认/var/log可能是易失目录,不要求重启前后的日志都保留;本次要确认的是新一轮启动后服务存在,并在写入新的日志。
十、遇到问题先查这些
| 现象 | 优先检查 |
|---|---|
| 启动停住,迟迟没有登录提示 | 是否直接在启动脚本前台执行了无限循环程序;是否漏了后台启动选项 |
| 镜像里没有启动脚本 | 是否启用 BusyBox init;安装接口是否位于generic-package之前;是否重建包及镜像 |
Permission denied或not found | 脚本是否为 0755、LF 换行;首行是否为#!/bin/sh;程序及动态加载器是否存在 |
提示不认识-b或-m | 目标 BusyBox 是否启用对应扩展功能;不要拿 Ubuntu 主机工具的参数能力作判断 |
status未运行、没有日志 | 检查/var/log是否可写、空间是否耗尽、程序是否为新版本,先看启动错误 |
start成功却不再更新日志 | 启动检查不是持续健康检查;重新检查进程和实际日志,程序可能之后退出 |
| 改完源码仍是旧行为 | 是否始终使用相同O=,是否完成包重建和镜像生成,QEMU 是否启动了旧镜像 |
| 关机没有执行自定义停止逻辑 | 核对目标/etc/inittab的 shutdown 项、rcK和脚本权限,是否被 overlay 改写 |
启动失败时,可以先通过脚本停止服务并确认没有残留实例,再在目标端前台运行/usr/bin/debug_demo --service。此时初始化错误会显示在终端,正常运行的日志仍写入文件;检查后用Ctrl+C退出。不要在服务已经运行时直接再启动一个实例。
如果改过 BusyBox 自身配置,仅保存 Buildroot defconfig 不够,还需保存并引用自定义 BusyBox 配置。本文主线假定这些工具已经存在,没有替你完成这项配置迁移。
十一、小结
这一篇把程序从“登录后手动运行”,推进到“系统开机自动运行”:
源码提供持续运行模式 + 启动脚本管理进程 + 软件包安装程序和脚本 + QEMU 验证启动、停止及重启需要保留的仍是外部目录中的实际文件,而不只是镜像或 defconfig。本篇修改源码、修改包规则,并新增启动脚本,没有引入 systemd 或新的硬件要求。
本次实验验证了开机自动运行、顺序重复启动不增加实例、停止与重新启动、无参数运行兼容性,以及系统重启后的自动运行和日志更新。这个脚本适合入门实验,但还不具备异常退出后自动恢复、日志轮转等长期运行能力。
下一步才是更接近长期运行设备的问题:程序异常退出如何恢复,日志怎样限额,以及服务如何以更小权限运行。本篇完成了从软件包集成到开机运行、状态查询、停止及重启的基础实践,实测结果与验证边界见前文。
参考资料
- Buildroot 2025.02.8 通用软件包接口:
INSTALL_INIT_SYSV、包目录变量及安装流程。 - Buildroot 2025.02.8 BusyBox inittab:启动及关机阶段调用入口。
- Buildroot 2025.02.8 rcS 与 rcK:启动脚本调用顺序和参数。