尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

嵌入式后台开发:守护进程制作与程序开机自启

嵌入式后台开发:守护进程制作与程序开机自启
📅 发布时间:2026/7/22 17:52:06

嵌入式后台开发:守护进程制作与程序开机自启

一、引言

前面九篇文章,我们的示例代码都是前台运行的程序——启动后在终端里打印日志,Ctrl+C 退出。但在嵌入式设备上,传感器采集服务、网络通信服务、MQTT 消息处理服务都不是这么玩的。

这些服务需要脱离终端独立运行、开机自动启动、崩溃后自动重启、可以优雅停止。这就是**守护进程(Daemon)**的用武之地。

本文先拆解守护进程的原理和创建步骤,再介绍三种主流的开机自启配置方式,帮你把前面的劳动成果——不管是文件 IO、网络服务器还是 MQTT 客户端——都做成真正的"企业级后台服务"。

二、守护进程是什么?

守护进程(Daemon)是在后台运行、不受终端控制的长期服务进程。它的名字来源于古希腊神话中的"守护精灵"。

和普通进程的核心区别:

特性普通进程守护进程
终端关联关联,终端关闭=进程退出脱离,终端关闭不影响
运行位置前台或后台纯后台
输出目标标准输出/标准错误日志文件或 syslog
父进程shell 或用户手动启动init/systemd(PID=1)
工作目录启动时的目录通常是根目录/

一句话:守护进程就是"不需要人来盯着的后台服务"——拿嵌入式传感器采集来说,你不可能每次断电重启后手动 SSH 上开发板启动程序。

三、制作守护进程的五步标准流程

Richard Stevens 在《Unix 环境高级编程》中定义了守护进程创建的经典步骤,被业界奉为"守护进程圣经"。

步骤一:fork 并退出父进程

