news 2026/11/5 7:51:49

Buildroot 开机自启动:让你的 ARM64 程序随 Linux 启动运行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Buildroot 开机自启动:让你的 ARM64 程序随 Linux 启动运行

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 在后台定期写入日志

最终检查四件事:

  1. 开机后不手动启动应用,就能查到服务进程及其日志。
  2. 再执行一次start,不会重复启动实例。
  3. 可以执行stop、start、restart和status。
  4. 正常重启系统后,程序再次自动运行。

开机自启动不等于进程守护。本文的脚本负责启动和停止,不会在程序崩溃后自动拉起它,也不提供健康检查或故障恢复。

二、沿用新输出目录,先备份文件

项目本文使用的环境
开发主机Ubuntu 20.04 x86_64 虚拟机
Buildroot2025.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 start

Buildroot 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.ext2

stripped表示目标程序的调试符号已被剥除,不影响正常运行。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=100

start、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:启动脚本调用顺序和参数。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 7:42:01

Horizon MCP集成教程:将新闻处理能力接入你的AI助手生态

Horizon MCP集成教程&#xff1a;将新闻处理能力接入你的AI助手生态 【免费下载链接】Horizon &#x1f4e1; Your own AI-powered news radar. Generates daily briefings in English & Chinese. | 用 AI 构建你专属的新闻雷达 项目地址: https://gitcode.com/GitHub_Tr…

作者头像 李华
网站建设 2026/11/5 7:38:57

零基础实现IEC61850(三)报告服务端

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 7:36:46

ADS122U04+dsPIC33EP实现PT100/PT1000高精度测温方案详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 7:36:23

基于ADS122U04与STM32F446ZE的RTD高精度温度测量方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 7:35:39

ADS122U04与STM32F410RB工业温度采集方案:UART接口ADC实战详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华