﻿# 内核视角的进程 —— task_struct 与它挂着的一切资源

> 在用户眼里"进程"是一个正在运行的程序；在内核眼里，**一个进程就是一个 `task_struct`**——一张几百个字段的大表,外加它用指针挂出去的一堆资源对象（地址空间、打开的文件、信号、凭证、命名空间、IO 上下文、seccomp……）。本篇从**内核数据结构**的角度拆开：内核用什么表示一个进程、私有字段 vs 可共享资源对象怎么分界、进程和线程在这套结构里到底差在哪、以及怎么从 `/proc` 反向看到这些结构。

> 📚 **历史背景**：Linux 0.01 版本时内核只区分"任务"，没有线程概念，每个task对应一个进程；Linux 0.99.11版本引入了POSIX线程的支持，通过clone系统调用实现了轻量级进程，也就是线程；Linux 2.2版本之后，task_struct的结构逐渐稳定，现在的task_struct有超过600个字段，包含了进程的所有信息。


> 📂 **下钻子目录**：[task-resources/](/concepts/process/task-resources/README.md) —— 每一样资源对象逐篇展开详解(含新增的 task 私有字段、fs_struct、io_context/seccomp/rseq)。


> 📚 子系统索引：[task-struct-knowledge-framework.md](/concepts/process/task-struct-knowledge-framework.md)，按子系统维度将 task_struct 字段映射到仓库文档——想从字段出发找对应文档时查这张表。

> 相关：进程怎么被**创建**出来（fork/clone 复制/共享这些结构）见 [process-creation.md](/concepts/process/process-creation.md)；线程为什么是"共享大部分结构的 task"见 [thread-creation.md](/concepts/process/thread-creation.md)；地址空间的段布局见 [../elf/memory-layout.md](/concepts/elf/memory-layout.md)；`mm_struct`→页表翻译见 [../cache/tlb.md](/concepts/cache/tlb.md)；进程调度相关见 [scheduling.md](/concepts/process/scheduling.md)；上下文切换见 [context-switch.md](/concepts/process/context-switch.md)。

## 零、一句话认知：进程 = 一个 task_struct + 它指向的资源对象

Linux 内核**不区分"进程"和"线程"**这两个概念——它只有一种调度实体,叫 **task（任务）**,每个 task 对应一个 `struct task_struct`。所谓"进程"和"线程",只是**一组 task 之间共享了多少资源对象**的不同：共享得多就是"同一个进程里的多个线程",共享得少就是"不同进程"。

### 核心类层次图

```plantuml
@startuml
left to right direction
skinparam shadowing false
class "task_struct" as TS {
  + pid : pid_t
  + tgid : pid_t
  + comm : char[16]
  + __state : long
  + prio : int
  + sched_class : sched_class*
  + stack : void*
  + thread_struct : 寄存器 + 内核栈
  + utime / stime : u64
  + parent / children : 进程树
  --
  + fs : fs_struct*
  + files : files_struct*
  + mm : mm_struct*
  + signal : signal_struct*
  + sighand : sighand_struct*
  + cred : cred_struct*
  + nsproxy : nsproxy*
  + io_context : io_context*
  + seccomp : seccomp*
}
class "fs_struct" as FS {
  + root : path
  + pwd : path
  + umask : umode_t
}
class "files_struct" as FILES {
  + fd_array : file**
  + nr_open : int
}
class "mm_struct" as MM {
  + pgd : pgd_t*
  + start_code : unsigned long
  + end_code : unsigned long
  + start_stack : unsigned long
}
class "signal_struct" as SIG {
  + shared_pending : 信号队列
  + rlimit : 资源限制
}
class "cred_struct" as CRED {
  + uid : kuid_t
  + gid : kgid_t
  + cap_effective : 有效能力
}
class "nsproxy" as NS {
  + ns : 命名空间数组
}
TS --> FS
TS --> FILES
TS --> MM
TS --> SIG
TS --> CRED
TS --> NS
note bottom of TS
进程/线程之别 = 上面这些指针是
"各指一份"还是"共指同一份"
end note
@enduml
```

> **核心记忆**：`task_struct` 本身存的是"这个 task 私有的东西"（pid、状态、调度、寄存器现场）；那些**可共享的资源**（内存、文件、信号……）被单独拆成独立对象,`task_struct` 只用**指针**指过去。fork/clone 的全部戏法,就是决定这些指针**复制一份新对象**还是**共享同一个对象**（见 [process-creation.md](/concepts/process/process-creation.md) §三）。

> 📊 **可视化说明**：上图展示了task_struct与核心资源对象的关联关系，每个箭头代表一个指针字段，箭头方向表示资源的拥有关系。

## 一、task_struct 的两大部分：私有字段 vs 资源指针

### 1.1 私有字段（只属于这一个 task）

这些字段描述**这一个 task 本身**,不和别人共享。每个字段都只能被当前task访问和修改，不会被其他task共享。下面是主要的私有字段分类：

#### 1.1.1 身份标识字段

| 字段 | 类型 | 含义 | 调试方法 |
|------|------|------|--------|
| `pid` | `pid_t` | 线程ID，每个线程唯一的ID，对应`gettid()`系统调用的返回值 | `cat /proc/<pid>/task/<tid>/status` |
| `tgid` | `pid_t` | 线程组ID，同一进程的所有线程共享该ID，对应`getpid()`系统调用的返回值 | `cat /proc/<pid>/status | grep Tgid` |
| `group_leader` | `struct task_struct*` | 线程组组长进程的指针，也就是主线程的task_struct | `ps -o pid,tgid,comm -p <pid>` |
| `comm` | `char[16]` | 进程的命令行名称，最多16个字符，对应`ps`命令中的`COMM`列 | `cat /proc/<pid>/comm` |
| `pid_links` | `struct hlist_node` | 用于PID哈希表的链表节点，内核通过PID查找task_struct | `hash PID` |

#### 1.1.2 进程树字段

| 字段 | 类型 | 含义 | 调试方法 |
|------|------|------|--------|
| `parent` | `struct task_struct*` | 指向父进程的task_struct | `cat /proc/<pid>/status | grep PPid` |
| `children` | `struct list_head` | 子进程链表的头节点 | `pstree -p <pid>` |
| `sibling` | `struct list_head` | 用于将子进程链接到父进程的children链表 | `ls /proc/<pid>/task/` |
| `real_parent` | `struct task_struct*` | 真实的父进程指针，不受`ptrace`影响 | `cat /proc/<pid>/status | grep real_parent` |

#### 1.1.3 状态字段

