土法炼钢兴趣小组的算法知识备份

【操作系统百科】task_struct 解剖

文章导航

分类入口
os
标签入口
#task-struct#kernel-data-structure#current#thread-info#cred#mm-struct

目录

Linux 里没有”进程对象”和”线程对象”的区分——两者都是 struct task_struct。这个结构体定义在 include/linux/sched.h,2026 年的 6.6 内核里约 1600 行,成员 300+ 个。它是 Linux 内核里最大、最”杂”的结构体之一。

读这个结构体的意义:

本文按功能区切分 task_struct,每区挑出关键字段讲语义和变更。

一、总览:九个功能区

flowchart TB
    T[task_struct]
    T --> ID[标识<br/>pid / tgid / comm / parent]
    T --> SC[调度<br/>prio / policy / se / dl / rt]
    T --> ST[状态<br/>state / flags / exit_state]
    T --> MM[内存<br/>mm / active_mm / vmacache]
    T --> FS[文件系统<br/>fs / files / nsproxy]
    T --> SIG[信号<br/>signal / sighand / blocked]
    T --> CR[凭据<br/>cred / real_cred / group_info]
    T --> NS[命名空间<br/>nsproxy / cgroups]
    T --> TR[追踪/审计<br/>audit / ptrace / seccomp / perf]

每个区域都有自己的锁规则和生命周期规则,下面分区讨论。

二、身份区:pid、tgid、comm

struct task_struct {
    pid_t               pid;        // thread id(线程级)
    pid_t               tgid;       // thread group id(进程级 = 主线程 pid)
    char                comm[TASK_COMM_LEN]; // 15 字符的命令名
    struct task_struct  __rcu *real_parent;
    struct task_struct  __rcu *parent;
    struct list_head    children;
    struct list_head    sibling;
    ...
};

几个常见误解:

2.1 PID 分配

PID 分配器(kernel/pid.c)在 task 所属 pid namespace 里找空闲号;namespace 之间独立。一个 task 在每个祖先 ns 里都有一个 pid——保存在 struct pid 引用链里,而不是 task_struct 直接字段。这让 pid namespace 嵌套成为可能。

三、调度区

struct task_struct {
    int                 prio;
    int                 static_prio;
    int                 normal_prio;
    unsigned int        policy;         // SCHED_NORMAL/FIFO/RR/BATCH/IDLE/DEADLINE
    const struct sched_class *sched_class;
    struct sched_entity         se;     // CFS / EEVDF 的调度实体
    struct sched_rt_entity      rt;
    struct sched_dl_entity      dl;
    cpumask_t           cpus_mask;
    int                 on_cpu;
    unsigned int        rcu_read_lock_nesting;
    ...
};

调度相关详情在 C 子系列深入。

四、状态与标志

unsigned int        __state;    // TASK_RUNNING, INTERRUPTIBLE, UNINTERRUPTIBLE, STOPPED, ZOMBIE, DEAD
unsigned long       flags;      // PF_* 标志:PF_KTHREAD、PF_EXITING、PF_SIGNALED 等
int                 exit_state;
int                 exit_code;
int                 exit_signal;

state 的经典值:

D state 是生产故障的常客:NFS 卡、磁盘坏、kernel bug 都会让 task 卡在 D。cat /proc/<pid>/stack 看内核栈。

五、内存管理

struct mm_struct    *mm;
struct mm_struct    *active_mm;

mm_struct 本身是另一个大结构体(D-33 专题),包含 vma、页表根、exec 名、参数区等。切换 task 时如果 mm 相同,不切 CR3;这是线程切换比进程切换快的本质。

vmacache 在旧版本是小缓存;5.x 后改为 maple tree 的 LRU,vmacache 字段被移除。

六、文件系统

struct fs_struct    *fs;         // cwd, root, umask
struct files_struct *files;      // fd table
struct nsproxy      *nsproxy;

CLONE_FS / CLONE_FILES 决定是否共享 fs / files。线程共享(同一个 files 指针);fork 后各自一份。

七、信号

struct signal_struct    *signal;    // 线程组共享
struct sighand_struct   *sighand;   // 线程组共享(handlers)
sigset_t                blocked;    // 每线程的 mask
sigset_t                real_blocked;
struct sigpending       pending;    // 线程级待处理
...

