task_struct 详解

task_struct 是 Linux 内核中**进程控制块(PCB)**的核心结构体,用来存储一个进程的所有信息,是内核管理进程的唯一标识,内核通过它实现进程的调度、资源管理、状态切换等所有操作。

一、 task_struct 核心核心信息

task_struct 定义在 include/linux/sched.h 头文件中,体积庞大(不同内核版本略有差异),核心包含以下8类关键信息,精准对应进程运行的全生命周期需求:

  1. 进程基本标识:PID(进程ID)、PPID(父进程ID)、UID/GID(用户/组权限ID),用于进程唯一识别与权限管控
  2. 进程状态:运行态(TASK_RUNNING)、睡眠态(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)、僵尸态(TASK_ZOMBIE)等,是调度器调度的核心依据
  3. 调度相关信息:优先级(static_prio/dynamic_prio)、调度类、时间片,决定进程获取CPU的先后顺序与时长
  4. 内存管理信息:指向 mm_struct 的指针,关联进程的地址空间(代码段、数据段、堆、栈等)
  5. 进程关系:父进程、子进程、兄弟进程的链表指针,构成进程树
  6. 文件与文件描述符:文件描述符表(files_struct),记录进程打开的所有文件、管道、套接字
  7. 信号处理信息:信号掩码、信号处理函数指针,处理内核/用户态发送的信号
  8. 上下文信息:进程切换时需保存的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」「遍历进程链表」「获取进程核心信息」三大核心场景。

前提准备

  1. 编译内核模块需提前安装内核源码、编译工具( gcc、make、linux-headers-$(uname -r) )
  2. 所有示例均包含模块基本框架(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)查找。

四、 关键注意事项(避坑指南)
  1. 权限与运行环境: task_struct 是内核态数据结构,用户态程序无法直接访问,只能通过系统调用(如 getpid() )间接获取部分信息(如PID),无法直接操作结构体本身。
  2. 内核版本差异:不同内核版本的 task_struct 字段略有调整(如早期内核的 pid 字段在 struct pid 中,5.x 内核直接放在 task_struct ),编写代码时需对应自己的内核版本。
  3. 链表遍历安全:示例2的遍历未加锁,在多核CPU环境下可能存在并发问题(进程创建/退出会修改链表),生产环境需加锁( rcu_read_lock() / rcu_read_unlock() )。
  4. 进程状态判断: 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
    ```
相关推荐
大树8828 分钟前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠31 分钟前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz1 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工2 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
网络研究院2 小时前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智2 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
treesforest2 小时前
AI安全系统如何识别异常访问?IP风险识别正在成为关键能力
网络·人工智能·tcp/ip·安全·web安全
shushangyun_3 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈3 小时前
Unix 与 Linux 异同小叙
linux·服务器·unix