| 字段 | 类型 | 含义 | 调试方法 |
|------|------|------|--------|
| `__state` | `long` | 进程的当前状态，比如`TASK_RUNNING`、`TASK_INTERRUPTIBLE`等 | `cat /proc/<pid>/status | grep State` |
| `flags` | `unsigned long` | 进程的标志位，比如`PF_EXITING`、`PF_KTHREAD`等 | `cat /proc/<pid>/status | grep Flags` |
| `exit_state` | `int` | 进程的退出状态，当进程退出时设置 | `cat /proc/<pid>/status | grep Exit` |

#### 1.1.4 调度字段

| 字段 | 类型 | 含义 | 调试方法 |
|------|------|------|--------|
| `prio` | `int` | 动态优先级，调度器使用这个字段来选择下一个要运行的进程 | `chrt -p <pid>` |
| `static_prio` | `int` | 静态优先级，由`nice`值设置 | `cat /proc/<pid>/sched | grep static_prio` |
| `normal_prio` | `int` | 正常优先级，基于静态优先级和调度类计算得出 | `cat /proc/<pid>/sched | grep normal_prio` |
| `sched_class` | `struct sched_class*` | 指向调度类的指针，比如`fair_sched_class`、`rt_sched_class`等 | `cat /proc/<pid>/sched | grep sched_class` |
| `cpus_ptr` | `const struct cpumask*` | 指向CPU掩码的指针，决定这个进程可以在哪些CPU上运行 | `taskset -p <pid>` |
| `thread` | `struct thread_struct*` | 指向thread_struct的指针，保存了CPU寄存器的上下文，用于上下文切换 | `cat /proc/<pid>/stack` |

#### 1.1.5 统计字段

| 字段 | 类型 | 含义 | 调试方法 |
|------|------|------|--------|
| `utime` | `unsigned long` | 进程在用户态运行的时间，单位是jiffies | `cat /proc/<pid>/stat | cut -d ' ' -f 14` |
| `stime` | `unsigned long` | 进程在内核态运行的时间，单位是jiffies | `cat /proc/<pid>/stat | cut -d ' ' -f 15` |
| `nivcsw` | `unsigned long` | 进程的自愿上下文切换次数，比如进程等待IO时 | `cat /proc/<pid>/stat | cut -d ' ' -f 11` |
| `nvcsw` | `unsigned long` | 进程的非自愿上下文切换次数，比如时间片用完时被抢占 | `cat /proc/<pid>/stat | cut -d ' ' -f 12` |
| `min_flt` | `unsigned long` | 进程的次要缺页中断次数，比如从页缓存中读取页面 | `cat /proc/<pid>/stat | cut -d ' ' -f 9` |
| `maj_flt` | `unsigned long` | 进程的主要缺页中断次数，比如从磁盘中读取页面 | `cat /proc/<pid>/stat | cut -d ' ' -f 10` |

> 📄 完整的私有字段详解请参考 **[task-resources/task-private.md](/concepts/process/task-resources/task-private.md)**，包含了所有字段的详细解释和代码示例。

### 1.2 资源对象指针（可被多个 task 共享）

以下资源对象被拆成独立结构,由 `task_struct` 用指针指向。同进程线程共享指针、fork 复制指针所指对象：

| 资源对象 | 内核结构 | `task->字段` | 详解文档 | 一句话 |
|---------|---------|-------------|---------|--------|
| 地址空间 | `mm_struct` | `mm` | [mm-struct.md](/concepts/process/task-resources/mm-struct.md) | VMA 集合+页表根+段边界；线程共享/fork 复制(CoW) |
| 打开文件表 | `files_struct` | `files` | [files-struct.md](/concepts/process/task-resources/files-struct.md) | fd→file→inode 三层；偏移在 file 层,决定 dup/fork 语义 |
| 文件系统上下文 | `fs_struct` | `fs` | [fs-struct.md](/concepts/process/task-resources/fs-struct.md) | pwd/root/umask；线程共享(fork 复制) |
| 信号 | `sighand_struct` / `signal_struct` | `sighand` / `signal` | [signals-kernel.md](/concepts/process/task-resources/signals-kernel.md) · [signals-alternatives.md](/concepts/process/task-resources/signals-alternatives.md) | 处理动作表(共享)+未决信号(分共享/私有)；高性能替代方案(signalfd/eventfd/timerfd/futex) |
| 凭证 | `cred` | `cred` | [cred.md](/concepts/process/task-resources/cred.md) | uid/gid/capabilities；四种 uid 为何存在 |
| 命名空间+限额 | `nsproxy` + cgroup | `nsproxy` | [namespaces-cgroups.md](/concepts/process/task-resources/namespaces-cgroups.md) | 容器底座:隔离视图+资源限额 |
| IO 上下文 | `io_context` | `io_context` | [io-context.md](/concepts/process/task-resources/io-context.md) §一 | AIO 上下文+IO 调度优先级；线程共享 |
| 系统调用过滤 | `seccomp` | `seccomp` | [io-context.md](/concepts/process/task-resources/io-context.md) §二 | BPF 过滤器,只能加严；Chrome/Docker 沙箱基石 |
| Futex 健壮列表 | `robust_list_head` | `robust_list` | [io-context.md](/concepts/process/task-resources/io-context.md) §四 | 线程死后内核代解锁 |

---

#### 1.2.1 地址空间（mm_struct）

`task->mm` 指向 `mm_struct`，描述进程的虚拟内存地址空间。每个进程的虚拟内存空间都是独立的，通过页表映射到物理内存。

一个进程的地址空间包含多个段（代码段、数据段、堆、mmap 映射区、栈等），每个段对应一个 `vm_area_struct`（VMA）。所有 VMA 通过**双向链表 + 红黑树**双重索引组织：

