2. Linux 进程管理全景:进程树结构、进程ID(PID/PPID)、进程生命周期、进程的UID/GID与权限
进程是 Linux 系统中资源分配和调度的基本单位。理解进程的全景视图——从进程树的层级结构、唯一标识符、生命周期状态变迁,到用户与组权限模型——是掌握进程管理的基础。本章将系统性地拆解这些核心概念,为后续的进程创建、调度与 IPC 通信实战打下坚实的理论基础。
2.1 进程树结构:从 init 到所有子进程
Linux 系统中的所有进程构成一棵以 init 进程(PID 1)为根的树形结构。每个进程(除 init 外)都有且只有一个父进程,通过 fork() 或 clone() 系统调用创建的子进程被挂接到父进程之下。这种层级关系使得系统能够统一管理进程生命周期、资源回收(孤儿进程被 init 收养)以及权限继承。
关键概念: 在终端执行
pstree -p 命令可以直观地看到当前系统的进程树。PID 1 通常是 systemd 或 init,它是所有用户进程的祖先。
2.2 进程标识符:PID 与 PPID
每个进程在创建时由内核分配一个唯一的 进程 ID(PID),类型为 pid_t(通常为 int)。PID 是进程在系统中的身份证,用于调度、信号发送和资源管理。父进程 ID(PPID)则标识了创建当前进程的父进程。
| 标识符 | 含义 | 获取方式(C 语言) | 典型值 |
|---|---|---|---|
| PID | 进程唯一标识符 | getpid() |
1, 100, 2000 |
| PPID | 父进程 PID | getppid() |
1, 100, 1500 |
| TGID | 线程组 ID(主线程 PID) | getpid()(等同) |
与 PID 相同 |
| PGID | 进程组 ID | getpgid() |
与首进程 PID 相同 |
| SID | 会话 ID | getsid() |
与首进程 PID 相同 |
实战要点: PID 是循环使用的,但不会立即重用。内核通过
pid_max(默认 32768)控制最大 PID 值。在编写守护进程或管理子进程时,务必记录 PID 以便后续信号控制。
2.3 进程生命周期:状态变迁全流程
进程从创建到终止经历一系列状态转换。Linux 内核使用 TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_STOPPED 和 TASK_ZOMBIE 等状态来描述进程当前的活动情况。
2.4 进程的 UID、GID 与权限模型
Linux 使用用户 ID(UID)和组 ID(GID)来控制进程对系统资源的访问权限。每个进程都有多个关联的 UID/GID,用于不同的权限检查场景。
2.4.1 进程凭证(Credentials)
| 凭证类型 | 含义 | 作用场景 |
|---|---|---|
| 实际 UID (ruid) | 启动进程的用户身份 | 用于统计、配额、信号发送权限 |
| 有效 UID (euid) | 当前生效的用户身份 | 用于文件访问、系统调用权限检查 |
| 保存设置 UID (suid) | 保存的 euid 副本 | 用于 setuid 程序切换后恢复权限 |
| 实际 GID (rgid) | 启动进程的组身份 | 与 ruid 类似 |
| 有效 GID (egid) | 当前生效的组身份 | 用于文件组权限检查 |
| 辅助组列表 | 用户所属的附加组 | 扩展组权限检查 |
安全提示: setuid 位(如
chmod u+s program)会使进程的 euid 变为文件所有者,而非启动用户。这是 passwd、ping 等程序能访问特权资源的关键机制,但也可能引入提权漏洞。编写 setuid 程序时务必谨慎。
2.4.2 权限检查流程
当进程尝试访问文件或执行系统调用时,内核按以下顺序进行权限判断:
- 超级用户检查: 如果 euid 为 0(root),直接授予权限。
- 用户匹配: 如果 euid 等于文件所有者 UID,检查文件所有者权限位(rwx)。
- 组匹配: 如果 egid 或辅助组列表匹配文件组 GID,检查文件组权限位。
- 其他用户: 检查文件其他用户权限位。
2.5 关键数据结构:task_struct
内核使用 struct task_struct 管理每个进程的全部信息。以下是与本章内容直接相关的关键字段:
// 位于 include/linux/sched.h(简化示意)
struct task_struct {
/* 进程标识 */
pid_t pid; // 进程 ID
pid_t tgid; // 线程组 ID
struct task_struct *parent; // 父进程指针
struct list_head children; // 子进程链表
struct list_head sibling; // 兄弟进程链表
/* 进程状态 */
volatile long state; // TASK_RUNNING, TASK_INTERRUPTIBLE 等
/* 权限凭证 */
const struct cred *cred; // 包含 uid, gid, euid, egid, suid 等
/* 进程树关系 */
struct task_struct *group_leader; // 线程组 leader
/* 调度相关 */
unsigned int policy; // 调度策略
int prio; // 动态优先级
int static_prio; // 静态优先级
};
内核视角:
task_struct 是进程管理的核心枢纽。通过 parent 和 children 链表,内核可以高效遍历进程树;通过 cred 结构体,实现细粒度的权限控制。理解这个结构体是深入进程管理的第一步。
2.6 本章小结
- 进程树: 所有进程以 init(PID 1)为根,形成树形层级,通过
pstree可直观查看。 - PID/PPID: 唯一标识进程,PPID 建立父子关系,内核通过
pid_max控制 PID 范围。 - 生命周期: 进程经历创建→就绪/运行→睡眠(可中断/不可中断)→停止→僵尸→终止,父进程必须回收僵尸进程。
- UID/GID 权限: 进程持有实际/有效/保存三组 UID/GID,内核按 root→用户→组→其他顺序检查权限。
- task_struct: 内核中管理进程的核心数据结构,包含标识、状态、凭证、树关系、调度信息等。