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,它是所有用户进程的祖先。
Linux 进程树结构示意图 init/systemd (PID 1) sshd (PID 100) cron (PID 120) getty (PID 130) bash (PID 200) bash (PID 210) login (PID 220) vim (PID 300) gcc (PID 310) bash (PID 320) 说明:每个子进程都有唯一的 PID,并通过 PPID 指向父进程。孤儿进程会被 init 进程(PID 1)收养。

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 等状态来描述进程当前的活动情况。

Linux 进程生命周期状态变迁图 创建 (fork) 就绪/运行 (R) 可中断睡眠 (S) 不可中断睡眠 (D) 停止 (T) 僵尸 (Z) 终止 (X) 信号唤醒 父进程 wait() 回收 状态说明:R(运行/就绪)、S(可中断睡眠,等待资源或信号)、D(不可中断睡眠,通常等待 I/O)、T(停止,收到 SIGSTOP)、Z(僵尸,已终止但未回收)、X(已完全销毁)。 僵尸进程必须由父进程调用 wait()/waitpid() 回收,否则会占用内核进程表项。

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 权限检查流程

当进程尝试访问文件或执行系统调用时,内核按以下顺序进行权限判断:

  1. 超级用户检查: 如果 euid 为 0(root),直接授予权限。
  2. 用户匹配: 如果 euid 等于文件所有者 UID,检查文件所有者权限位(rwx)。
  3. 组匹配: 如果 egid 或辅助组列表匹配文件组 GID,检查文件组权限位。
  4. 其他用户: 检查文件其他用户权限位。

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: 内核中管理进程的核心数据结构,包含标识、状态、凭证、树关系、调度信息等。