- **双向链表** `vm_next` / `vm_prev`：按地址升序串联所有 VMA，遍历所有段时使用
- **红黑树** `vm_rb`：以起始地址为 key，实现按地址的 O(log n) 快速查找，缺页中断时内核需要快速定位目标地址属于哪个 VMA

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `pgd` | `pgd_t*` | 页全局目录指针，进程页表的根，切换时加载到 CR3 |
| `mmap` | `struct vm_area_struct*` | VMA 双向链表头，按地址升序串联所有段 |
| `mm_rb` | `struct rb_root` | VMA 红黑树根，按地址快速查找 O(log n) |
| `total_vm` | `unsigned long` | 虚拟内存总页面数 |
| `start_code` / `end_code` | `unsigned long` | 代码段起止地址（快捷指针，指向已有的 VMA） |
| `start_stack` | `unsigned long` | 用户态栈起始地址 |
| `brk` | `unsigned long` | 堆（heap）边界地址，`sbrk()` 移动此值 |
| `mm_users` | `atomic_t` | 使用该 mm 的线程数，最后一个线程退出时释放 |
| `mm_count` | `atomic_t` | 总引用计数，含内核 lazy 借用场景 |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #FFE0B2
  BorderColor #EF6C00
}
class "mm_struct" as MM {
  + pgd : pgd_t*
  + mmap : vm_area_struct*
  + mm_rb : rb_root
  + total_vm : unsigned long
  + start_code / end_code
  + start_stack : unsigned long
  + brk : unsigned long
  + mm_users / mm_count
}
class "vm_area_struct" as VMA_TEMPLATE {
  + vm_start / vm_end : unsigned long
  + vm_flags : PROT_READ|WRITE|EXEC
  + vm_next / vm_prev : vm_area_struct*
  + vm_rb : rb_node
  + vm_ops : vm_operations_struct*
  + vm_file : file*
}
MM --> VMA_TEMPLATE : 1..* (链表+红黑树索引)
rectangle "进程虚拟地址空间\n(低位→高位)" as ADDR_SPACE #E8F5E9 {
  rectangle "VMA-1: 代码段\n.text R-X  vm_file=a.out" as VMA1 #C8E6C9
  rectangle "VMA-2: 数据段\n.data+.bss RW-  vm_file=a.out" as VMA2 #A5D6A7
  rectangle "VMA-3: 堆\nRW-  vm_file=NULL  brk扩展" as VMA3 #81C784
  rectangle "VMA-4: mmap映射\nlibc.so R-X  vm_file=libc.so" as VMA4 #66BB6A
  rectangle "VMA-5: 匿名mmap\nRW-  vm_file=NULL" as VMA5 #4CAF50
  rectangle "VMA-6: 栈\nRW-  vm_file=NULL  向低地址增长" as VMA6 #388E3C
}
VMA1 -[hidden]right-> VMA2
VMA2 -[hidden]right-> VMA3
VMA3 -[hidden]right-> VMA4
VMA4 -[hidden]right-> VMA5
VMA5 -[hidden]right-> VMA6
note bottom of ADDR_SPACE
链表: VMA1-VMA6 按地址升序
红黑树: vm_start 为 key, O(log n) 定位
缺页/brk/mmap/munmap 均需在此链操作
end note
@enduml
```

- 🏷️ 关键特性：
  - 每个段（代码、数据、堆、mmap、栈…）都是一个 `vm_area_struct`，按起始地址升序串成双向链表
  - 红黑树 `mm_rb` 以 `vm_start` 为 key，缺页中断或 `mmap`/`munmap` 时 O(log n) 定位 VMA
  - `cat /proc/PID/maps` 可以看到进程所有 VMA 的起止地址、权限和映射文件
  - 页全局目录`pgd`是进程页表的根，切换进程时需要将`pgd`加载到CR3寄存器
  - `mm_users`记录使用这个mm_struct的线程数，当最后一个线程退出时释放mm_struct
  - `mm_count`记录总引用数，包括内核的lazy借用
  - 写时复制（CoW）：fork时复制mm_struct，但物理页面共享，当有线程修改页面时才复制新的物理页面

#### 1.2.2 打开文件表（files_struct）

`task->files` 指向 `files_struct`，管理进程所有打开的文件。核心是一张动态扩容的 fd 指针数组，下标是文件描述符。

文件描述符的组织是**三层引用链**：

- **第 1 层 files_struct**：fd 数组本身，按 fd 编号索引到 `struct file*`。fd 0/1/2 固定为 stdin/stdout/stderr，超过 `max_fds` 时数组自动扩容
- **第 2 层 struct file**：代表"一次打开"的会话对象，持有独立的读写偏移 `f_pos` 和打开标志 `f_flags`。`dup()` 创建的新 fd 指向**同一个** `struct file`，共享偏移
- **第 3 层 inode**：磁盘文件本体，无论被打开多少次，系统中只有一份

**files_struct 核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `fd_array` | `struct file**` | fd 指针数组，0→stdin、1→stdout、2→stderr |
| `nr_open` | `int` | 当前已打开的文件数量 |
| `max_fds` | `unsigned int` | 数组最大容量，超限时自动扩容 |
| `fdtab` | `struct fdtable` | 内嵌 fd 表，含 `max_fds` 和 `close_on_exec` 位图 |

**struct file 核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `f_pos` | `loff_t` | 当前读写偏移位置，`dup` 创建的 fd 共享此字段 |
| `f_flags` | `unsigned int` | 打开模式 `O_RDONLY`/`O_WRONLY`/`O_RDWR`/`O_APPEND` 等 |
| `f_op` | `struct file_operations*` | 文件操作函数表，由具体文件系统实现 |
| `f_inode` | `struct inode*` | 指向磁盘文件本体 |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #FFF9C4
  BorderColor #F9A825
}
class "files_struct" as FILES {
  + fd_array : file**
  + nr_open : int
  + max_fds : unsigned int
  + fdtab : fdtable
}
class "struct file\n(第2层：会话对象)" as FILE {
  + f_pos : loff_t
  + f_flags : O_RDONLY|O_WRONLY|...
  + f_op : file_operations*
  + f_inode : inode*
}
class "inode\n(第3层：文件实体)" as INODE {
  + i_mode : umode_t
  + i_uid / i_gid
  + i_size : loff_t
}
FILES --> FILE : fd[0]=stdin  fd[1]=stdout\nfd[2]=stderr  fd[3]=app.log
FILE --> INODE : f_inode 指向
note bottom of FILES
三层引用语义:
不同 fd 可共指同一 file (dup) -- 共享 f_pos
不同 file 可共指同一 inode (两次 open) -- 各自独立 f_pos
end note
@enduml
```

- 🏷️ 关键特性：
  - `dup()`/`dup2()` 创建的新 fd 指向同一个 `struct file`，共享读写偏移
  - 两次独立 `open()` 同一文件产生两个 `struct file`，各自维护独立的 `f_pos`
  - `close_on_exec`：fd 可设 `FD_CLOEXEC` 标志，`execve` 时自动关闭，防止泄漏给新程序
  - 进程退出时，内核遍历 `files_struct` 关闭所有 fd

#### 1.2.3 文件系统上下文（fs_struct）

`task->fs` 指向 `fs_struct`，记录进程在文件系统中的"立足点"——当前目录、根目录和权限掩码。它决定了进程如何解析路径名和创建文件。

`root` 和 `pwd` 存的不是字符串，而是 `struct path = {vfsmount, dentry}`：

