在 Linux 系统中,进程是操作系统资源分配和调度的基本单位,而task_struct结构体则是内核中描述进程的核心数据结构 ------ 它就像进程的 "身份证",记录了进程的所有关键信息;涵盖了进程从创建到消亡的全生命周期状态。本博客将基于linux源码拆解task_struct的核心字段。
task_struct结构体定义在 Linux 内核源码中,通常位于:
c
include/linux/sched.h
task_struct 可以理解为 Linux 中的进程控制块,也就是 PCB。
操作系统想要管理一个进程,就必须知道这个进程的状态、优先级、地址空间、打开的文件、父子关系、信号、调度信息等内容,而这些信息大部分都被组织在 task_struct 中。
Linux 内核中的每一个进程或者线程,本质上都对应一个 task_struct 对象。
一. 什么是PCB
PCB 的全称是 Process Control Block,中文叫进程控制块。
操作系统需要同时管理很多进程,每个进程都有自己的运行状态和资源信息。为了把这些信息统一管理起来,内核就为每一个进程创建一个 PCB。
在 Linux 中,PCB 对应的结构体就是:struct task_struct
所以我们可以这样理解:
进程 = 内核数据结构 task_struct + 进程代码和数据
用户看到的是一个正在运行的程序,而内核看到的是一个个 task_struct。
二、基础状态与核心属性
这部分字段描述了进程的核心状态和基础标识,是进程最根本的属性
1. 进程状态
c
volatile long state;
state 表示进程当前所处的状态,volatile 修饰确保不被编译器缓存(中断 / 其他 CPU 可能随时修改).
常见状态有:
c
//下面这些全是内核宏
//就绪/正在CPU执行 值为0
TASK_RUNNING
//浅睡眠:可被信号唤醒 值为1
TASK_INTERRUPTIBLE
//深睡眠:不能被信号打断,只能等待资源就绪 值为2
TASK_UNINTERRUPTIBLE
//以下是高位bit,用_前缀,和上面低三位何以或运算共存
//收到sigstop停止 值为4
__TASK_STOPPED
//gdb调试被暂停 值为8
__TASK_TRACED
//表示僵尸进程状态 值16
EXIT_ZOMBIE
EXIT_DEAD
2. 内核栈stack
c
void *stack;
stack 是指向进程的内核栈。通常 8KB/16KB
每个进程在用户态运行时使用用户栈;当它通过系统调用、异常、中断进入内核态时,就会使用自己的内核栈。
所以一个进程通常有两套栈:
- 用户栈:用户态代码使用
- 内核栈:进入内核态后使用
task_struct 中保存内核栈地址,是为了方便内核在进程切换、系统调用、中断处理时找到对应的执行上下文。
3. 引用计数 usage
c
atomic_t usage;
usage 是 task_struct 的引用计数。
因为内核中可能有多个地方同时持有某个进程的 task_struct 指针,所以不能随便释放它。
当有模块使用这个进程结构体时,引用计数增加;当不再使用时,引用计数减少。
只有引用计数降到合适的值之后,内核才可以真正释放这个 task_struct。
这和 C++ 中智能指针的引用计数思想有点类似。
4. 进程标志flags
c
unsigned int flags;
flags 用来保存进程的一些标志位。
例如:
c
PF_KTHREAD
PF_EXITING
PF_FORKNOEXEC
这些标志可以说明进程的一些特殊状态。
例如:
PF_KTHREAD:表示这是一个内核线程
PF_EXITING:表示进程正在退出
PF_FORKNOEXEC:表示进程 fork 之后还没有 exec
通过 flags,内核可以快速判断进程是否具备某些特殊属性。
三、进程调度相关字段
1. 进程优先级相关
c
int prio, static_prio, normal_prio;
unsigned int rt_priority;
- static_prio
表示静态优先级,进程创建时确定,默认普通进程120. - prio
动态优先级(调度器实时调整),表示进程当前实际使用的动态优先级。
调度器真正进行调度时,会更关注这个值。 - normal_prio
表示普通优先级。
内核会根据调度策略、静态优先级、实时优先级等信息计算出普通优先级。 - rt_priority
实时进程优先级(1-99,数值越高优先级越高)。实时进程的优先级通常高于普通进程,常用于对响应时间要求比较高的场景。
2. 调度实体类
在较新的 Linux 内核中,task_struct 里面通常还会包含和调度类相关的成员,这些结构用于支持不同的调度策略。
c
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
cpumask_t cpus_allowed;
- sched_class
指向调度类(如 CFS 公平调度、实时调度、空闲调度),不同调度类对应不同的调度算法; - sched_entity se/sched_rt_entity rt -------调度实体
调度器不直接操作task_struct,而是通过嵌入的调度实体实现红黑树(CFS)、优先级队列(实时调度)等调度逻辑。 - cpumask_t cpus_allowed
进程可运行的 CPU 掩码(多核场景下的 CPU 亲和性),决定进程能在哪些 CPU 上执行。
四、内存管理
这部分字段描述了进程的虚拟内存空间,是进程访问内存的 "导航图"。
c
struct mm_struct *mm;
struct mm_struct *active_mm;
1. mm字段
mm指向进程的内存描述符(包含页表、代码段、数据段、堆 / 栈等用户空间信息);内核线程的 mm 为 NULL(无用户空间).
mm_struct 中保存了进程地址空间的信息,例如:
- 代码段
- 数据段
- 堆
- 栈
- 内存映射区域
- 页表信息
2. active_mm字段
指向当前正在使用的内存地址空间;内核线程会 "借用" 上一个用户进程的active_mm,避免频繁切换页表。
- 对于普通用户进程来说:
mm == active_mm - 对于内核进程,他没有独立的用户地址空间,所以:
mm == NULL
五、进程描述符
Linux 通过 PID 标识进程,这部分字段是进程的 "身份编号",同时区分线程组。
c
pid_t pid; /* 进程 ID */
pid_t tgid; /* 线程组 ID (用户态看到的 PID) */
- pid--内核层面的进程 ID(轻量级线程 LWP 的唯一标识);
- tgid--表示线程组id
用户态ps命令看到的 PID 实际是tgid,同一线程组内所有线程共享tgid,只有线程组 leader 的pid等于tgid
对于单线程进程来说:
c
pid == tgid
对于多线程进程来说:
c
每个线程的 pid 不同
同一个进程内所有线程的 tgid 相同
所以用户层面看到的"进程 ID",很多时候其实是 tgid。
六、亲属关系
Linux 进程以树状结构组织,这部分字段描述了进程的父子、兄弟、线程组关系。
c
struct task_struct *real_parent; /* 真正的父进程 */
struct task_struct *parent; /* 接收 SIGCHLD 信号的父进程 */
struct list_head children; /* 子进程链表 */
struct list_head sibling; /* 兄弟进程链表 */
struct task_struct *group_leader; /* 线程组领头人 */
1. real_parent
表示真正创建当前进程的父进程
2. parent
表示当前意义上的父进程。
大多数情况下:
parent == real_parent
但是在调试、ptrace 等场景下,parent 可能会发生变化
3. children
表示当前进程的子进程链表。
一个进程可以创建多个子进程,这些子进程都会挂到父进程的 children 链表中。
4. sibling
表示当前进程作为兄弟节点,挂到父进程的 children 链表中。
也就是说:
父进程通过 children 找到所有子进程
子进程通过 sibling 连接到兄弟进程链表中
5. group_leader
表示线程组的组长。
对于一个多线程进程来说,主线程通常就是线程组组长。
七、凭证与权限
这部分字段定义了进程的访问权限,是进程操作系统资源的通行证。
c
const struct cred *real_cred; /* 客观权限(谁启动了进程) */
const struct cred *cred; /* 有效权限(当前生效的权限,如sudo) */
struct mutex cred_guard_mutex; /* 权限计算的保护锁 */
两个权限字段里面通常包括:
c
uid
gid
euid
egid
capability
linux判断一个进程能不能访问某个文件,能不能执行某个特权操作,本质就是看他的权限信息。例如:
bash
sudo
chmod
chown
- real_cred
真实权限(进程创建者的 UID/GID),不可修改 - cred
有效权限(进程当前使用的 UID/GID),可通过setuid等系统调用修改(如 sudo 后,cred 的 euid 变为 0);
八、文件系统与资源:进程的资源清单
进程运行依赖的文件、目录、命名空间等资源,都记录在这部分字段中。
c
char comm[TASK_COMM_LEN]; /* 进程名(最多16字节) */
struct fs_struct *fs; // 根目录和当前工作目录
struct files_struct *files; // 已打开文件描述符表 (fd table)
struct nsproxy *nsproxy; // 命名空间(Container/Docker 的基础)
1. comm字段
进程名(如bash、nginx),可通过ps、top查看,长度限制为TASK_COMM_LEN(默认 16);
2. fs字段
记录进程的根目录(/)和当前工作目录(CWD),不同进程可拥有不同的根目录(如容器)
当我们在 shell 中执行:
bash
pwd
cd
这些操作背后都和进程的文件系统上下文有关
3. files字段
files 保存进程打开的文件表。
比如一个进程默认打开了标准输入、标准输出、标准错误:
c
0 -> stdin
1 -> stdout
2 -> stderr
这些文件描述符最终都会被组织到 files_struct 中。
当我们调用:
open()
read()
write()
close()
内核就会根据当前进程的 files 找到对应的文件对象。
九、信号处理
信号是 Linux 进程间通信的重要方式,这部分字段定义了进程如何响应信号。
c
struct signal_struct *signal; // 共享的信号(发给整个线程组)
struct sighand_struct *sighand; // 信号处理函数
sigset_t blocked, real_blocked; // 被屏蔽的信号集
struct sigpending pending; // 待处理的信号
Linux 中进程可以接收信号,例如:
bash
kill -9 pid
Ctrl + C
这些都和 task_struct 中的信号相关字段有关
1. signal字段
保存进程级别的信号信息,线程组共享的信号状态(如收到 SIGKILL,整个线程组退出)
2. sighand字段
指向信号处理函数表(如SIGINT对应ctrl+c的处理逻辑);也就是说,当进程收到某个信号时,应该执行默认动作、忽略信号,还是调用用户自定义处理函数,都和它有关。
3. blocked字段
被屏蔽的信号集(屏蔽的信号不会被进程响应);
4.pending字段
待处理的信号队列(信号已发送但未被处理)
十、源代码
这个直接给出linux的task_struct的源代码
c
struct task_struct {
//1.基础状态和核心属性
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ /* -1不可运行, 0可运行, >0已停止 */
//state:描述进程是正在运行、在就绪队列中,还是处于睡眠/停止状态。使用 volatile 是为了确保编译器不会缓存该值,因为中断或另一个 CPU 可能会随时修改它
void *stack; /* 指向内核栈的指针 stack:每个进程都有自己的内核栈,通常是 8KB 或 16KB。 */
atomic_t usage; /* 引用计数 */
unsigned int flags; /* per process flags, defined below */ /* 进程标志,如 PF_KTHREAD(内核线程) */
unsigned int ptrace; /* ptrace 系统调用相关的状态 */
int lock_depth; /* BKL lock depth */
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif
//2.调度相关字段:这些字段决定了CPU何时运行、以及运行该进程多久
int prio, static_prio, normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class; /* 调度类(如 CFS、实时调度) */
struct sched_entity se; /* 普通进程的调度实体 */
struct sched_rt_entity rt; /* 实时进程的调度实体 */
//se 和 rt: 调度器并不直接操作 task_struct,而是操作这些嵌入的"调度实体",以实现红黑树(CFS)或优先级队列调度。
#ifdef CONFIG_PREEMPT_NOTIFIERS
/* list of struct preempt_notifier: */
struct hlist_head preempt_notifiers;
#endif
/*
* fpu_counter contains the number of consecutive context switches
* that the FPU is used. If this is over a threshold, the lazy fpu
* saving becomes unlazy to save the trap. This is an unsigned char
* so that after 256 times the counter wraps and the behavior turns
* lazy again; this to deal with bursty apps that only use FPU for
* a short time
*/
unsigned char fpu_counter;
#ifdef CONFIG_BLK_DEV_IO_TRACE
unsigned int btrace_seq;
#endif
unsigned int policy;
cpumask_t cpus_allowed;
#ifdef CONFIG_TREE_PREEMPT_RCU
int rcu_read_lock_nesting;
char rcu_read_unlock_special;
struct rcu_node *rcu_blocked_node;
struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
struct sched_info sched_info;
#endif
struct list_head tasks;
struct plist_node pushable_tasks;
//3.内存管理 描述了进程如何看待物理和虚拟内存
struct mm_struct *mm, *active_mm; //mm: 指向进程的内存描述符(页表、代码段、数据段等)。如果是内核线程,mm 为 NULL,因为它没有用户空间内存。
//active_mm: 指向当前正在使用的内存地址空间(对于内核线程,它会"借用"上一个用户进程的 mm)
/* task state */
int exit_state;
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
/* ??? */
unsigned int personality;
unsigned did_exec:1;
unsigned in_execve:1; /* Tell the LSMs that the process is doing an
* execve */
unsigned in_iowait:1;
/* Revert to default priority/policy when forking */
unsigned sched_reset_on_fork:1;
//进程标识符 (Identifiers)
pid_t pid; //pid_t pid; /* 进程 ID */
pid_t tgid; //线程组 ID (也就是用户态看到的 PID)
#ifdef CONFIG_CC_STACKPROTECTOR
/* Canary value for the -fstack-protector gcc feature */
unsigned long stack_canary;
#endif
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/ //5.亲属关系 Linux 进程是以树状结构组织的。
struct task_struct *real_parent; /* real parent process */ // 真正的父进程
struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */ //接收 SIGCHLD 信号的父进程
/*
* children/sibling forms the list of my natural children
*/
struct list_head children; /* list of my children */ //子进程链表
struct list_head sibling; /* linkage in my parent's children list */ //兄弟进程链表
struct task_struct *group_leader; /* threadgroup leader */ //线程组领头人
/*
* ptraced is the list of tasks this task is using ptrace on.
* This includes both natural children and PTRACE_ATTACH targets.
* p->ptrace_entry is p's link on the p->parent->ptraced list.
*/
struct list_head ptraced;
struct list_head ptrace_entry;
/*
* This is the tracer handle for the ptrace BTS extension.
* This field actually belongs to the ptracer task.
*/
struct bts_context *bts;
/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];
struct list_head thread_group;
struct completion *vfork_done; /* for vfork() */
int __user *set_child_tid; /* CLONE_CHILD_SETTID */
int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */
cputime_t utime, stime, utimescaled, stimescaled;
cputime_t gtime;
cputime_t prev_utime, prev_stime;
unsigned long nvcsw, nivcsw; /* context switch counts */
struct timespec start_time; /* monotonic time */
struct timespec real_start_time; /* boot based time */
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
unsigned long min_flt, maj_flt;
struct task_cputime cputime_expires;
struct list_head cpu_timers[3];
/* process credentials */ //6.凭据与权限
const struct cred *real_cred; /* objective and real subjective task //客观权限(谁启动了它
* credentials (COW) */
const struct cred *cred; /* effective (overridable) subjective task //主观权限(当前生效的权限,如 sudo 后
* credentials (COW) */
struct mutex cred_guard_mutex; /* guard against foreign influences on
* credential calculations
* (notably. ptrace) */
struct cred *replacement_session_keyring; /* for KEYCTL_SESSION_TO_PARENT */
//7.文件系统与资源
char comm[TASK_COMM_LEN]; /* executable name excluding path //进程名(最多16字节)
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by setup_new_exec */
/* file system info */
int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
#endif
#ifdef CONFIG_DETECT_HUNG_TASK
/* hung task detection */
unsigned long last_switch_count;
#endif
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs; //根目录和当前工作目录
/* open file information */
struct files_struct *files; //已打开文件描述符表 (fd table)
/* namespaces */
struct nsproxy *nsproxy; //命名空间(Container/Docker 的基础)
/* signal handlers */ //8.信号处理
struct signal_struct *signal; //共享的信号(发给整个线程组)
struct sighand_struct *sighand; //信号处理函数
sigset_t blocked, real_blocked; //被屏蔽的信号集
sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
struct sigpending pending; //待处理的任务信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
uid_t loginuid;
unsigned int sessionid;
#endif
seccomp_t seccomp;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings, mems_allowed,
* mempolicy */
spinlock_t alloc_lock;
#ifdef CONFIG_GENERIC_HARDIRQS
/* IRQ handler threads */
struct irqaction *irqaction;
#endif
/* Protection of the PI data structures: */
spinlock_t pi_lock;
#ifdef CONFIG_RT_MUTEXES
/* PI waiters blocked on a rt_mutex held by this task */
struct plist_head pi_waiters;
/* Deadlock detection and priority inheritance handling */
struct rt_mutex_waiter *pi_blocked_on;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
/* mutex deadlock detection */
struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
u64 curr_chain_key;
int lockdep_depth;
unsigned int lockdep_recursion;
struct held_lock held_locks[MAX_LOCK_DEPTH];
gfp_t lockdep_reclaim_gfp;
#endif
/* journalling filesystem info */
void *journal_info;
/* stacked block device info */
struct bio *bio_list, **bio_tail;
/* VM state */
struct reclaim_state *reclaim_state;
struct backing_dev_info *backing_dev_info;
struct io_context *io_context;
unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use. */
struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
u64 acct_rss_mem1; /* accumulated rss usage */
u64 acct_vm_mem1; /* accumulated virtual memory usage */
cputime_t acct_timexpd; /* stime + utime since last update */
#endif
#ifdef CONFIG_CPUSETS
nodemask_t mems_allowed; /* Protected by alloc_lock */
int cpuset_mem_spread_rotor;
#endif
#ifdef CONFIG_CGROUPS
/* Control Group info protected by css_set_lock */
struct css_set *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock */
struct list_head cg_list;
#endif
#ifdef CONFIG_FUTEX
struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
struct compat_robust_list_head __user *compat_robust_list;
#endif
struct list_head pi_state_list;
struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
struct perf_event_context *perf_event_ctxp;
struct mutex perf_event_mutex;
struct list_head perf_event_list;
#endif
#ifdef CONFIG_NUMA
struct mempolicy *mempolicy; /* Protected by alloc_lock */
short il_next;
#endif
atomic_t fs_excl; /* holding fs exclusive resources */
struct rcu_head rcu;
/*
* cache last used pipe for splice
*/
struct pipe_inode_info *splice_pipe;
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
struct prop_local_single dirties;
#ifdef CONFIG_LATENCYTOP
int latency_record_count;
struct latency_record latency_record[LT_SAVECOUNT];
#endif
/*
* time slack values; these are used to round up poll() and
* select() etc timeout values. These are in nanoseconds.
*/
unsigned long timer_slack_ns;
unsigned long default_timer_slack_ns;
struct list_head *scm_work_list;
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
/* Index of current stored adress in ret_stack */
int curr_ret_stack;
/* Stack of return addresses for return function tracing */
struct ftrace_ret_stack *ret_stack;
/* time stamp for last schedule */
unsigned long long ftrace_timestamp;
/*
* Number of functions that haven't been traced
* because of depth overrun.
*/
atomic_t trace_overrun;
/* Pause for the tracing */
atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
/* state flags for use by tracers */
unsigned long trace;
/* bitmask of trace recursion */
unsigned long trace_recursion;
#endif /* CONFIG_TRACING */
};