一,守护进程
1,会话的概念

对话的结构包括进程组,进程,线程
调度器的调度策略是以线程为大内进行调度的
1,多进程并发和多线程并发
- 多进程是操作系统为了多任务而自然演化出来的,先有了各种实现,后来才被标准化,所以是「先有事实,再有规范」;
- 多线程是为了高效并发而主动设计的,先定好了模型和接口规范,再去各个系统实现,所以从一开始就更「规范」。
所以一般来说很多都要支持多线程并发的
2,前台进程组和后台进程组
前台进程组:一个终端有且仅有一个,独占标准输入(可以读取键盘),可以正常标准输出。
后台进程组:将前台运行的进程组切换到后台状态继续运行,禁止读取外部键盘输入,但可以正常输出。
如果后台进程组强行尝试读取标准输入(键盘),操作系统会发送信号将其暂停,而不是杀死。
比如前台正在压缩一个大文件,占用命令行无法操作,我们在压缩指令后面加 &,就能把它放到后台进程组运行,不影响前台使用命令行。
为什么后台进程组尝试读标准输入,会被暂停?
这是操作系统为了保护终端交互设计的强制规则,原理超简单:
2,函数的理解
首先创建守护进程对应的函数是


上面这个是对于这个函数的描述,
如果调用进程不是进程组组长,setsid() 会创建一个新会话。
调用进程将成为新会话的会话组长(即其会话 ID 会被设置为与自身进程 ID 一致)。
调用进程同时也会成为该会话内一个新进程组的进程组组长(即其进程组 ID 会被设置为与自身进程 ID 一致)。
调用进程将是这个新进程组和新会话中唯一的进程。
新会话在初始状态下没有控制终端。
setsid() 创建新会话的那一瞬间✅
这个新会话里 有且仅有 1 个进程组✅
这个进程组里 有且仅有 1 个进程(就是调用 setsid() 的进程)
这里很难理解,主播也是好不容易画出了这个示意图,有错误可以随时指出

下面让我们逐步来理解这些话
1,如果调用进程不是进程组组长,setsid() 会创建一个新会话。
2,调用进程将成为新会话的会话组长(即其会话 ID 会被设置为与自身进程 ID 一致)。
调用进程同时也会成为该会话内一个新进程组的进程组组长(即其进程组 ID 会被设置为与自身进程 ID 一致)。新会话在初始状态下没有控制终端。
一、会话ID=自身进程ID的作用
- 这个进程不再属于原来的终端会话,和原终端、原会话彻底解绑。
- 新会话没有控制终端,关终端、退 SSH 都不会影响这个进程。
- 只有它能为这个会话申请控制终端(一般守护进程不会这么做)。
- 会话里后续所有进程组、子进程,都归这个会话管理。
二、进程组ID=自身进程ID的作用
- 这个进程不再属于原来的进程组,不受原进程组的信号影响(比如原进程组收到 Ctrl+C,不会传给它)。
- 它可以管理组内后续创建的子进程(比如给整个组发信号)。
- 新进程组和原进程组完全隔离,信号、资源都互不干扰。
三、组合起来的终极效果 ✨
这两个设置同时生效后,进程就变成了 **「无父无母、自己当家」的独立个体 **:
- ✅ 不受终端控制:关终端不会杀死它(守护进程的核心要求)。
- ✅ 不受原进程组 / 会话信号干扰:比如 Ctrl+C、终端挂断信号都传不到它这里。
- ✅ 自己管理自己的后代:后续创建的子进程都会归到它的会话 / 进程组里,由它统一管理。
- ✅ 系统层面唯一标识:会话 ID、进程组 ID 都等于它的 PID,内核能精准识别和管理。
3,查看守护进程
![]()