- **dentry**（目录项缓存）：指向目录的 inode 和父目录 dentry，即使目录已被删除，只要 dentry 还在缓存，进程仍可通过相对路径找到它
- **vfsmount**（挂载点）：记录该路径所在文件系统的挂载信息，跨挂载点遍历时需要此信息

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `root` | `struct path` | 进程眼中的文件系统根目录 `"/"`（含 dentry + vfsmount） |
| `pwd` | `struct path` | 当前工作目录，所有相对路径解析的起点 |
| `umask` | `umode_t` | 新建文件/目录的默认权限掩码，实际权限 = `mode & ~umask` |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #E3F2FD
  BorderColor #1976D2
}
class "fs_struct\n文件系统上下文" as FS {
  + root : path
  + pwd : path
  + umask : umode_t
}
class "struct path\n路径描述" as PATH {
  + dentry : dentry*
  + mnt : vfsmount*
}
class "struct dentry\n目录项缓存" as DENTRY {
  + d_inode : inode*
  + d_parent : dentry*
  + d_name : 目录名
}
FS --> PATH : root
FS --> PATH : pwd
PATH --> DENTRY : dentry
note bottom of FS
路径解析原理:
chdir("/home/user") -> pwd.dentry 指向目标 dentry
相对路径 "a.txt" -> 从 pwd.dentry 出发逐级查找
chroot("/container") -> root.dentry 指向 /container
end note
@enduml
```

- 🏷️ 关键特性：
  - 同进程的线程共享同一个 `fs_struct`（`CLONE_FS`），一个线程 `chdir` 会影响所有线程
  - fork 默认复制 `fs_struct`，父子进程的文件系统上下文独立
  - `chroot` 只改 `root` 而不改 `pwd`，root 用户可通过 `chdir("..")` 逃逸；容器用 `pivot_root` + 挂载命名空间实现更彻底的隔离

#### 1.2.4 信号处理（sighand_struct / signal_struct）

信号管理被**拆成两个独立结构**，各司其职：

| 指针 | 指向结构 | 职责 |
|------|---------|------|
| `task->sighand` | `sighand_struct` | **信号处理动作表**（"收到信号时怎么处理"） |
| `task->signal` | `signal_struct` | **线程组共享状态**（未决信号队列 + 资源限制） |

此外每个 `task_struct` 自身还持有私有的信号字段：`blocked`（阻塞掩码）和 `pending`（本线程私有的未决信号）。

**"信号投递给谁"的分流规则**：

- 发给**整个进程**的信号（如 `kill -15 pid`）→ 放入 `signal->shared_pending`，由线程组中任意一个未阻塞该信号的线程处理
- 发给**特定线程**的信号（如 `pthread_kill(tid, SIGSEGV)`、线程自身触发的异常）→ 放入该线程的 `task->pending`
- 处理时按优先级：先检查私有 `pending`，再检查共享 `shared_pending`

**sighand_struct 核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `action[_NSIG]` | `struct k_sigaction[64]` | 64 个信号的处理器数组，含 `sa_handler`（处理函数）和 `sa_flags` |
| `siglock` | `spinlock_t` | 保护信号处理表的自旋锁 |

**signal_struct 核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `shared_pending` | `struct sigpending` | 线程组共享的未决信号队列 |
| `rlim[RLIM_NLIMITS]` | `struct rlimit[]` | 资源限制数组（文件数、CPU 时间、内存等） |
| `tgid` | `pid_t` | 线程组 ID |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #C8E6C9
  BorderColor #388E3C
}
class "sighand_struct\n信号处理动作表\n(线程组共享)" as SH {
  + action[0..63] : k_sigaction
  + siglock : spinlock_t
}
class "signal_struct\n线程组信号状态\n(线程组共享)" as SIG {
  + shared_pending : sigpending
  + rlim[RLIM_NLIMITS]
  + tgid : pid_t
}
class "task_struct\n(每个线程)" as TS {
  + sighand : sighand_struct*
  + signal : signal_struct*
  + blocked : sigset_t
  + pending : sigpending
}
TS --> SH : task->sighand
TS --> SIG : task->signal
note bottom of TS
信号投递分流:
kill(pid, SIGTERM) -> shared_pending (任意线程处理)
pthread_kill(tid, SIG) -> task->pending (目标线程私有)
线程先检查私有pending, 再检查共享shared_pending
end note
@enduml
```

- 🏷️ 关键特性：
  - 同进程的线程共享 `sighand_struct` 和 `signal_struct`
  - 每个线程有自己的 `blocked` 掩码，可独立阻塞/放行信号
  - 未决信号分共享和私有两种队列，保证"发给进程"和"发给线程"语义正确
  - `SIGKILL`/`SIGSTOP` 不可被捕获或忽略，也不受 `blocked` 影响

#### 1.2.5 凭证与权限（cred）

`task->cred` 指向 `cred_struct`，包含进程的用户/组身份和特权能力。这是 Linux 权限模型的载体——打开文件、发送信号、执行特权操作，全部依赖这张凭证。

**四种 UID 的分工**：

所有权限检查只看 `euid`（effective UID），其他三种 UID 是为 `euid` 在不同场景间安全切换服务的：

1. **real UID**：进程的"真实身份"，继承自父进程，几乎不变
2. **effective UID**：用于**所有权限检查**——打开文件、执行系统调用、发送信号，内核只看它
3. **saved UID**：setuid 程序的"旧 euid 备份"，允许程序在 root 和原用户之间来回切换
4. **fs UID**：专用于文件系统访问检查，默认等于 euid，可独立调整而不影响其他权限

> 典型 setuid 流程：普通用户执行 `passwd`（owner=root, setuid 位）→ euid 变成 root → 改完 `/etc/shadow` → `seteuid(saved_uid)` 切回原用户，放弃特权

**Capabilities 的分层设计**：

