UNIX下C语言编程与实践35-UNIX 守护进程编写:后台执行、脱离终端、清除掩码与信号处理
从核心步骤到完整实例,掌握 UNIX 守护进程的手工编写方法
一、编写守护进程的核心原则
UNIX 守护进程的本质是“脱离终端控制、长期后台稳定运行”,因此编写时需围绕以下核心原则设计流程:
- 脱离终端:切断与所有控制终端的关联,避免终端关闭或用户操作导致进程终止;
- 独立会话:创建新的进程会话和进程组,避免受原会话信号(如 SIGHUP)影响;
- 资源可控:清除文件创建掩码、关闭无用文件描述符,确保资源使用符合预期;
- 信号安全:处理或忽略关键信号(如 SIGCHLD 预防僵死进程、SIGINT 避免终端中断);
- 日志可追溯:将输出重定向到文件,避免日志丢失,便于故障排查。
基于这些原则,手工编写守护进程需遵循固定的五步流程:创建子进程→父进程退出→创建新会话→清除掩码与关闭文件→信号处理与业务逻辑。以下将逐步解析每个步骤的实现与原理。
二、编写守护进程的五步核心流程
手工编写守护进程的流程具有固定范式,每一步均解决特定问题,缺一不可。以下结合代码片段,详细讲解每个步骤的实现方法与作用。
步骤 1:创建子进程,实现后台执行
核心目的:通过 fork
创建子进程,父进程立即退出,子进程由 init 进程(PID=1)收养,实现“后台运行”和“脱离原父进程控制”。
原理:父进程退出后,子进程成为“孤儿进程”,被 init 进程收养;同时,子进程脱离原终端会话,为后续“完全脱离终端”奠定基础。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {// 步骤 1:创建子进程,父进程退出pid_t pid = fork();if (pid == -1) {perror("fork 失败");exit(EXIT_FAILURE);}if (pid > 0) {// 父进程:创建子进程后立即退出,子进程由 init 托管printf("父进程 PID = %d,创建子进程 PID = %d,父进程退出\n", getpid(), pid);exit(EXIT_SUCCESS);}// 后续代码仅子进程执行printf("子进程 PID = %d,父进程已退出,开始初始化守护进程\n", getpid());// 后续步骤...return EXIT_SUCCESS;
}
关键注意:父进程必须调用 exit
而非 return
,确保父进程彻底终止,避免残留资源影响子进程。
步骤 2:调用 setsid,脱离控制终端
核心目的:通过 setsid
函数创建新的进程会话(Session)和进程组,使子进程完全脱离原终端的控制,成为“无终端进程”。
原理:
setsid
函数会创建新会话,子进程成为新会话的“会话组长”和新进程组的“组长进程”;- 新会话无关联的控制终端,即使原终端关闭,子进程也不会收到
SIGHUP
等终端信号; - 这一步是“脱离终端”的核心,确保守护进程不依赖任何终端运行。
// 接步骤 1 代码
#include <sys/types.h>
#include <sys/stat.h>// 步骤 2:调用 setsid,创建新会话,脱离终端
pid_t sid = setsid();
if (sid == -1) {perror("setsid 失败");exit(EXIT_FAILURE);
}
printf("子进程创建新会话,会话 ID = %d,此时终端(TTY)已脱离\n", sid);// 验证:查看当前进程的终端(应显示为 ?)
system("ps -eo pid,tty,cmd | grep -w %d | grep -v grep", getpid());
子进程创建新会话,会话 ID = 1234,此时终端(TTY)已脱离 1234 ? ./daemon_init
关键注意:setsid
仅能在“非进程组组长”的进程中调用,步骤 1 中父进程退出后,子进程必然不是进程组组长,因此可安全调用 setsid
。
步骤 3:清除文件创建掩码(umask)
核心目的:调用 umask(0)
清除文件创建掩码,确保守护进程创建文件或目录时,权限符合预期(不受父进程掩码影响)。
原理:
- 文件创建掩码(umask)用于限制新创建文件的默认权限(如默认 umask 为 022 时,新文件权限为 644 = 666 - 022);
- 父进程的 umask 会被子进程继承,若父进程 umask 限制过严(如 077),会导致守护进程创建的文件无法被其他用户访问;
umask(0)
清除掩码,使守护进程创建文件时默认权限为 666(文件)或 777(目录),后续可通过chmod
按需调整权限。
代码格式化
// 接步骤 2 代码
// 步骤 3:清除文件创建掩码
umask(0);
printf("已清除文件创建掩码,当前 umask 值为 %d\n", umask(0));// 验证:再次调用 umask(0) 输出当前值
// 验证:创建测试文件,查看权限(应为 666,即 -rw-rw-rw-)
FILE *test_file = fopen("/tmp/daemon_test.txt", "w");
if (test_file != NULL) {fclose(test_file);system("ls -l /tmp/daemon_test.txt | awk '{print $1, $9}'");remove("/tmp/daemon_test.txt"); // 清理测试文件
}
输出结果
已清除文件创建掩码,当前 umask 值为 0
-rw-rw-rw- /tmp/daemon_test.txt
关键注意:umask(0)
仅影响当前进程及后续创建的子进程,不会修改系统或父进程的 umask,安全性可控。
步骤 4:关闭无用文件描述符
核心目的:关闭子进程从父进程继承的无用文件描述符(如标准输入 stdin、标准输出 stdout、标准错误 stderr),避免资源泄漏和终端关联。
原理:
- 子进程会继承父进程的所有打开文件描述符(默认包括 stdin=0、stdout=1、stderr=2);
- 这些文件描述符关联原终端,若不关闭,即使调用
setsid
,守护进程仍可能通过这些描述符与终端交互,且会占用系统文件描述符资源; - 关闭后,可将 stdout 和 stderr 重定向到日志文件,确保输出不丢失。
#include <fcntl.h>// 步骤 4:关闭标准输入、输出、错误描述符
close(STDIN_FILENO); // 关闭 stdin (0)
close(STDOUT_FILENO); // 关闭 stdout (1)
close(STDERR_FILENO); // 关闭 stderr (2)
printf("已关闭标准输入、输出、错误描述符\n"); // 此句无输出,因 stdout 已关闭// (可选)将 stdout/stderr 重定向到日志文件,避免输出丢失
int log_fd = open("/var/log/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd != -1) {// 复制 log_fd 到 stdout 和 stderrdup2(log_fd, STDOUT_FILENO);dup2(log_fd, STDERR_FILENO);close(log_fd);printf("守护进程日志将输出到 /var/log/daemon.log\n"); // 此句写入日志文件
}
// 查看日志文件内容 cat /var/log/daemon.log 守护进程日志将输出到 /var/log/daemon.log
关键注意:关闭文件描述符后,若需输出日志,必须重定向到文件或系统日志(如 syslog
),否则所有输出会丢失。
步骤 5:信号处理,确保稳定运行
核心目的:注册信号处理函数,忽略终端相关信号(如 SIGINT、SIGHUP),处理子进程终止信号(SIGCHLD),预防僵死进程,确保守护进程稳定运行。
需处理的关键信号:
SIGCHLD
:子进程终止时发送,需调用waitpid
回收,预防僵死进程;SIGINT
(Ctrl+C)、SIGTERM
(终止信号):忽略或优雅处理,避免守护进程被意外终止;SIGHUP
(终端挂起):忽略,避免原终端关闭时影响守护进程。
守护进程信号处理代码解析
该代码片段展示了守护进程中信号处理的关键实现,包括子进程回收、优雅退出和信号忽略机制。以下是核心功能点和优化建议:
信号处理函数设计
函数通过switch-case
结构处理多种信号:
- SIGCHLD:使用
waitpid(-1, NULL, WNOHANG)
非阻塞回收所有终止子进程,防止僵死进程产生,同时打印日志通知。 - SIGINT/SIGTERM:触发优雅退出流程,打印终止日志后调用
exit(EXIT_SUCCESS)
,注释提示可在此处扩展资源清理逻辑。 - SIGHUP:仅打印日志并忽略该信号,确保终端挂起不影响守护进程运行。
信号注册实现
struct sigaction sa;
sa.sa_handler = signal_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGCHLD, &sa, NULL) == -1 || sigaction(SIGINT, &sa, NULL) == -1 ||sigaction(SIGTERM, &sa, NULL) == -1 || sigaction(SIGHUP, &sa, NULL) == -1) {perror("sigaction注册失败");exit(EXIT_FAILURE);
}
- 使用
sigaction
替代过时的signal
函数,提供更精确的信号控制。 sigemptyset
清空信号掩码,避免信号阻塞。- 错误检查涵盖所有注册信号,失败时立即终止进程。
业务逻辑模拟
while (1) {printf("守护进程运行中,PID = %d\n", getpid());sleep(5);
}
- 简易循环模拟守护进程持续运行,实际应用中需替换为具体任务逻辑。
- 建议将日志输出重定向至系统日志文件(如
/var/log/daemon.log
而非直接打印到标准输出)。
优化建议
- 日志系统:使用
syslog
替代printf
,确保日志持久化且符合系统管理规范。 - 资源清理:在
SIGINT/SIGTERM
处理分支补充文件描述符关闭、共享内存释放等逻辑。 - 信号安全:避免在信号处理函数中调用非异步安全函数(如
printf
),可使用write
替代。 - PID文件:增加对PID文件的创建/删除管理,防止多实例冲突。
典型应用场景
- 网络服务守护进程(如HTTP服务器)
- 定时任务监控进程
- 系统级后台服务(如日志收集器)
通过该模式可构建健壮的守护进程,正确处理系统信号和子进程生命周期。
关键注意:信号处理函数中需使用 while (waitpid(-1, NULL, WNOHANG))
循环回收子进程,避免多个子进程同时终止时仅回收一个,导致僵死。
三、完整实例:编写一个定时日志守护进程
结合上述五步流程,编写一个完整的守护进程实例——timed_log_daemon
,功能为“每 3 秒向日志文件写入当前时间,支持优雅退出和信号处理”,并演示其启动、运行和停止过程。
完整代码实现
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#include <string.h>#define LOG_FILE "/var/log/timed_log_daemon.log" // 日志文件路径
#define PID_FILE "/var/run/timed_log_daemon.pid" // PID 文件路径(用于管理守护进程)// 信号处理函数:优雅退出与子进程回收
void signal_handler(int sig) {switch (sig) {case SIGCHLD:// 回收子进程,预防僵死while (waitpid(-1, NULL, WNOHANG) > 0);break;case SIGINT:case SIGTERM:// 优雅退出:删除 PID 文件,关闭日志printf("[%s] 收到终止信号,守护进程退出\n", __func__);remove(PID_FILE); // 删除 PID 文件exit(EXIT_SUCCESS);break;case SIGHUP:// 忽略 SIGHUP 信号printf("[%s] 收到 SIGHUP 信号,已忽略\n", __func__);break;default:break;}
}// 初始化守护进程:五步流程
void daemon_init() {pid_t pid;// 步骤 1:创建子进程,父进程退出pid = fork();if (pid == -1) {perror("fork 失败");exit(EXIT_FAILURE);}if (pid > 0) {printf("父进程 PID = %d,子进程 PID = %d,父进程退出\n", getpid(), pid);exit(EXIT_SUCCESS);}// 步骤 2:调用 setsid,脱离终端if (setsid() == -1) {perror("setsid 失败");exit(EXIT_FAILURE);}// 步骤 3:清除文件创建掩码umask(0);// 步骤 4:关闭标准文件描述符,重定向日志close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 重定向 stdout/stderr 到日志文件int log_fd = open(LOG_FILE, O_WRONLY | O_CREAT | O_APPEND, 0644);if (log_fd == -1) {perror("打开日志文件失败");exit(EXIT_FAILURE);}dup2(log_fd, STDOUT_FILENO);dup2(log_fd, STDERR_FILENO);close(log_fd);// 步骤 5:注册信号处理函数struct sigaction sa;sa.sa_handler = signal_handler;sa.sa_flags = 0;sigemptyset(&sa.sa_mask);if (sigaction(SIGCHLD, &sa, NULL) == -1 || sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1 || sigaction(SIGHUP, &sa, NULL) == -1) {perror("sigaction 注册信号失败");exit(EXIT_FAILURE);}// 写入 PID 到文件,便于后续管理(如停止守护进程)FILE *pid_file = fopen(PID_FILE, "w");if (pid_file == NULL) {perror("创建 PID 文件失败");exit(EXIT_FAILURE);}fprintf(pid_file, "%d\n", getpid());fclose(pid_file);printf("守护进程初始化完成,PID = %d,日志文件:%s,PID 文件:%s\n", getpid(), LOG_FILE, PID_FILE);
}// 守护进程业务逻辑:每 3 秒写入当前时间到日志
void daemon_business() {time_t now;struct tm *local_tm;char time_str[64];while (1) {// 获取当前时间并格式化time(&now);local_tm = localtime(&now);strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", local_tm);// 写入日志printf("[%s] 守护进程运行中,PID = %d\n", time_str, getpid());// 每 3 秒执行一次sleep(3);}
}int main() {// 初始化守护进程daemon_init();// 执行业务逻辑daemon_business();// 业务逻辑为无限循环,此处不会执行return EXIT_SUCCESS;
}
编译与运行
编译与运行守护进程
# 1. 编译程序(需 root 权限,因日志和 PID 文件路径需系统权限)
sudo gcc timed_log_daemon.c -o timed_log_daemon # 2. 启动守护进程
sudo ./timed_log_daemon # 3. 查看守护进程状态(确认已后台运行,无终端)
ps -ef | grep timed_log_daemon | grep -v grep # 4. 查看日志文件(验证业务逻辑)
tail -f /var/log/timed_log_daemon.log # 5. 停止守护进程(通过 PID 文件获取 PID,发送 SIGTERM 信号)
sudo kill -TERM $(cat /var/run/timed_log_daemon.pid) # 6. 验证守护进程已停止
ps -ef | grep timed_log_daemon | grep -v grep
运行示例
启动守护进程输出
父进程 PID = 1234,子进程 PID = 1235,父进程退出
查看守护进程状态
root 1235 1 0 14:00 ? 00:00:00 ./timed_log_daemon
# TTY=?,PPID=1,确认为守护进程
查看日志文件
守护进程初始化完成,PID = 1235,日志文件:/var/log/timed_log_daemon.log,PID 文件:/var/run/timed_log_daemon.pid
[2024-09-30 14:00:00] 守护进程运行中,PID = 1235
[2024-09-30 14:00:03] 守护进程运行中,PID = 1235
[2024-09-30 14:00:06] 守护进程运行中,PID = 1235
停止守护进程后,查看日志
[signal_handler] 收到终止信号,守护进程退出
验证守护进程已停止
(无输出)
实例亮点:
- 包含 PID 文件管理:通过
/var/run/timed_log_daemon.pid
记录守护进程 PID,便于后续停止、重启等管理操作; - 优雅退出:收到
SIGINT
或SIGTERM
信号时,删除 PID 文件后再退出,避免残留文件影响下次启动; - 日志可追溯:所有输出重定向到日志文件,便于排查守护进程运行状态和故障。
四、编写守护进程的常见错误与解决方法
手工编写守护进程时,易因步骤遗漏、函数使用不当或信号处理错误导致守护进程异常。以下是高频错误及对应的解决方法:
常见错误 | 问题现象 | 原因分析 | 解决方法 |
---|---|---|---|
未调用 setsid 或调用时机错误 | 守护进程仍关联终端,终端关闭时守护进程被终止;或 setsid 调用失败,返回 -1 | 1. 遗漏 setsid 步骤,守护进程未脱离原终端; 2. 在“进程组组长”进程中调用 setsid(如未 fork 直接调用),setsid 要求调用进程不能是进程组组长 | 1. 严格遵循“fork→父进程退出→setsid”的顺序,确保子进程不是进程组组长; 2. 调用 setsid 后,通过 ps -eo pid,tty,cmd 验证终端是否为 ? ;3. 检查 setsid 返回值,若为 -1,通过 perror 排查错误原因(如权限不足) |
未清除文件创建掩码 | 守护进程创建的文件/目录权限不符合预期(如权限为 600,其他用户无法访问) | 父进程的 umask 被子进程继承,若父进程 umask 为 077,守护进程创建的文件权限会变为 600(666-077),导致其他用户无法访问 | 1. 必须在守护进程初始化时调用 umask(0) ,清除文件创建掩码;2. 创建文件/目录时,显式指定权限(如 open(path, O_CREAT, 0644) ),避免依赖默认权限;3. 创建后通过 chmod 按需调整权限,确保符合业务需求 |
信号处理函数中未循环回收子进程 | 多个子进程同时终止时,部分子进程未被回收,变为僵死进程 | 内核发送 SIGCHLD 信号时,若多个子进程同时终止,仅会发送一次信号;信号处理函数中若仅调用一次 waitpid ,只能回收一个子进程,剩余子进程无人处理 | 1. 在 SIGCHLD 信号处理函数中,用 while (waitpid(-1, NULL, WNOHANG) > 0) 循环回收,直到 waitpid 返回 <= 0;2. 避免在信号处理函数中使用 wait (只能回收一个子进程),必须使用 waitpid 结合 WNOHANG 非阻塞回收;3. 若守护进程不创建子进程,可忽略 SIGCHLD 信号( signal(SIGCHLD, SIG_IGN) ),避免僵死 |
未关闭标准文件描述符或重定向日志 | 守护进程输出丢失(如 printf 无内容);或占用终端文件描述符,导致终端无法正常关闭 | 1. 未关闭 stdin、stdout、stderr,这些描述符关联原终端,关闭终端后输出无法写入; 2. 关闭描述符后未重定向日志,所有输出会写入 /dev/null ,导致日志丢失 | 1. 必须关闭 stdin(0)、stdout(1)、stderr(2),避免终端关联; 2. 关闭后将 stdout 和 stderr 重定向到日志文件(如 dup2(log_fd, STDOUT_FILENO) ),或通过 syslog 函数发送到系统日志;3. 验证日志重定向:启动后查看日志文件,确认输出正常 |
PID 文件管理不当 | 守护进程重复启动(多个相同 PID 文件);或停止时无法找到 PID,导致守护进程无法停止 | 1. 启动时未检查 PID 文件是否存在,直接覆盖,导致多个守护进程同时运行; 2. 退出时未删除 PID 文件,下次启动时误判守护进程已运行; 3. PID 文件路径无写入权限(如 /var/run 仅 root 可写,普通用户运行时创建失败) | 1. 启动时检查 PID 文件:若文件存在且对应进程运行,提示“守护进程已启动”并退出; 2. 优雅退出时删除 PID 文件:在 SIGINT/SIGTERM 信号处理函数中调用 remove(PID_FILE) ;3. 确保 PID 文件路径权限:普通用户运行时,将 PID 文件放在 ~/tmp 等有权限的目录,避免使用 /var/run ;4. 读取 PID 文件时处理异常:若文件存在但对应进程不存在,删除文件后重新启动 |
五、守护进程的运行管理与监控
编写守护进程后,还需考虑其运行后的管理(启动、停止、重启)和监控(资源占用、运行状态),确保守护进程长期稳定服务。以下是常用的管理与监控方法:
1. 基于 PID 文件的手动管理
通过 PID 文件记录守护进程的 PID,结合 kill
命令实现手动管理,适用于简单场景:
启动守护进程(假设启动脚本会创建 PID 文件)
sudo ./timed_log_daemon
查看守护进程状态(通过 PID 文件)
if [ -f /var/run/timed_log_daemon.pid ]; thenpid=$(cat /var/run/timed_log_daemon.pid)if ps -p $pid > /dev/null; thenecho "守护进程运行中,PID = $pid"elseecho "守护进程已停止,但 PID 文件残留,正在清理..."rm -f /var/run/timed_log_daemon.pidfi
elseecho "守护进程未运行"
fi
停止守护进程(发送 SIGTERM 信号,优雅退出)
sudo kill -TERM $(cat /var/run/timed_log_daemon.pid)
重启守护进程(先停止,再启动)
sudo kill -TERM $(cat /var/run/timed_log_daemon.pid)
sleep 1
sudo ./timed_log_daemon
2. 注册为系统服务(systemd 管理)
将守护进程注册为 systemd 服务,通过 systemd 实现开机自启、自动重启和状态监控,适用于系统级守护进程:
创建 systemd 服务配置文件
sudo vim /etc/systemd/system/timed-log-daemon.service
写入配置内容
[Unit]
Description=Timed Log Daemon
After=network.target[Service]
Type=forking
ExecStart=/usr/local/bin/timed_log_daemon
ExecStop=/bin/kill -TERM \$(\cat /var/run/timed_log_daemon.pid)
Restart=always
User=root
Group=root
PIDFile=/var/run/timed_log_daemon.pid[Install]
WantedBy=multi-user.target
重新加载 systemd 配置
sudo systemctl daemon-reload
管理守护进程
sudo systemctl start timed-log-daemon
sudo systemctl stop timed-log-daemon
sudo systemctl restart timed-log-daemon
sudo systemctl status timed-log-daemon
sudo systemctl enable timed-log-daemon
sudo systemctl disable timed-log-daemon
查看守护进程日志
sudo journalctl -u timed-log-daemon -f
查看服务状态
● timed-log-daemon.service - Timed Log DaemonLoaded: loaded (/etc/systemd/system/timed-log-daemon.service; enabled; vendor preset: enabled)Active: active (running) since Mon 2024-09-30 14:30:00 CST; 5min agoMain PID: 1235 (timed_log_daemon)Tasks: 1 (limit: 4915)Memory: 1.2MCPU: 10msCGroup: /system.slice/timed-log-daemon.service└─1235 /usr/local/bin/timed_log_daemon
实时查看日志
Sep 30 14:30:00 ubuntu systemd[1]: Starting Timed Log Daemon...
Sep 30 14:30:00 ubuntu timed_log_daemon[1235]: 守护进程初始化完成,PID = 1235,日志文件:/var/log/timed_log_daemon.log,PID 文件:/var/run/timed_log_daemon.pid
Sep 30 14:30:00 ubuntu timed_log_daemon[1235]: [2024-09-30 14:30:00] 守护进程运行中,PID = 1235
Sep 30 14:30:03 ubuntu timed_log_daemon[1235]: [2024-09-30 14:30:03] 守护进程运行中,PID = 1235
优势:systemd 提供完善的生命周期管理,支持自动重启、日志聚合和开机自启,无需手动编写启动脚本,是生产环境中管理守护进程的首选方式。
3. 资源监控与故障排查
通过系统工具监控守护进程的 CPU、内存占用和运行状态,及时发现异常并排查故障:
实时监控守护进程资源占用(top 命令)
top -p $(cat /var/run/timed_log_daemon.pid)
查看守护进程打开的文件描述符(排查资源泄漏)
sudo lsof -p $(cat /var/run/timed_log_daemon.pid)
查看守护进程的系统调用(排查异常行为)
sudo strace -p $(cat /var/run/timed_log_daemon.pid)
查看守护进程的内存使用详情(排查内存泄漏)
sudo pmap $(cat /var/run/timed_log_daemon.pid)
定期检查守护进程状态(可加入 crontab 定时执行)
cat > /usr/local/bin/check_daemon.sh << EOF
#!/bin/bash
pid_file="/var/run/timed_log_daemon.pid"
if [ ! -f \$pid_file ]; thenecho "守护进程未运行,正在重启..."/usr/local/bin/timed_log_daemonexit 0
fi
pid=\$(cat \$pid_file)
if ! ps -p \$pid > /dev/null; thenecho "守护进程已停止,正在重启..."rm -f \$pid_file/usr/local/bin/timed_log_daemon
elseecho "守护进程运行正常,PID = \$pid"
fi
EOF
chmod +x /usr/local/bin/check_daemon.sh
添加到 crontab,每 5 分钟检查一次
sudo crontab -e
# 加入:*/5 * * * * /usr/local/bin/check_daemon.sh >> /var/log/check_daemon.log 2>&1
UNIX 守护进程的手工编写流程,从核心五步(创建子进程、脱离终端、清除掩码、关闭文件、信号处理)到完整实例,覆盖了守护进程编写的关键技术点和常见问题。手工编写守护进程的核心是“脱离终端、资源可控、信号安全”,需严格遵循固定流程,避免步骤遗漏或函数使用不当。
在实际开发中,除手工编写外,也可使用 daemon(3)
函数(部分系统提供)或 supervisord
等工具简化守护进程创建,但手工编写能更深入理解守护进程的本质。建议结合实例多做实践,掌握信号处理、日志重定向和 PID 文件管理等细节,编写稳定、可管理的守护进程。