一、核心铺垫:先懂 3 个层级关系
Linux 进程三层包裹:会话 (Session) → 进程组 (Process Group) → 进程 (Process)
- 一个会话包含多个进程组
- 一个进程组包含多个进程
- 终端登录 = 打开一个会话,所有命令 / 程序都挂在这个会话下
一、进程组(Process Group)
1. 什么是进程组?
把一组有关联的进程打包成一个团队 ,方便统一管理(发信号、结束、调度)。比如:一条管道命令 ls | grep txt,两个进程默认在同一个进程组。
2. 进程组长(组长进程)
- 创建这个进程组的第一个进程,就是组长
- 规矩:进程组 ID = 组长的 PID
- 举例:组长 PID=1000 → 整个进程组 ID=1000
3. 关键特性
- 组长可以创建子进程,子进程自动归入当前进程组
- 只要组里还有一个进程,进程组就不会销毁
- 终端操作(Ctrl+C / Ctrl+Z)是发给整个进程组,不是单个进程
4. 常用函数
getpgid(pid):获取某个进程的进程组 IDsetpgid(pid, pgid):修改进程组(把某个进程加入指定组)
通俗总结
进程组 = 一个班级组长 = 班长全班 ID = 班长学号老师发指令(信号)→ 全班都收到
二、会话(Session)
1. 什么是会话?
会话是最高层级 ,绑定一个控制终端(你打开的黑框终端)。:你登录终端→创建一个会话→所有执行的程序都挂在这个会话里。
2. 会话核心特点
- 一个会话对应一个终端(桌面终端、SSH 远程终端)
- 终端关闭 / 用户注销 → 会话下所有前台进程全部被杀掉
- 守护进程要脱离终端,本质就是脱离原会话
3. 创建新会话的硬性规则(超级重要)
调用 setsid() 创建新会话,必须满足:
- 当前进程不能是原进程组的组长(核心!)
- 执行完
setsid()后:- 该进程变成新会话的会长
- 自动成为新进程组的组长
- 彻底脱离原来的控制终端(再也不收终端影响)
- 原终端关闭、注销登录,完全跟它没关系
4. 为什么要先 fork、父进程退出?
- 原本进程是组长,没法
setsid() - fork 出子进程 → 子进程不是组长
- 父进程退出,子进程独立 → 子进程顺利新建会话
5. 常用 API
getsid(pid):获取进程所属会话 IDsetsid():创建新会话(无参数,调用者自己进新会话)
通俗总结
会话 = 整个年级终端 = 年级教室关教室(关终端)→ 年级里所有学生(进程)全赶走守护进程 = 转学出去,脱离教室,不关不停
三、守护进程(Daemon)
1. 什么是守护进程?
Linux 后台永久运行的服务进程,英文名 Daemon,大部分系统服务、后台程序都是它。
2. 五大核心特点(扩展完整版)
- 全程后台运行,不占用终端
- 彻底脱离控制终端:关黑框、注销 SSH、退出登录都不影响
- 周期性循环工作 / 常驻监听(日志、定时、端口监听)
- 不会被终端信号杀死(Ctrl+C、关闭终端无效)
- 官方规范:服务名一般以 d 结尾(httpd、sshd、mysqld)
- 不打印杂乱输出到终端(要打就打日志文件)
3. 为什么普通程序不能当守护进程?
普通程序挂在终端会话下:
- 关掉终端 → SIGHUP 信号杀掉所有进程
- 守护进程必须:脱离会话→脱离进程组→脱离终端
四、创建守护进程 标准 7 步模型
步骤 1:fork () 创建子进程,父进程直接 exit
原因:
- 原进程是进程组长,没法新建会话
- 子进程是普通进程,满足
setsid()条件效果:程序后台化,脱离前台终端
步骤 2:子进程调用 setsid () 创建全新会话
三大质变:
- 子进程 = 新会话首领
- 子进程 = 新进程组组长
- 彻底切断与原终端的所有关联(关键点)
步骤 3:再次 fork ()(可选高阶加固)
防止后期进程重新打开终端,彻底杜绝控端绑定。
步骤 4:chdir ("/") 修改工作目录
原因:
- 原来目录如果被卸载、删除、U 盘拔出,程序会崩
- 改成根目录 /,绝对安全常驻
步骤 5:umask (0) 重置文件权限掩码
原因:
- 默认掩码可能限制日志、文件创建权限
- 清零后,守护进程创建文件权限完全可控
步骤 6:关闭所有无用文件描述符(0/1/2)
- 0:标准输入(键盘)
- 1:标准输出(终端打印)
- 2:标准错误(终端报错)原因:守护进程不用终端 IO,留着会占用资源、乱输出。规范做法:把 0/1/2 重定向到 /dev/null(黑洞)
步骤 7:写核心业务逻辑(while (1) 常驻循环)
- 定时任务
- 监听端口
- 写日志
- 巡检、同步数据
五、三者层级关系一张图看懂
plaintext
终端会话(Session)
├─ 进程组1(前台命令)
│ ├ 进程1(组长)
│ └ 子进程
├─ 进程组2(后台普通程序)
└─ 被剥离:守护进程
独立会话 → 独立进程组 → 无终端绑定 → 永久后台运行
六、总结
-
进程组:一组关联进程,有组长,组 ID = 组长 PID
-
会话:包裹多个进程组,绑定终端,关终端杀全组进程
-
setsid():非组长才能调用,创建新会话、脱离终端
-
守护进程本质:脱离终端 + 脱离原会话 + 常驻后台 + 独立运行
-
创建流程口诀:fork 退出建会话,改目录清掩码,关描述常驻干活
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/stat.h>void daemon_create()
{
pid_t pid = fork();
if(pid > 0) exit(0); // 父进程退出setsid(); // 子进程建新会话 chdir("/"); // 切换根目录 umask(0); // 清空权限掩码 // 关闭标准IO close(0); close(1); close(2);}
int main()
{
daemon_create();// 核心常驻业务 while(1) { // 定时任务、日志、监听 sleep(3); } return 0;}