| 能力集 | 含义 |
|------|------|
| `cap_inheritable` | exec 新程序时，能被继承过去的能力 |
| `cap_permitted` | 进程"被允许拥有"的能力池，是所有有效能力的超集 |
| `cap_effective` | 当前**实际生效**的能力，每次权限检查看这个 |
| `cap_bset` / `cap_ambient` | 边界集（上限锁）/ 环境集（exec 时自动传递） |

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `uid` / `gid` | `kuid_t` / `kgid_t` | 真实用户/组 ID |
| `euid` / `egid` | `kuid_t` / `kgid_t` | 有效用户/组 ID，**所有权限检查以此为据** |
| `suid` / `sgid` | `kuid_t` / `kgid_t` | 保存的 UID/GID，setuid 程序切换身份的"备份" |
| `fsuid` / `fsgid` | `kuid_t` / `kgid_t` | 文件系统 UID/GID，专用于文件访问检查 |
| `cap_inheritable` | `kernel_cap_t` | exec 时继承的能力集 |
| `cap_permitted` | `kernel_cap_t` | 允许使用的能力超集 |
| `cap_effective` | `kernel_cap_t` | 当前有效的能力集，实际权限检查用 |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #F3E5F5
  BorderColor #7B1FA2
}
class "cred_struct\n进程凭证" as CRED {
  + uid / gid (真实身份)
  + euid / egid (有效身份)
  + suid / sgid (setuid备用)
  + fsuid / fsgid (文件系统专用)
  --
  + cap_permitted (能力池)
  + cap_effective (当前生效能力)
  + cap_inheritable (可继承能力)
}
class "task_struct" as TS {
  + cred : cred_struct*
}
TS --> CRED : task->cred
note bottom of CRED
核心公式:
euid=0(root)           -> 超级用户
euid!=0 + cap_effective=0 -> 普通用户，按 euid/egid 检查
euid!=0 + cap_effective!=0 -> 拥有部分 root 特权
end note
@enduml
```

- 🏷️ 关键特性：
  - 内核**只看 euid** 做权限检查，real/saved/fs uid 只是辅助切换角色
  - 四种 UID 让 setuid 程序可以在 root 和普通用户之间安全来回切换
  - capabilities 将 root 全权拆分为 40+ 个独立位（`CAP_SYS_ADMIN`、`CAP_NET_RAW` 等），实现最小权限
  - `cap_permitted` 是进程能拥有的能力的上限，`cap_effective` 是当前实际在用的子集，可以临时关掉不需要的能力

#### 1.2.6 命名空间与控制组（nsproxy + cgroup）

`task->nsproxy` 和 `task->cgroups` 共同构成容器的内核底座——**命名空间隔离"看到什么"**，**cgroup 限额"能用多少"**。

**命名空间的组织方式**：`nsproxy` 是一个"代理"结构，内嵌 8 个命名空间指针。所有命名空间通过一个 `nsproxy` 统一管理，fork 时如果不想创建新命名空间，则直接共享 `nsproxy`，引用计数 +1。

**nsproxy 核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `ns[NR_NAMESPACES]` | `struct ns_common*[]` | 命名空间数组，含 PID/NET/MNT/UTS/IPC/USER/CGROUP/TIME 共 8 种 |
| `count` | `atomic_t` | 引用计数，共享本 nsproxy 的 task 数量 |

**8 种命名空间一览**：

| 命名空间 | 隔离内容 | clone flag | 典型场景 |
|---------|---------|------------|---------|
| PID | 进程 ID 编号 | `CLONE_NEWPID` | 容器内 PID 1 是宿主机上的普通进程 |
| NET | 网络设备、IP、端口、路由表 | `CLONE_NEWNET` | 每个容器有独立网卡和 IP |
| MNT | 文件系统挂载点视图 | `CLONE_NEWNS` | 容器有自己的 `/` 挂载视图 |
| UTS | 主机名和域名 | `CLONE_NEWUTS` | 每个容器可设独立 hostname |
| IPC | System V IPC、POSIX 消息队列 | `CLONE_NEWIPC` | 防止容器间 IPC 通信 |
| USER | 用户/组 ID 映射 | `CLONE_NEWUSER` | 非 root 用户在容器内可以是 root |
| CGROUP | cgroup 层级视图 | `CLONE_NEWCGROUP` | 容器内的 `cat /proc/self/cgroup` 看到虚路径 |
| TIME | 系统时钟（启动时间、单调时间） | `CLONE_NEWTIME` | 容器可拥有独立的"开机时间" |

**cgroup 的组织方式**：`task->cgroups` 指向 `css_set`，记录该任务在所有 cgroup 子系统中的成员位置。cgroup 采用**层级树**结构，每个子系统独立建树，`css_set` 是 task 在各子树中的位置快照。

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #E1BEE7
  BorderColor #7B1FA2
}
class "nsproxy\n命名空间代理\n(count: 引用计数)" as NSP {
  + ns[NR_NAMESPACES] : ns_common*
}
class "cgroup_subsys_state\n(每个子系统一份)" as CSS
class "css_set\n进程所属cgroup集合\n(= 在各子树中的位置)" as CS
class "task_struct" as TS {
  + nsproxy : nsproxy*
  + cgroups : css_set*
}
TS --> NSP : task->nsproxy (隔离"看到什么")
TS --> CS : task->cgroups (限额"能用多少")
note bottom of NSP
nsproxy 的代理模式:
8 个命名空间指针打包在一个 nsproxy 中
nsproxy 可被多个 task 共享(引用计数)
unshare() 创建新 nsproxy: 拷贝旧指针再替换目标命名空间
end note
@enduml
```

- 🏷️ 关键特性：
  - 命名空间实现进程的视图隔离——同一组内核资源，在不同 ns 中看到不同的"名称"
  - cgroup 实现进程的资源限额——将 task 分组，每组配置 CPU/内存/IO 上限
  - 容器 = 命名空间（隔离视图）+ cgroup（限额资源）+ seccomp（过滤系统调用）+ pivot_root（隔离文件系统）

#### 1.2.7 IO上下文（io_context）

`task->io_context` 指向 `io_context`，管理进程的异步 IO 请求和 IO 调度优先级。它是块设备 IO 调度器的进程级控制入口。

**两种职责的分工**：