pid_tpid=fork();if(pid>0){exit(0);// 父进程退出}// 子进程继续

目的:让 shell 认为程序已经结束,释放终端。

步骤二:创建新会话(setsid)

if(setsid()<0){exit(1);}

sessid()让调用进程成为新会话和新进程组的首领,彻底切断与旧终端的一切关联。

步骤三:再次 fork(可选但推荐)

pid=fork();if(pid>0)exit(0);

第二次 fork 后的孙子进程不再是会话首领,永远不会意外获得控制终端。

步骤四:改变工作目录到根目录

chdir("/");

如果不改,进程启动时的目录(可能是挂载点)将无法卸载。

步骤五:关闭不需要的文件描述符,重定向标准 IO

close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 重定向到 /dev/nullintfd=open("/dev/null",O_RDWR);dup2(fd,STDIN_FILENO);dup2(fd,STDOUT_FILENO);dup2(fd,STDERR_FILENO);if(fd>2)close(fd);

重要:守护进程不应该往 stdout/stderr 输出——因为没有终端能接收了。日志要通过 syslog 或文件记录。

完整创建函数

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<fcntl.h>#include<sys/stat.h>voiddaemonize(void){pid_tpid;// Step 1: fork, 父进程退出pid=fork();if(pid<0){perror("fork");exit(1);}if(pid>0){exit(0);// 父进程退出,子进程变孤儿}// Step 2: 创建新会话if(setsid()<0){perror("setsid");exit(1);}// Step 3: 再次 fork,孙进程不再是会话首领pid=fork();if(pid<0){perror("fork#2");exit(1);}if(pid>0){exit(0);// 第二次父进程退出}// Step 4: 切换到根目录chdir("/");// Step 5: 重设文件掩码umask(0);// Step 6: 关闭所有文件描述符intmax_fd=sysconf(_SC_OPEN_MAX);for(intfd=0;fd<max_fd;fd++){close(fd);}// Step 7: 重定向标准 IO 到 /dev/nullintdev_null=open("/dev/null",O_RDWR);if(dev_null>=0){dup2(dev_null,STDIN_FILENO);dup2(dev_null,STDOUT_FILENO);dup2(dev_null,STDERR_FILENO);if(dev_null>2)close(dev_null);}// Step 8: 记录 PID 文件(便于后续管理)FILE*fp=fopen("/var/run/mydaemon.pid","w");if(fp){fprintf(fp,"%d\n",getpid());fclose(fp);}}

使用示例:把网络服务器做成守护进程

#include<signal.h>staticvolatileintg_running=1;staticvoidsig_handler(intsigno){if(signo==SIGTERM||signo==SIGINT){g_running=0;// 优雅退出}}intmain(intargc,char*argv[]){// 安装信号处理器signal(SIGTERM,sig_handler);signal(SIGINT,sig_handler);// 变成守护进程if(argc<2||strcmp(argv[1],"-f")!=0){daemonize();}// 打开日志文件(守护进程后 stdout 已经没用)FILE*log_fp=fopen("/var/log/mydaemon.log","a");fprintf(log_fp,"[启动] PID=%d\n",getpid());fflush(log_fp);// 启动 TCP 服务 (省略了具体的 epoll 实现)intserver_fd=tcp_server_init(8888);fprintf(log_fp,"[就绪] 端口 8888\n");fflush(log_fp);// 主循环while(g_running){event_loop_iteration(server_fd);}// 清理fprintf(log_fp,"[退出] 服务正常关闭\n");fflush(log_fp);close(server_fd);fclose(log_fp);unlink("/var/run/mydaemon.pid");return0;}

-f参数(foreground)是守护进程开发的标配——开发调试时用-f跑在前台看日志,发布后去掉-f跑在后台。

四、PID 文件机制

PID 文件是守护进程的"身份证",记录了当前运行的进程 ID,用于管理脚本的启停控制:

#!/bin/sh# /etc/init.d/mydaemon 启动/停止脚本PID_FILE="/var/run/mydaemon.pid"APP="/usr/local/bin/mydaemon"start(){echo"Starting mydaemon..."$APPsleep1if[-f"$PID_FILE"];thenecho"OK, PID=$(cat$PID_FILE)"elseecho"Failed"fi}stop(){if[-f"$PID_FILE"];thenPID=$(cat"$PID_FILE")echo"Stopping mydaemon (PID=$PID)..."kill$PIDrm-f"$PID_FILE"elseecho"Not running"fi}case"$1"instart)start;;stop)stop;;restart)stop;start;;*)echo"Usage:$0{start|stop|restart}";;esac

五、开机自启的三种方式

方式一:/etc/rc.local(最简单)

在/etc/rc.local的exit 0之前添加:

/usr/local/bin/mydaemon&

优点:零学习成本,改完即生效
缺点:不支持崩溃重启,没有进程监控

方式二:SysV init 脚本(兼容性最好)

将启动脚本放到/etc/init.d/,再创建软链接:

sudocpmydaemon_init.sh /etc/init.d/mydaemonsudochmod+x /etc/init.d/mydaemonsudoupdate-rc.d mydaemon defaults9901

参数含义:

  • defaults:使用默认的启动/停止顺序
  • 99:启动优先级(数字越大越晚启动)
  • 01:停止优先级(数字越小越早停止)

优点:兼容所有老式嵌入式 Linux 系统
缺点:配置较繁琐

方式三:systemd 服务文件(现代标配)

创建/etc/systemd/system/mydaemon.service:

[Unit] Description=嵌入式传感器服务 After=network.target [Service] Type=forking PIDFile=/var/run/mydaemon.pid ExecStart=/usr/local/bin/mydaemon ExecStop=/bin/kill -TERM $MAINPID Restart=always RestartSec=5 StandardOutput=file:/var/log/mydaemon.log StandardError=file:/var/log/mydaemon_error.log [Install] WantedBy=multi-user.target

启用并启动:

sudosystemctl daemon-reloadsudosystemctlenablemydaemon# 开机自启sudosystemctl start mydaemon# 立即启动sudosystemctl status mydaemon# 查看状态sudosystemctl stop mydaemon# 停止

systemd 关键字段解析:

字段含义
Type=forking程序自己 fork 成守护进程后才返回
PIDFilesystemd 通过这个文件跟踪进程
Restart=always不管什么原因退出都自动重启
RestartSec=5重启前等待 5 秒(防止 crash loop)
WantedBy=multi-user.target在多用户模式(正常开机)时启动

三种方式选型指南

场景推荐方式
开发调试-f前台模式
老系统(BusyBox init)/etc/rc.local
Debian/Ubuntu 旧版SysV init 脚本
嵌入式 Linux(systemd)systemd service
需要崩溃自动重启systemd(Restart=always)

六、常见问题排查

现象原因排查方法
启动后立即退出fork失败或setsid失败前台模式(-f)调试
日志文件为空未刷新缓冲区每次写日志后fflush或fsync
systemd 显示 activating程序没有返回(没有 fork 完)检查Type=forking是否匹配
重启循环(crash loop)程序启动后就崩溃RestartSec调大,查看错误日志
两个实例同时运行PID 文件残留启动前检查 PID 文件并确认进程是否存在

七、守护进程调试技巧

调试守护进程最大的痛点是"看不到 stdout"。解决方法:

// 开发阶段:不 daemonize,直接用 printfif(getenv("FOREGROUND")==NULL){daemonize();}// 运行时$ FOREGROUND=1./mydaemon// 前台调试$./mydaemon// 后台运行

或者用 syslog:

#include<syslog.h>openlog("mydaemon",LOG_PID,LOG_DAEMON);syslog(LOG_INFO,"传感器服务启动");syslog(LOG_ERR,"传感器初始化失败: %s",strerror(errno));

然后用tail -f /var/log/syslog或journalctl -fu mydaemon查看。

八、总结

守护进程制作的核心步骤一个都不能少:

  1. fork + exit 父进程— 让 shell 以为程序结束
  2. setsid— 成为新会话首领,脱离终端
  3. 二次 fork— 彻底杜绝意外获得控制终端
  4. **chdir(“/”) ** — 释放工作目录
  5. 关闭 fd + 重定向到 /dev/null— 防止后台输出干扰
  6. PID 文件— 管理脚本的"身份证"
  7. signal 处理器— 优雅停止,不丢数据

开机自启选择:老系统用rc.local,新系统用systemd service,需要崩溃重启能力果断上 systemd。

把前面九篇文章的任何一段代码——不管是文件 IO、串口采集还是 epoll 服务器——套进daemonize()函数,它就成了一名合格的"嵌入式后台打工人"。

相关新闻

  • Tack高级配置:深入理解Terraform模块化架构设计
  • 3大场景深度解析:Sandboxie启动故障的实战排查与解决方案
  • CST · FDTD · COMSOL 携手AI:超越单一工具的智能设计方法!

最新新闻

  • TI定时器OCP接口与中断机制深度解析:从Posted模式到实战避坑
  • 嵌入式硬件接口核心:寄存器、DMA与地址转换深度解析
  • 阿里云国际版折扣和免绑卡开户:充值最高返 25%
  • n8n工作流状态管理变更解析与操作指南
  • NativeFiatTokenV2_2深度剖析:stablecoin-evm原生代币实现原理
  • 合肥人 2026 年的断舍离,从易奢福开始:5 区 70 店全品类通收,从梵克雅宝到蒂芙尼,带盒更贵 - 易奢福

日新闻

  • AI云原生实战05-金融AI上云最难的不是技术,是“不出事“——TCE银行风控架构拆解
  • 2026年GEOSEO优化公司选型深度测评:五大硬核标准严选,这六家重塑搜索增长新格局 - 品牌前沿专家
  • **核验!2026年7月卡地亚香港**售后网点地址及服务电话公告 - 卡地亚服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号