在日常使用Linux时,你是否遇到过这样的困惑:
-
为什么直接运行一个程序后,Shell就"卡住"了,无法输入新命令?
-
为什么在命令后面加上&放到后台,Shell就能继续接受新命令?
-
Ctrl+C、Ctrl+Z 到底影响了谁?
-
jobs、fg、bg这些命令是如何工作的?
这一切的背后,都离不开 进程组 、会话 和 作业控制 这三个核心概念。
一、核心概念总览
在Linux中,进程不是孤立存在的,它们被组织成层次化的关系结构。理解这个结构,就像理解一个公司的组织架构:

二、从进程到进程组
2.1 进程(Process)
我们知道,每个进程都有唯一的 PID (进程ID)。但进程往往不是孤立工作的,比如通过管道 sleep 100 | sleep 200 | sleep 300 就同时启动了三个进程,它们协同完成一个"作业"。

2.2 进程组(Process Group)
进程组是一个或多个进程的集合,用于完成某个特定任务。
===》 就像建筑工地上,几个工人组成一个施工小组 来完成"砌墙"这个任务。进程组中的进程通常是兄弟进程(由同一个父进程创建),通过管道协作。
- 进程组 是一个或多个进程的集合。
- 每个进程组有一个 PGID (进程组ID),并且该组的 组长进程 的PID等于PGID。
- 进程组的生命周期:从第一个进程加入开始**,到最后一个进程离开结束------ 即使组长退出了,只要还有组员,进程组依然存在。**

- PID不同:说明是三个不同的进程
- PPID相同 : ps ajx | grep PPID ==》 bash进程
- PGID : 进程组
- 进程 以 进程组的形式 , 完成对应的任务
- 单个进程,自己可以成为进程组
- UID : 表明这个进程是由那个用户创建的
# 查看进程的PID、PGID、PPID
ps -eo pid,pgid,ppid,comm | grep sleep
2.3 进程组的生命周期
从进程组创建开始,到最后一个进程离开为止。
⚠️ 重要:组长进程终止后,进程组依然可以存在!就像主厨请假了,帮厨们还能继续干活。
三、会话(Session):进程组的容器
3.1 什么是会话
会话是一个或多个进程组的集合。
关键标识:
- SID (Session ID):会话ID,等于会话首进程的PID
会话 是一个或多个进程组的集合。通常,当你打开一个终端(如SSH、xshell)并登录后,系统就会为你创建一个新的会话。会话中的首进程 (通常是bash)称为会话首进程,它的PID就是会话ID(SID)。

-
用户登录成功 → 系统创建新会话
-
会话内部必须有一个默认进程组 → 就是bash本身
-
bash是独立进程 ,也是独立进程组(PID=PGID=SID)
3.2 控制终端(Controlling Terminal)
每个会话可以关联一个控制终端 (就是你登录的终端设备,比如 /dev/pts/0)。这个终端的信息会记录在进程的PCB中,子进程会继承它。
-
会话首进程也是控制进程 ,当终端断开时,它会收到
SIGHUP信号。 -
键盘输入(按键、
Ctrl+C等)只发给前台进程组,后台进程组无法直接收到。
所以**"键盘只有一个,输入的键值必须明确给一个进程或进程组"**------ 实际上这个"明确"就是通过前台进程组来决定的。
3.3 创建新会话:setsid()
#include <unistd.h>
pid_t setsid(void);
调用效果(三步走):
-
调用进程成为新会话首进程
-
调用进程成为新进程组组长(新PGID = 当前PID)
-
脱离控制终端(如果有的话,切断联系)
⚠️ 前提条件 :调用进程不能是进程组组长!
常见技巧:先fork再setsid
if (fork() > 0) exit(0); // 父进程退出
setsid(); // 子进程创建新会话
// 现在子进程是新会话的首进程,也是新进程组的组长
四、控制终端:键盘和屏幕的归属
4.1 为什么需要区分前台和后台?
"为什么我们直接启动一个任务,就无法输入命令执行了?"
"为什么放在后台执行,我们依旧能够执行命令?"
答案:键盘只有一个!

4.2 前台 vs 后台:会话内部的划分
-
前台作业:独占终端输入和信号,此时Shell会等待它结束(或挂起),无法执行其他命令。
-
后台作业:在后台运行,不占用终端输入,Shell可以立即接受新命令。

核心规则:
一次会话中,有且只能有一个前台进程组
后台进程组可以有多个
键盘输入只给前台进程组
Ctrl+C(SIGINT)、Ctrl+\(SIGQUIT)、Ctrl+Z(SIGTSTP)只发给前台进程组
# 前台运行(默认)
./test
# 后台运行
./test &
4.3 信号流向图

五、作业控制(Job Control):用户的视角
5.1 作业 = 用户眼中的任务
作业是用户为完成某项任务而启动的进程集合,通常是一个管道。
Shell控制的是作业/进程组,而不是单个进程!
| 令 | 作用 |
|---|---|
jobs |
查看当前会话下的后台/停止作业 |
fg %作业号 |
将后台或停止的作业放到前台运行 |
bg %作业号 |
让一个停止的后台作业继续在后台运行 |
Ctrl+C |
发送 SIGINT 给前台进程组(终止) |
Ctrl+Z |
发送 SIGTSTP 给前台进程组(暂停/挂起) |
Ctrl+\ |
发送 SIGQUIT 给前台进程组(退出+core dump) |
注意:
Ctrl+Z只是暂停,并不是终止。被暂停的作业会变为 停止 状态,可以通过bg或fg恢复。
5.2 实验演示
# 1. 后台运行一个作业
$ sleep 300 &
[1] 2265 # [作业号] PID
# 2. 前台运行死循环程序(看image(3).png的proc.cc)
$ ./proc # 无限打印"hello server"
hello server
hello server
^Z # 按Ctrl+Z挂起
[2]+ 已停止 ./proc # 作业[2]停止
# 3. 查看所有作业
$ jobs -l
[1]- 2265 运行中 sleep 300 & # "-" 即将成为默认作业
[2]+ 2267 停止 ./proc # "+" 默认作业
# 4. 将停止的作业切回前台
$ fg %2 # 或直接用 fg(默认切"+"的作业)
./proc # 继续打印hello server...
5.3 作业状态
| 状态 | 含义 |
|---|---|
| 运行中 | 正在执行 |
| 已停止 | 被SIGTSTP/SIGSTOP暂停 |
| 完成 | 正常退出 |
| 已终止 | 被信号杀死 |
六、为什么直接启动任务会"卡住"终端?
原因很简单 :默认情况下,一个作业是前台 运行的。前台作业会霸占 终端输入和信号,Shell(bash)会把自己阻塞,等待该作业结束。你可以理解为:"Shell把终端控制权交给了你运行的这一组进程"。
当你在命令后加 &,相当于明确告诉Shell:把这个作业放到后台,终端控制权立即还给我。后台作业在运行时,Shell可以继续解释新命令。
一次会话中,有且仅有一个前台进程组,但可以有多个后台进程组。键盘输入的数据只会发给前台进程组。