- **AIO 请求跟踪**：当进程调用 `io_submit()` 发起异步 IO，内核在 `io_context` 中创建 `aio_kiocb` 记录请求状态。这个链表是内核 IO 完成时查找回调函数的依据
- **IO 调度优先级**：通过 `ionice` 系统调用写入 `ioprio` 字段，分 RT / Best-effort / Idle 三级，影响 CFQ、BFQ 等调度器分配给该进程的 IO 时间片

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `ioprio` | `unsigned short` | IO 调度优先级，`ionice` 设置，分 RT/BE/IDLE 三级，每级 0~7 子级 |
| `aio_ring` | `struct aio_ring*` | AIO 环形缓冲区，用于内核→用户态传递完成事件 |
| `refcount` | `atomic_t` | 引用计数，`CLONE_IO` 的线程共享时 +1 |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #FFE0B2
  BorderColor #EF6C00
}
class "io_context\nAIO上下文 + IO优先级" as IOC {
  + ioprio : unsigned short
  + aio_ring : aio_ring*
  + refcount : atomic_t
}
class "aio_kiocb\n异步IO请求" as AIO {
  + ki_filp : file*
  + ki_buf : 数据缓冲区
  + ki_nbytes : 数据长度
  + ki_pos : 文件偏移
}
class "task_struct" as TS {
  + io_context : io_context*
}
TS --> IOC : task->io_context
IOC --> AIO : AIO 请求队列
note bottom of IOC
双重职责:
ionice -p <pid> -> 读/写 ioprio
io_submit() -> AIO 请求挂入 io_context
共享规则: CLONE_IO 共享, fork 继承引用计数
end note
@enduml
```

- 🏷️ 关键特性：
  - `io_submit`/`io_getevents` 的 AIO 请求全部挂在 `io_context` 中，内核完成时通过 `aio_ring` 通知用户态
  - `ionice` 分三级：RT（最高，独占 IO 带宽）> Best-effort（默认，0~7 子级）> Idle（仅闲时 IO）
  - 同进程线程通过 `CLONE_IO` 共享 `io_context`，IO 优先级统一

#### 1.2.8 seccomp 系统调用过滤

`task->seccomp` 指向 `seccomp_filter` 链表，在每个系统调用入口处拦截、检查、过滤。它是进程沙箱的最后一道防线——在系统调用真正执行前完成 BPF 规则匹配。

**组织方式**：seccomp 过滤器是一个**单向链表**，每个节点包含一条 BPF 程序和对应的过滤模式。多个过滤器可以叠加，内核依次执行每个过滤器中的 BPF 程序，按"最严格结果"原则生效。一旦设置了第一个过滤器，只能向后追加更严格的规则，不能移除。

**两种模式**：

| 模式 | 行为 | 适用场景 |
|------|------|---------|
| `SECCOMP_SET_MODE_STRICT` | 仅允许 `read`/`write`/`_exit`/`sigreturn`，其他调用直接 `SIGKILL` | 极简沙箱 |
| `SECCOMP_SET_MODE_FILTER` | 加载自定义 BPF 程序，按规则匹配系统调用号和参数 | Chrome、Docker 等复杂沙箱 |

**BPF 返回值及含义**：

| 返回值 | 行为 |
|------|------|
| `SECCOMP_RET_ALLOW` | 放行，执行系统调用 |
| `SECCOMP_RET_ERRNO` | 返回指定错误码（如 `EPERM`），不执行系统调用 |
| `SECCOMP_RET_TRAP` | 向进程发送 `SIGSYS` 信号 |
| `SECCOMP_RET_KILL` | 直接杀死进程 |

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `filter` | `struct seccomp_filter*` | 过滤器链表头，每节点含 BPF 程序和过滤模式 |
| `mode` | `int` | 当前模式：0=禁用 / 1=STRICT / 2=FILTER |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #FCE4EC
  BorderColor #C62828
}
class "task_struct" as TS {
  + seccomp : seccomp_filter*
}
class "seccomp_filter\n过滤器节点 (链表)" as SF {
  + prog : bpf_prog*
  + prev : seccomp_filter*
}
class "bpf_prog\nBPF 字节码程序" as BPF {
  + insns : 指令数组
  + len : 指令数量
}
TS --> SF : task->seccomp
SF --> BPF : prog
SF --> SF : prev (向前链接)
note bottom of SF
过滤器链: 链表节点依次执行
所有节点返回 ALLOW -> 放行
任一节点返回 KILL/ERRNO/TRAP -> 立即终止
fork 继承 / exec 保留, 只能加严不能放宽
end note
@enduml
```

- 🏷️ 关键特性：
  - seccomp 过滤器只能追加、不能移除——安全不可逆，**只能加严不能放宽**
  - BPF 程序可以检查系统调用号、参数值和架构，实现精细过滤
  - fork 子进程继承过滤器，exec 新程序保留过滤器，沙箱贯穿整个进程生命周期
  - Chrome 渲染进程和 Docker 容器默认使用 seccomp，拦截危险系统调用（如 `clone`、`mount`）

#### 1.2.9 Futex 健壮列表（robust_list）

`task->robust_list` 指向 `robust_list_head`，解决多线程编程中的致命问题：**持有锁的线程异常退出 → 等待者永久死锁**。

**工作流程**：

1. **注册**：线程获取 robust mutex 时，glibc 将该锁的用户态地址写入 `robust_list` 链表
2. **检测**：线程异常退出（被信号杀死、段错误等），内核在 `do_exit()` 中遍历 `robust_list`，找出所有该线程持有的 futex 锁
3. **代解锁**：内核执行 `futex(WAKE)` 标记锁为 `FUTEX_OWNER_DIED`，唤醒等待队列中的线程
4. **恢复**：被唤醒的线程收到 `EOWNERDEAD`，知道锁的前任持有者已死亡，可检查共享数据一致性并继续

**核心字段**：