注意:

递送规则(B-13 专题):先查线程私有 pending;无则看共享;共享信号递送给任何一个未屏蔽它的线程。

八、凭据

const struct cred __rcu *real_cred;   // 真实凭据
const struct cred __rcu *cred;        // 有效凭据
char                    comm[TASK_COMM_LEN];

struct cred 包含 uid、euid、suid、fsuid、gid、egid 以及 capabilities 三个集合(permitted/effective/inheritable/ambient)、keyring、LSM security blob。

RCU 保护:credentials 不可变,切换用 commit_creds() 原子替换指针,旧指针 RCU 释放。这让所有 capable() 检查可以无锁。

九、追踪与审计

struct audit_context    *audit_context;
struct task_struct      __rcu *ptracer;
struct list_head        ptrace_entry;
unsigned long           ptrace;         // flags
struct seccomp          seccomp;
...
struct io_uring_task    *io_uring;
struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];

perf、bpf、ftrace、audit、seccomp 都在 task_struct 里有落脚点。这也是为什么现代 task_struct 越长越大——每一个可观测性功能都要加字段。

十、task_struct 本身的分配

task_struct 本身通过 slab 分配。内核栈是另外分配的——典型 16KB(x86_64 THREAD_SIZE=8KB 一开始,后因 KASAN 加大到 16KB)。

栈和 task_struct 的关系经历过两次变革:

  1. 2.4 时代:栈顶存 struct thread_info,通过 current_thread_info() 从 sp 反推,再从 thread_info 拿 task_struct *
  2. 4.9 引入 CONFIG_THREAD_INFO_IN_TASK:thread_info 直接嵌在 task_struct 里。current 改为从 GS:%[current_task](x86_64)读 per-CPU 变量
  3. VMAP_STACK(4.9+):内核栈用 vmalloc 分配,有 guard 页检测栈溢出

current 在不同架构实现:

读 current 是一条指令级开销。

十一、task 的生命周期里 task_struct 的命运

sequenceDiagram
    participant P as 父进程
    participant K as kernel
    participant C as 子进程 task_struct
    P->>K: clone3()
    K->>C: alloc_task_struct + copy fields
    K->>C: add to pid hash, runqueue
    C->>C: schedule → run
    C->>K: exit() / _exit() / signal
    K->>C: do_exit: 清 files, mm, signal, cleanup IO
    C->>K: __state = EXIT_ZOMBIE
    P->>K: waitpid()
    K->>C: release_task: 释放 task_struct (RCU 延迟)

关键:task_struct 在 do_exit 后还不能立刻释放(还在 runqueue 上要清理、父进程要看 exit_code)。必须等 release_task,后者在父 wait 或 subreaper 处理后调。RCU 再延迟一次以保证 find_task_by_vpid 等 readers 安全。

僵尸进程(zombie)只占一个 task_struct + kernel stack(~20KB),几乎不占资源——但大量僵尸会消耗 pid 空间。

十二、大小与开销

task_struct 大小(x86_64, 6.6):

一个进程起步 ~20KB;数万 task 时 task_struct 占用 GB 级内存。

减小 task_struct 是持续工程——例如 struct task_structrcu_node_entry 条件编译、INIT_TASK 宏的演化、CLONE_THREAD 合并 signal/sighand。

十三、读 task_struct 的实战用法

内核调试场景:

crash> ps
crash> task ffff88003a7e0000
crash> struct task_struct.pid,tgid,comm,state ffff88003a7e0000
crash> foreach UN ps   # 所有 D state

eBPF 场景:

// bpftrace
kprobe:do_exit {
    printf("%s[%d] exiting\n", comm, pid);
}

BCCbpftrace 把 task_struct 的字段暴露给脚本。F-44 会详细讲 tracing。

十四、小结

下一篇 B-12 沿 task 的一生走一遍——从 clone 到 release 的每个阶段动了什么。


参考文献

工具


上一篇线程模型 下一篇进程生命周期

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-24 · os

【操作系统百科】mm_struct 与 VMA

进程地址空间在内核里靠 mm_struct + VMA 链表/树描述。本文讲 mm_struct 核心字段、VMA 从红黑树到 maple tree 的改造、anon_vma 反向映射、mmap_lock 争抢。

2026-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。


By .