task_struct 是 Linux 内核中**进程控制块(PCB)**的核心结构体,用来存储一个进程的所有信息,是内核管理进程的唯一标识,内核通过它实现进程的调度、资源管理、状态切换等所有操作。
一、 task_struct 核心核心信息
task_struct 定义在 include/linux/sched.h 头文件中,体积庞大(不同内核版本略有差异),核心包含以下8类关键信息,精准对应进程运行的全生命周期需求:
- 进程基本标识:PID(进程ID)、PPID(父进程ID)、UID/GID(用户/组权限ID),用于进程唯一识别与权限管控
- 进程状态:运行态(TASK_RUNNING)、睡眠态(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)、僵尸态(TASK_ZOMBIE)等,是调度器调度的核心依据
- 调度相关信息:优先级(static_prio/dynamic_prio)、调度类、时间片,决定进程获取CPU的先后顺序与时长
- 内存管理信息:指向 mm_struct 的指针,关联进程的地址空间(代码段、数据段、堆、栈等)
- 进程关系:父进程、子进程、兄弟进程的链表指针,构成进程树
- 文件与文件描述符:文件描述符表(files_struct),记录进程打开的所有文件、管道、套接字
- 信号处理信息:信号掩码、信号处理函数指针,处理内核/用户态发送的信号
- 上下文信息:进程切换时需保存的CPU寄存器值,确保切换后能恢复原有执行状态
二、 关键字段(Linux 5.4 版本核心字段)
以下是 task_struct 中最常用的核心字段,贴合实际内核开发与学习场景,避开冷门字段:
c
struct task_struct {
// 1. 进程标识核心字段
pid_t pid; // 进程ID
pid_t tgid; // 线程组ID(主线程PID = 进程ID)
struct task_struct *parent; // 父进程指针
struct list_head children; // 子进程链表头
uid_t uid; // 真实用户ID
gid_t gid; // 真实组ID
// 2. 进程状态与调度
volatile long state; // 进程状态(核心字段)
int prio, static_prio, normal_prio; // 优先级
unsigned int rt_priority; // 实时进程优先级
struct sched_entity se; // 调度实体,关联调度类
// 3. 内存管理核心
struct mm_struct *mm; // 用户态地址空间(普通进程有效)
struct mm_struct *active_mm; // 内核态地址空间(所有进程有效)
// 4. 文件与IO
struct files_struct *files; // 文件描述符表(fd数组)
struct fs_struct *fs; // 文件系统信息(当前目录、根目录)
// 5. 信号处理
struct signal_struct *signal; // 信号相关全局信息
struct sighand_struct *sighand; // 信号处理函数集合
sigset_t blocked; // 阻塞的信号集
// 6. 上下文与栈
void *stack; // 进程内核栈指针
struct thread_struct thread; // CPU寄存器上下文(切换时保存/恢复)
// 7. 时间统计
cputime_t utime, stime; // 用户态耗时、内核态耗时
};
三、 核心操作代码示例(内核态)
task_struct 仅能在内核态访问(用户态无法直接操作),以下示例均为 Linux 内核模块代码,基于 5.4 内核编写,可直接编译运行,覆盖「获取当前进程task_struct」「遍历进程链表」「获取进程核心信息」三大核心场景。
前提准备
- 编译内核模块需提前安装内核源码、编译工具( gcc、make、linux-headers-$(uname -r) )
- 所有示例均包含模块基本框架(init/exit 函数),编译后用 insmod 加载、 rmmod 卸载, dmesg 查看输出
示例1: 获取当前进程的 task_struct 及核心信息
核心函数: current 宏(内核提供,直接返回当前运行进程的 task_struct 指针),是内核开发中最常用的宏。
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h> // task_struct 头文件
#include <linux/pid.h> // PID 相关头文件
MODULE_LICENSE("GPL"); // 开源协议,必须声明
MODULE_AUTHOR("Test");
MODULE_DESCRIPTION("Get current task_struct info");
// 模块初始化函数(加载模块时执行)
static int __init task_struct_demo_init(void)
{
struct task_struct *curr_task = current; // 获取当前进程task_struct指针
// 打印当前进程核心信息(dmesg 查看)
printk(KERN_INFO "===== 当前进程信息 =====\n");
printk(KERN_INFO "进程PID: %d\n", curr_task->pid);
printk(KERN_INFO "进程名称: %s\n", curr_task->comm); // comm:进程名称(对应argv[0])
printk(KERN_INFO "进程状态: %ld\n", curr_task->state);
printk(KERN_INFO "父进程PID: %d\n", curr_task->parent->pid);
printk(KERN_INFO "用户态耗时: %llu jiffies\n", curr_task->utime);
printk(KERN_INFO "内核态耗时: %llu jiffies\n", curr_task->stime);
return 0;
}
// 模块卸载函数(卸载模块时执行)
static void __exit task_struct_demo_exit(void)
{
printk(KERN_INFO "模块卸载成功!\n");
}
module_init(task_struct_demo_init);
module_exit(task_struct_demo_exit);
运行结果:加载模块后执行 dmesg | tail -10 ,会输出当前 insmod 进程的 PID、名称、父进程 PID 等信息。
示例2: 遍历系统所有进程(遍历 task_struct 链表)
内核中所有进程的 task_struct 通过双向循环链表组织,核心链表头为 init_task (0号进程,即 idle 进程,所有进程的祖先),遍历核心依赖 list_for_each_entry 宏(内核链表遍历专用宏)。
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/list.h> // 链表操作头文件
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Test");
MODULE_DESCRIPTION("Traverse all task_struct");
static int __init task_traverse_init(void)
{
struct task_struct *task; // 遍历用的task_struct指针
printk(KERN_INFO "===== 系统所有进程(PID+名称)=====\n");
// 遍历全局进程链表:init_task 是链表头,tasks 是 task_struct 中的链表节点
list_for_each_entry(task, &init_task.tasks, tasks) {
printk(KERN_INFO "PID: %5d | 进程名称: %-15s | 状态: %ld\n",
task->pid, task->comm, task->state);
}
return 0;
}
static void __exit task_traverse_exit(void)
{
printk(KERN_INFO "进程遍历模块卸载!\n");
}
module_init(task_traverse_init);
module_exit(task_traverse_exit);
关键说明: task->tasks 是 task_struct 中的链表成员,用于将所有进程串联成双向循环链表, list_for_each_entry 宏会自动遍历链表并赋值 task 指针。
示例3: 根据 PID 查找指定进程的 task_struct
核心函数: pid_task() + find_get_pid() ,内核提供的标准接口,用于通过 PID 精准查找目标进程的 task_struct ,避免手动遍历链表的低效操作。
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/sched/signal.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Test");
MODULE_DESCRIPTION("Find task_struct by PID");
#define TARGET_PID 1 // 目标PID(此处以1号进程systemd为例,可修改为任意存在的PID)
static int __init task_find_by_pid_init(void)
{
struct pid *pid_struct;
struct task_struct *target_task;
// 1. 根据PID获取pid结构体(内核内部PID管理结构)
pid_struct = find_get_pid(TARGET_PID);
if (!pid_struct) {
printk(KERN_ERR "未找到PID为%d的进程!\n", TARGET_PID);
return -EINVAL;
}
// 2. 根据pid结构体获取task_struct指针
target_task = pid_task(pid_struct, PIDTYPE_PID);
if (!target_task) {
printk(KERN_ERR "获取task_struct失败!\n");
return -EINVAL;
}
// 3. 打印目标进程信息
printk(KERN_INFO "===== PID=%d 进程信息 =====\n", TARGET_PID);
printk(KERN_INFO "进程名称: %s\n", target_task->comm);
printk(KERN_INFO "进程状态: %ld\n", target_task->state);
printk(KERN_INFO "父进程PID: %d\n", target_task->parent->pid);
printk(KERN_INFO "是否为实时进程: %d\n", target_task->rt_priority > 0 ? 1 : 0);
return 0;
}
static void __exit task_find_by_pid_exit(void)
{
printk(KERN_INFO "按PID查找进程模块卸载!\n");
}
module_init(task_find_by_pid_init);
module_exit(task_find_by_pid_exit);
关键说明: PIDTYPE_PID 表示按进程PID查找,内核还支持按线程组ID(PIDTYPE_TGID)、会话ID(PIDTYPE_SID)查找。
四、 关键注意事项(避坑指南)
- 权限与运行环境: task_struct 是内核态数据结构,用户态程序无法直接访问,只能通过系统调用(如 getpid() )间接获取部分信息(如PID),无法直接操作结构体本身。
- 内核版本差异:不同内核版本的 task_struct 字段略有调整(如早期内核的 pid 字段在 struct pid 中,5.x 内核直接放在 task_struct ),编写代码时需对应自己的内核版本。
- 链表遍历安全:示例2的遍历未加锁,在多核CPU环境下可能存在并发问题(进程创建/退出会修改链表),生产环境需加锁( rcu_read_lock() / rcu_read_unlock() )。
- 进程状态判断: state 字段的取值为内核宏定义(如 TASK_RUNNING=0 、 TASK_INTERRUPTIBLE=1 ),直接打印数值需对照内核头文件的宏定义解读。
五、 编译脚本(Makefile)
以上所有内核模块均可用以下 Makefile 编译,无需修改,直接放在同目录下执行 make 即可生成 .ko 模块文件。
makefile
c
obj-m += task_struct_demo.o # 文件名对应:xxx.c 则写 xxx.o(多个示例需分别修改)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
```