| 字段 | 类型 | 含义 |
|------|------|------|
| `robust_list` | `struct robust_list_head*` | 健壮锁链表头指针，每个线程一条链表 |
| `futex_offset` | `long` | futex 字段在锁结构内部的偏移量 |

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #E8EAF6
  BorderColor #3949AB
}
class "task_struct\n(线程 T1)" as TS1 {
  + robust_list : robust_list_head*
}
class "task_struct\n(线程 T2)" as TS2 {
  + robust_list : robust_list_head*
}
rectangle "robust_list_head\n(T1 的锁链表)" as RL
rectangle "Futex 锁\nmutex-A\n持有者=T1" as MUTEX_A
rectangle "Futex 锁\nmutex-B\n持有者=T1" as MUTEX_B
rectangle "等待队列\nT2 阻塞在 mutex-A" as WAITQ
TS1 --> RL : task->robust_list
RL --> MUTEX_A : 持有
RL --> MUTEX_B : 持有
MUTEX_A --> WAITQ : T2 等待中
TS2 --> WAITQ : T2 阻塞
note bottom of TS1
T1 异常退出时:
do_exit() -> 遍历 robust_list
-> 找到 mutex-A, mutex-B
-> futex(WAKE, FUTEX_OWNER_DIED) 唤醒 T2
-> T2 收 EOWNERDEAD -> 检查数据 -> 恢复或放弃
end note
@enduml
```

- 🏷️ 关键特性：
  - 线程异常退出时，内核自动代解锁所有健壮 futex 锁，防止等待者永久死锁
  - 被唤醒者收到 `EOWNERDEAD`，知道数据可能处于半修改状态，需要自行一致性检查
  - 每个线程维护自己的 `robust_list`，只记录本线程持有的锁

## 二、资源对象与共享行为速查

§1.2.1~1.2.9 已逐项剖析了各资源对象的结构、字段表和 PlantUML 类图，本节仅汇总 **fork 与线程的共享行为差异**——同一资源对象，两种创建方式处理完全不同：

| § | 资源对象 | fork 行为 | 同进程线程 | 关键后果 |
|---|---------|----------|-----------|---------|
| 1.2.1 | mm_struct | 复制(CoW) | 共享(`CLONE_VM`) | 线程间指针互传、全局变量互见 |
| 1.2.2 | files_struct | 复制fd表(条目共指同一file) | 共享(`CLONE_FILES`) | fork后父子共享读写偏移 |
| 1.2.3 | fs_struct | 复制 | 共享(`CLONE_FS`) | 一个线程chdir全员cwd变 |
| 1.2.4 | sighand / signal | 复制 | 共享(`CLONE_SIGHAND`/`CLONE_THREAD`) | 信号处理动作全线程组一致 |
| 1.2.5 | cred | 复制(继承) | 共享 | setuid影响所有线程 |
| 1.2.6 | nsproxy | 默认继承(可`CLONE_NEW*`) | 共享 | unshare仅改变当前task的ns |
| 1.2.7 | io_context | 继承(引用+1) | 共享(`CLONE_IO`) | IO优先级全线程统一 |
| 1.2.8 | seccomp | 继承(只能加严) | 共享 | 沙箱贯穿进程生命周期 |
| 1.2.9 | robust_list | **清空** | 每线程独立 | 子进程不继承父的futex锁 |

> 📄 各资源的结构、字段、组织方式详见 §1.2.1~1.2.9；clone flag 完整对照表见 §三；每类资源的逐篇深度剖析见 [task-resources/](/concepts/process/task-resources/README.md)。

## 三、进程 vs 线程：完整共享规则总表

同一套 `clone` 调用,决定这些指针**指向新对象**（进程）还是**指向同一对象**（线程）。共享得越多越像线程,越少越像进程。

| 资源对象 | 内核结构 | fork 子进程 | 同进程线程 | clone flag |
|---------|---------|------------|-----------|-----------|
| 地址空间 | `mm_struct` | 复制(CoW) | **共享** | `CLONE_VM` |
| 打开文件表 | `files_struct` | 复制 | **共享** | `CLONE_FILES` |
| 文件系统上下文 | `fs_struct` | 复制 | **共享** | `CLONE_FS` |
| 信号处理表 | `sighand_struct` | 复制 | **共享** | `CLONE_SIGHAND` |
| 线程组信号状态 | `signal_struct` | 复制 | **共享** | `CLONE_THREAD` |
| 凭证 | `cred` | 复制(继承) | 共享 | — |
| 命名空间 | `nsproxy` | 默认继承(可 `CLONE_NEW*`) | 共享 | `CLONE_NEW*` |
| IO 上下文 | `io_context` | 继承(引用+1) | **共享** | `CLONE_IO` |
| seccomp filter | `seccomp` | 继承(只加严) | **共享** | `CLONE_SECCOMP` |
| robust_list | `robust_list_head` | **清空** | 每线程独立 | — |
| rseq | `rseq` | **不继承** | 每线程独立注册 | — |
| 私有字段 | pid/state/调度/统计... | 复制/清零 | 各自独立 | — |

### fork 与 clone 时的完整场景图

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<p>> #E3F2FD
  BorderColor<<p>> #1976D2
  BackgroundColor<<c>> #FFE0B2
  BorderColor<<c>> #EF6C00
  BackgroundColor<<obj>> #C8E6C9
  BorderColor<<obj>> #388E3C
}
rectangle "父 task (pid=1000)" <<p>> as PARENT
rectangle "子 task (pid=1001)\nfork 创建的结果" <<c>> as CHILD
rectangle "父的 mm_struct\n(地址空间)" <<obj>> as PMM
rectangle "父的 files_struct\n(fd 表)" <<obj>> as PF
rectangle "父的 fs_struct\n(pwd/root)" <<obj>> as PFS
rectangle "子的 mm_struct (复制)" <<obj>> as CMM
rectangle "子的 files_struct (复制)" <<obj>> as CF
PARENT --> PMM : mm
PARENT --> PF : files
PARENT --> PFS : fs
CHILD --> CMM : mm (新,CoW)
CHILD --> CF : files (新,\n但条目指向**同一批 file**)
CHILD --> PFS : fs (同一份!\n~CLONE_FS时)
note bottom of PMM : 父子有各自的 mm_struct\n→ 地址空间隔离
note bottom of PF : 父子有各自的 files_struct\n但 fd 数组项指向**同一个 struct file**\n→ 共享读写偏移
note bottom of PFS : 如果没有 CLONE_FS\n子会复制一份 fs_struct
@enduml
```

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<t>> #E3F2FD
  BorderColor<<t>> #1976D2
  BackgroundColor<<obj>> #C8E6C9
  BorderColor<<obj>> #388E3C
}
rectangle "线程组 (用户眼里的一个'进程')\ntgid=1000" {
  rectangle "主线程 T1\npid=1000" <<t>> as T1
  rectangle "线程 T2\npid=1001" <<t>> as T2
  rectangle "线程 T3\npid=1002" <<t>> as T3
}
rectangle "mm_struct\n(一份, 三人共享)" <<obj>> as MM
rectangle "files_struct\n(一份, 三人共享)" <<obj>> as FILES
rectangle "fs_struct\n(一份, 三人共享)" <<obj>> as FS
T1 --> MM
T2 --> MM
T3 --> MM
T1 --> FILES
T2 --> FILES
T3 --> FILES
T1 --> FS
T2 --> FS
T3 --> FS
note bottom of T1 : clone(CLONE_VM|CLONE_FILES|CLONE_FS|CLONE_SIGHAND|CLONE_THREAD|...)\n→ 几乎所有资源对象指针都共指同一份
@enduml
```

> 更完整的 fork 过程（CoW、fd 复制、信号处理）见 [process-creation.md](/concepts/process/process-creation.md)；线程创建细节见 [thread-creation.md](/concepts/process/thread-creation.md)。

## 四、pid vs tgid：内核怎么看"线程组"

用户说的"进程 PID",内核里叫 **`tgid`（thread group id）**；用户说的"线程 TID",才是内核的 **`pid`**。一个多线程进程 = 一组共享 tgid 的 task：

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor #E3F2FD
  BorderColor     #1976D2
}
rectangle "线程组 (用户眼里的一个'进程', tgid=1000)" {
  rectangle "主线程\npid=1000\ntgid=1000\ngroup_leader" as A
  rectangle "线程2\npid=1001\ntgid=1000" as B
  rectangle "线程3\npid=1002\ntgid=1000" as C
}
note bottom : getpid() 返回 tgid(1000); gettid() 返回 pid(各自)\n`ps` 默认按 tgid 聚合; `ps -L` / `/proc/1000/task/` 才看到每个 tid
@enduml
```