不难看到红色框住的TTY为?的,那么就是说明这个是脱离终端控制的,接下来就看PID,PGIDSID是不是一样的
还有就是PPID一般都是为1的
为什么守护进程的 PPID 几乎都是 1?这不是巧合,是人为故意做的!
核心一句话
守护进程要彻底断绝所有依赖:
分步拆解(标准守护进程的强制步骤)
这是 Linux 写守护进程固定不变的套路,一步都不能少:
步骤 1:父进程 fork 创建子进程
父进程(比如终端 shell、你的程序)生一个子进程。此时:
- 子进程 PPID = 父进程 PID
- 子进程是进程组的普通成员(不是组长)
步骤 2:父进程直接退出、自杀!(最关键一步)
父进程立刻死掉,子进程变成孤儿进程。系统规则:
孤儿进程必须被系统的「始祖进程」PID=1(init/systemd)收养
此时:
- 子进程 PPID 直接变成 1
- 子进程彻底没有了「原来的父进程」,无牵无挂
步骤 3:子进程调用 setsid()
因为父进程已经退出,子进程不是进程组组长,满足调用条件:
- 创建新会话,脱离终端
- 会话 ID = 进程组 ID=PID
为什么非要让 PPID=1?(3 个核心理由)
1. 父进程会 “拖累” 守护进程
原来的父进程(比如终端、普通程序)随时可能关闭:
- 父进程一死,子进程就会被系统骚扰
- 让父进程主动退出,从根源上杜绝拖累
2. PID=1 是系统唯一永远不死的进程
- PID=1(init/systemd)是系统启动第一个进程,关机才会停
- 它专门负责收养孤儿、回收进程资源,绝对靠谱
- 守护进程交给它管,永远不用担心没人兜底
3. 满足 setsid() 的硬性前提
这是最现实的原因:只有父进程退出,子进程才不是进程组组长,才能调用 setsid()为了调用 setsid 脱离终端,必须先让父进程死 → PPID 自然变 1。
单实例的守护进程,同一时刻只运行运行一次: 锁文件 /var/run/name.pid
每个守护进程启动时都会将pid写入锁文件中
启动脚本文件:/etc/rc.d/rc.local 不同系统位置不同
二,守护进程的实例
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
#define FNAME "/tmp/out"
static int daemonize(){
pid_t pid;
int fd;
pid = fork();
if(pid < 0){
perror("fork()");
return -1;
}
if(pid > 0){ // 父进程直接退出
exit(0);
}
// 重定向输入输出到 /dev/null(丢弃所有终端输出)
fd = open("/dev/null",O_RDWR);
if(fd < 0){
perror("open()");
return -1;
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2)
close(fd);
setsid(); // 创建新会话,脱离终端
chdir("/"); // 切换工作目录到根目录,防止占用磁盘
return 0;
}
int main(){
FILE* fp;
if(daemonize()){
exit(1);
}
fp = fopen(FNAME, "w");
if(fp == NULL){
perror("fopen()");
exit(1);
}
// 死循环,每秒写入数字
for(int i = 0; ;i++){
fprintf(fp,"%d\\n", i);
fflush(NULL); // 刷新缓冲区,立即写入文件
sleep(1);
}
exit(0);
}

![]()
可以观察到这这些都是一样的,说明创建成功了
杀死进程就是 kill 进程号
三,系统日志
系统日志
syslogd服务, 所有需要写系统日志的操作将其交给syslogd
openlog(); 关联系统日志服务
syslog();
closelog();
通过信号可以将异常终止转变为正常终止
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<syslog.h>
#include<errno.h>
#include<string.h>
#define FNAME "/tmp/out"
static int daemonize(){
pid_t pid;
int fd;
pid = fork();
if(pid < 0){
// perror("fork()");
return -1;
}
if(pid > 0){ // 父进程直接退出
exit(0);
}
// 重定向输入输出到 /dev/null(丢弃所有终端输出)
fd = open("/dev/null",O_RDWR);
if(fd < 0){
// perror("open()");
return -1;
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if(fd > 2)
close(fd);
setsid(); // 创建新会话,脱离终端
chdir("/"); // 切换工作目录到根目录,防止占用磁盘
return 0;
}
int main(){
FILE* fp;
//我接下来要打日志了,我的名字叫
// mydaemon,日志带上 PID,我是守护进程类型的程序!
openlog("mydaemon", LOG_PID, LOG_DAEMON);
if(daemonize()){
//补药写\\n,因为格式是syslog服务决定的
syslog(LOG_ERR, "daemonize() failed!");
exit(1);
}
else{
syslog(LOG_INFO, "daemonize() successded!");
}
fp = fopen(FNAME, "w");
if(fp == NULL){
// perror("fopen()");
syslog(LOG_ERR, "fopen():%s", strerror(errno));
exit(1);
}
syslog(LOG_INFO, "%s was opend.", FNAME);
// 死循环,每秒写入数字
for(int i = 0; ;i++){
fprintf(fp,"%d\\n", i);
fflush(NULL); // 刷新缓冲区,立即写入文件
syslog(LOG_DEBUG, "%d is printed.", i);
sleep(1);
}
fclose(fp);
closelog();
exit(0);
}
![]()
可以看到程序已经运行了

可以看到这个日志已经输出进来了


