1. 创建第一个守护进程
任务说明
本案例要求实现一个标准的Linux守护进程创建流程,核心目标是通过fork()、setsid()、chdir()、umask()四个关键步骤,使进程脱离控制终端独立运行。最终验证方式为:启动该守护进程后,关闭终端或退出登录,进程仍持续运行,且其父进程PID变为1(init/systemd)。
守护进程实现
// daemonize.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#define DAEMON_NAME "mydaemon"
#define WORK_DIR "/tmp"
#define LOG_FILE "/tmp/mydaemon.log"
void write_log(const char *msg) {
int fd = open(LOG_FILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) return;
char buf[256];
int len = snprintf(buf, sizeof(buf), "[%ld] %s\n", (long)getpid(), msg);
write(fd, buf, len);
close(fd);
}
int main(void) {
// 第一步:fork,父进程退出
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出,使子进程成为孤儿进程,由init收养
exit(EXIT_SUCCESS);
}
// 第二步:创建新会话,脱离控制终端
if (setsid() < 0) {
perror("setsid");
exit(EXIT_FAILURE);
}
// 第三步:修改工作目录,避免占用挂载点
if (chdir(WORK_DIR) < 0) {
perror("chdir");
exit(EXIT_FAILURE);
}
// 第四步:重置文件权限掩码
umask(0);
// 关闭标准输入/输出/错误,避免占用终端
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 将标准文件描述符重定向到/dev/null,防止意外错误
int devnull = open("/dev/null", O_RDWR);
if (devnull >= 0) {
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > STDERR_FILENO) close(devnull);
}
// 忽略SIGCHLD信号,避免产生僵尸进程
signal(SIGCHLD, SIG_IGN);
// 守护进程主循环:每5秒写一次日志
write_log("daemon started");
while (1) {
sleep(5);
write_log("daemon is running");
}
return 0;
}
编译与运行
# 编译
gcc -o mydaemon daemonize.c
# 启动守护进程
./mydaemon
# 查看进程状态(确认PPID为1,且无控制终端tty)
ps -o pid,ppid,pgid,sid,tty,cmd -p $(pgrep -x mydaemon)
验证方法与判定标准
执行以下步骤验证守护进程是否正确脱离终端:
- 启动
./mydaemon后,终端应立即返回提示符(父进程已退出),不会阻塞。 - 执行
ps -o pid,ppid,pgid,sid,tty,cmd -p $(pgrep -x mydaemon),观察输出:PPID应为1(被init/systemd收养),TTY应为?(表示无控制终端),PGID与SID应等于进程自身的PID(因setsid()创建了新会话)。 - 关闭当前终端窗口或执行
exit退出登录,重新打开终端后执行pgrep -x mydaemon,进程仍然存在。 - 查看日志文件
/tmp/mydaemon.log,应持续追加包含PID和运行状态的行,证明主循环正常工作。
以上条件全部满足,即判定守护进程创建成功。
关键步骤说明
守护进程创建流程
┌─────────────────────────────────────────────────────────────┐
│ 终端启动 ./mydaemon │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ fork() ──→ 父进程退出,子进程被init收养 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ setsid() ──→ 创建新会话,子进程成为会话首进程,脱离控制终端 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ chdir("/tmp") ──→ 切换工作目录,避免占用文件系统挂载点 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ umask(0) ──→ 清除文件权限掩码,确保创建文件权限可控 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 关闭stdin/stdout/stderr → 重定向到/dev/null │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 主循环:每5秒写日志,持续运行 │
└─────────────────────────────────────────────────────────────┘
预期输出示例
执行ps命令后,预期输出格式如下(PID数值因系统而异):
PID PPID PGID SID TTY CMD
1234 1 1234 1234 ? ./mydaemon
其中PPID=1、TTY=?是守护进程脱离终端的关键标志。日志文件/tmp/mydaemon.log内容示例:
[1234] daemon started
[1234] daemon is running
[1234] daemon is running