> 这解释了为什么 `top -H`、`ps -L`、`/proc/<pid>/task/<tid>` 能把一个进程"摊开"成多个线程——它们本就是内核里一组独立的 task,只是共享了 tgid 和资源对象。

## 五、进程的生命周期状态（内核视角）

`task->__state` 记录进程当前处于哪个状态,调度器据此决定要不要给它 CPU。详见 [task-resources/task-private.md](/concepts/process/task-resources/task-private.md) §三。

```plantuml
@startuml
skinparam shadowing false
state "TASK_RUNNING (R)\n就绪/正在运行" as R
state "可中断睡眠 (S)\n等事件,能被信号唤醒" as S
state "不可中断睡眠 (D)\n等IO,信号也不理" as D
state "停止 (T)\nSIGSTOP/被调试" as T
state "僵尸 (Z)\n已退出,等父进程 wait" as Z
[*] --> R : fork/clone 建好\n进入运行队列
R --> S : 等锁/sleep/等数据
S --> R : 事件到达/被唤醒
R --> D : 发起阻塞式磁盘IO
D --> R : IO 完成
R --> T : 收到 SIGSTOP
T --> R : 收到 SIGCONT
R --> Z : exit()——释放大部分资源,\n但保留 task_struct 存退出码
Z --> [*] : 父进程 wait() 收尸\n→ 回收 task_struct
@enduml
```

- **R（RUNNING）**：在运行队列里,正跑或随时可跑。
- **S（可中断睡眠）**：等某个事件,**能被信号打断**——绝大多数"闲着"的进程是这个状态。
- **D（不可中断睡眠）**：不响应信号,**`kill -9` 都杀不掉**。大量 D 状态是 IO 卡死的信号。折中方案 `TASK_KILLABLE` 允许被致命信号唤醒。
- **T（停止）**：被 `SIGSTOP` 或调试器暂停。
- **Z（僵尸）**：进程已 `exit`,资源大都释放,但 `task_struct` **暂留**存退出码,等父 `wait()` 回收。父不 `wait` → 僵尸堆积；父先死 → 子被 init(PID 1)收养。

> 状态字母就是 `ps`/`top` 里 STAT 列看到的那些（见 [top.md](/tools/cpu/top.md)）。异常终止后的现场分析见 [../crash/core-dump.md](/crash/core-dump.md)。

## 六、从 /proc 反向看这些内核结构

`/proc/<pid>/` 是内核把 `task_struct` 及其资源对象**导出成文件**的窗口——读它就是在读内核里那个进程的实时状态：

```bash
cat /proc/<pid>/status     # 人类可读汇总:Name/State/Tgid/Pid/PPid/Threads/Uid/CapEff...
cat /proc/<pid>/stat       # 单行原始字段:state/utime/stime/min_flt/maj_flt/starttime/prio...
ls  /proc/<pid>/task/      # 线程组所有线程(每个 tid 一个子目录) → 对应多个 task_struct
ls  /proc/<pid>/fd/        # files_struct:打开的每个 fd 及其指向(→ lsof)
cat /proc/<pid>/maps       # mm_struct 的 VMA 列表:每段虚拟地址区间/权限/后备文件
cat /proc/<pid>/cmdline    # exec 进来的命令行
ls -l /proc/<pid>/cwd /proc/<pid>/root   # fs_struct:当前目录、根目录
ls -l /proc/<pid>/ns/      # nsproxy:各命名空间
cat /proc/<pid>/cgroup     # 所属 cgroup
cat /proc/<pid>/limits     # rlimit 资源上限
cat /proc/<pid>/sched      # 调度参数:policy/prio/se...
cat /proc/<pid>/status | grep Seccomp  # seccomp 状态:0=禁用/1=STRICT/2=FILTER
```

对照本篇：`status`/`stat`/`sched` 是 `task_struct` 私有字段的导出、`maps`↔`mm_struct`、`fd/`↔`files_struct`、`cwd`/`root`↔`fs_struct`、`ns/`↔`nsproxy`、`task/`↔线程组里的多个 task、`Seccomp`↔`seccomp` 过滤器。

## 七、和本仓库其他文档的关系

- **各资源对象的逐篇详解**：子目录 [task-resources/](/concepts/process/task-resources/README.md) —— [task-private](/concepts/process/task-resources/task-private.md) · [mm-struct](/concepts/process/task-resources/mm-struct.md) · [files-struct](/concepts/process/task-resources/files-struct.md) · [fs-struct](/concepts/process/task-resources/fs-struct.md) · [signals-kernel](/concepts/process/task-resources/signals-kernel.md) · [cred](/concepts/process/task-resources/cred.md) · [namespaces-cgroups](/concepts/process/task-resources/namespaces-cgroups.md) · [io-context](/concepts/process/task-resources/io-context.md)。
- **进程/线程创建**：[process-creation.md](/concepts/process/process-creation.md)（fork/exec/clone 怎么复制或共享这些结构、CoW）、[thread-creation.md](/concepts/process/thread-creation.md)（线程 = 共享大部分对象的 task）、[fork-and-threads.md](/concepts/process/fork-and-threads.md)（多线程 fork 只带调用线程的坑）。
- **调度**：[scheduling.md](/concepts/process/scheduling.md)（task 怎么被选中跑）、[thread-affinity.md](/concepts/process/thread-affinity.md)（`cpus_ptr` 亲和性）、[top.md](/tools/cpu/top.md)（STAT 状态列）、[pidstat.md](/tools/cpu/pidstat.md)（utime/stime/上下文切换）。
- **切换与冻结点**：[context-switch.md](/concepts/process/context-switch.md)（`switch_to` 的 SP 切换魔法）、[kernel-stack-snapshot.md](/concepts/process/kernel-stack-snapshot.md)（内核栈快照——thread_struct.sp 如何保存冻结点）。
- **反向观测**：[procfs.md](/tools/proc/procfs.md)（`/proc/<pid>/` 怎么导出这些结构）。

## 八、一句话总结

> **内核不分进程与线程,只有一种调度实体 task,每个 task 一张 `task_struct`。这张表里分两类东西：私有字段（pid/tgid、状态、调度参数、CPU 寄存器、统计计数——只描述这个 task 自己）和资源对象指针（mm_files/fs/signal/cred/nsproxy/io_context/seccomp...——指向可被多 task 共享的独立对象）。同进程线程通过 clone flags 共享资源指针（共指同一对象）,不同进程 fork 时复制这些对象（各自独立）。fork/clone 的全部戏法就是决定这些指针复制新对象还是共指同一份——共享得多就是线程、共享得少就是进程。这一切都能透过 `/proc/<pid>/` 反向看到。**
