Linux 内核进程管理之 task_struct 结构体详解

在 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

每个进程在用户态运行时使用用户栈;当它通过系统调用、异常、中断进入内核态时,就会使用自己的内核栈。

所以一个进程通常有两套栈:

  1. 用户栈:用户态代码使用
  2. 内核栈:进入内核态后使用
    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;
  1. static_prio
    表示静态优先级,进程创建时确定,默认普通进程120.
  2. prio
    动态优先级(调度器实时调整),表示进程当前实际使用的动态优先级。
    调度器真正进行调度时,会更关注这个值。
  3. normal_prio
    表示普通优先级。
    内核会根据调度策略、静态优先级、实时优先级等信息计算出普通优先级。
  4. 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;
  1. sched_class
    指向调度类(如 CFS 公平调度、实时调度、空闲调度),不同调度类对应不同的调度算法;
  2. sched_entity se/sched_rt_entity rt -------调度实体
    调度器不直接操作task_struct,而是通过嵌入的调度实体实现红黑树(CFS)、优先级队列(实时调度)等调度逻辑。
  3. cpumask_t cpus_allowed
    进程可运行的 CPU 掩码(多核场景下的 CPU 亲和性),决定进程能在哪些 CPU 上执行。

四、内存管理

这部分字段描述了进程的虚拟内存空间,是进程访问内存的 "导航图"。

c 复制代码
struct mm_struct *mm;
struct mm_struct *active_mm;

1. mm字段

mm指向进程的内存描述符(包含页表、代码段、数据段、堆 / 栈等用户空间信息);内核线程的 mm 为 NULL(无用户空间).

mm_struct 中保存了进程地址空间的信息,例如:

  1. 代码段
  2. 数据段
  3. 内存映射区域
  4. 页表信息

2. active_mm字段

指向当前正在使用的内存地址空间;内核线程会 "借用" 上一个用户进程的active_mm,避免频繁切换页表。

  1. 对于普通用户进程来说:
    mm == active_mm
  2. 对于内核进程,他没有独立的用户地址空间,所以:
    mm == NULL

五、进程描述符

Linux 通过 PID 标识进程,这部分字段是进程的 "身份编号",同时区分线程组。

c 复制代码
pid_t pid;				/* 进程 ID */
pid_t tgid;				/* 线程组 ID (用户态看到的 PID) */
  1. pid--内核层面的进程 ID(轻量级线程 LWP 的唯一标识);
  2. 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
  1. real_cred
    真实权限(进程创建者的 UID/GID),不可修改
  2. 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 */
};