第11天:进程基础内核认知:PCB与task_struct结构体解析
导语进程的身份证如果把Linux系统比作一座繁华的城市那么进程就是这座城市中形形色色的居民。每一个居民都有一张独特的身份证记录着姓名、住址、职业等信息。而操作系统内核则为每一个运行的进程颁发了一张特殊的身份证——进程控制块PCB。在Linux内核中这个PCB的实现就是大名鼎鼎的task_struct结构体。它是内核管理进程的核心数据结构包含了进程的所有信息从进程状态、优先级到内存映射、打开的文件描述符甚至包括信号处理和命名空间等。今天让我们深入内核源码探索这个庞大而精密的task_struct结构体。一、进程与PCB基础概念1.1 什么是进程进程Process是Linux系统中最基本的执行单元是程序的一次执行实例。# 查看当前系统进程 ps aux # 查看进程数 ps aux | wc -l # 查看特定进程 ps -ef | grep bash进程的五大特性并发性多个进程可同时运行动态性进程具有创建、运行、暂停、终止的生命周期独立性每个进程有独立的地址空间异步性进程执行顺序不可预测结构性进程由PCB和数据段组成1.2 PCB的作用**进程控制块PCB, Process Control Block**是操作系统用于管理进程的核心数据结构┌─────────────────────────────────────────┐ │ 进程控制块 (PCB) │ ├─────────────────────────────────────────┤ │ 进程标识 (PID, PPID, UID, GID) │ │ 进程状态 (就绪/运行/阻塞/终止) │ │ 调度信息 (优先级、调度策略) │ │ 内存信息 (页表指针、段表指针) │ │ I/O状态 (打开文件表、I/O设备) │ │ 计时信息 (运行时间、创建时间) │ │ 信号处理 (信号屏蔽、信号处理函数) │ │ ... │ └─────────────────────────────────────────┘1.3 Linux中的PCB在Linux内核中PCB由task_struct结构体实现每个进程或线程都对应一个task_struct。# 查看进程PID echo $$ # 查看进程状态 cat /proc/$$/status二、task_struct结构体深度解析2.1 task_struct源码位置在Linux 6.x内核源码中task_struct定义于# 内核源码路径取决于安装位置 ls /usr/src/linux-*/include/linux/sched.h2.2 task_struct核心字段struct task_struct { volatile long state; // 进程状态 void *stack; // 进程内核栈 unsigned int flags; // 进程标志 int pid; // 进程标识符 int tgid; // 线程组标识符 struct task_struct __rcu *parent; // 父进程 struct list_head children; // 子进程链表 struct list_head sibling; // 兄弟链表 struct mm_struct *mm; // 内存描述符 struct files_struct *files; // 文件描述符表 struct signal_struct *signal; // 信号描述符 const struct cred *cred; // 进程凭证 struct sched_entity se; // 调度实体 struct sched_rt_entity rt; // 实时调度实体 int prio; // 动态优先级 int static_prio; // 静态优先级 int normal_prio; // 正常优先级 unsigned int rt_priority; // 实时优先级 char comm[TASK_COMM_LEN]; // 进程命令名 struct pid_link pids[PIDTYPE_MAX]; // PID链表 struct fs_struct *fs; // 文件系统信息 struct files_struct *files; // 打开文件表 struct nsproxy *nsproxy; // 命名空间 struct thread_struct *thread; // 处理器特定状态 struct list_head tasks; // 全局任务链表 struct wake_q_node wake_q; // 唤醒队列节点 };2.3 关键字段详解2.3.1 进程状态// 进程状态定义include/linux/sched.h #define TASK_RUNNING 0x00000000 // 运行或就绪 #define TASK_INTERRUPTIBLE 0x00000001 // 可中断睡眠 #define TASK_UNINTERRUPTIBLE 0x00000002 // 不可中断睡眠 #define TASK_STOPPED 0x00000004 // 已停止 #define EXIT_ZOMBIE 0x00000080 // 僵尸状态 #define EXIT_DEAD 0x00000100 // 死亡状态# 查看进程状态 cat /proc/1/status | grep -E State:|State # 状态含义 # R: 运行 (TASK_RUNNING) # S: 可中断睡眠 (TASK_INTERRUPTIBLE) # D: 不可中断睡眠 (TASK_UNINTERRUPTIBLE) # T: 停止 (TASK_STOPPED) # Z: 僵尸 (EXIT_ZOMBIE)2.3.2 进程标识struct task_struct { int pid; // 进程ID每个进程唯一 int tgid; // 线程组ID同一线程组的所有线程共享 struct task_struct *parent; // 父进程指针 struct list_head children; // 子进程链表头 struct list_head sibling; // 兄弟进程链表节点 };# 查看进程PID和PPID ps -ef # PID: 进程ID # PPID: 父进程ID # UID: 用户ID # EUID: 有效用户ID2.3.3 内存管理struct task_struct { struct mm_struct *mm; // 内存描述符 struct mm_struct *active_mm; // 当前活跃的内存描述符 }; struct mm_struct { struct vm_area_struct *mmap; // 虚拟内存区域链表 struct rb_root mm_rb; // VMA红黑树 unsigned long total_vm; // 总虚拟页数 unsigned long locked_vm; // 锁定页数 unsigned long pinned_vm; // 钉住页数 unsigned long shared_vm; // 共享页数 unsigned long exec_vm; // 可执行页数 unsigned long stack_vm; // 栈页数 pgd_t *pgd; // 页全局目录指针 atomic_t mm_users; // 使用该地址空间的线程数 atomic_t mm_count; // 主引用计数 };# 查看进程内存映射 cat /proc/$$/maps # 查看进程内存状态 cat /proc/$$/status | grep -E Vm|Rss # VmPeak: 虚拟内存峰值 # VmSize: 虚拟内存大小 # VmRSS: 物理内存占用2.3.4 文件描述符struct task_struct { struct files_struct *files; // 文件描述符表 }; struct files_struct { atomic_t count; // 引用计数 struct fdtable *fdt; // 文件描述符表 struct file * fd_array[NR_OPEN_DEFAULT]; // 默认文件描述符数组 }; struct file { union { struct llist_node fu_llist; // 链表节点 struct rcu_head fu_rcuhead; // RCU头 } f_u; struct path f_path; // 文件路径 struct inode *f_inode; // 关联的inode const struct file_operations *f_op; // 文件操作 unsigned int f_flags; // 文件标志 fmode_t f_mode; // 文件模式 loff_t f_pos; // 文件位置 void *private_data; // 私有数据 };# 查看进程打开的文件 ls -la /proc/$$/fd # 查看文件描述符限制 ulimit -n # 查看进程fd使用情况 cat /proc/$$/limits | grep Max open files三、task_struct的组织与管理3.1 全局任务链表Linux内核维护一个全局任务链表通过task_struct的tasks成员连接所有进程。struct task_struct { struct list_head tasks; // 全局任务链表节点 }; // 内核全局变量 struct task_struct *init_task; // init进程(PID1) // 遍历所有进程 #define for_each_process(p) \ for (p init_task; (p next_task(p)) ! init_task; )# 查看init_task地址 cat /proc/1/attr/current 2/dev/null || echo Permission denied # 任务链表操作示例需要内核调试 # grep -A 5 struct task_struct /boot/config-$(uname -r)3.2 PID哈希表与链表enum pid_type { PIDTYPE_PID, // 进程ID PIDTYPE_TGID, // 线程组ID PIDTYPE_PGID, // 进程组ID SID, // 会话ID PIDTYPE_MAX }; struct pid_link { struct hlist_node node; struct pid *pid; }; struct task_struct { struct pid_link pids[PIDTYPE_MAX]; // 多种PID关联 };# 查看进程的多种ID ps -eo pid,pgid,sid,tty,comm # PID: 进程ID # PGID: 进程组ID # SID: 会话ID3.3 进程调度实体struct sched_entity { struct load_weight load; // 负载权重 struct rb_node run_node; // 红黑树节点 unsigned int on_rq; // 是否在运行队列 u64 exec_start; // 开始执行时间 u64 sum_exec_runtime; // 累计执行时间 u64 vruntime; // 虚拟运行时间(CFS) u64 prev_sum_exec_runtime; // 上次累计运行时间 u64 last_wakeup_time; // 上次唤醒时间 u64 avg_overlap; // 平均重叠时间 }; struct task_struct { struct sched_entity se; // 普通调度实体 struct sched_rt_entity rt; // 实时调度实体 int prio; // 动态优先级 int static_prio; // 静态优先级 int normal_prio; // 正常优先级 unsigned int rt_priority; // 实时优先级 };四、进程的创建与销毁4.1 fork/exec机制Linux通过fork()和exec()系统调用创建新进程fork()工作流程 ┌─────────────────────────────────────┐ │ 1. 分配新的PCB (task_struct) │ │ 2. 复制父进程地址空间 │ │ 3. 复制父进程文件描述符表 │ │ 4. 复制父进程信号处理方式 │ │ 5. 设置新进程的PID、PPID │ │ 6. 返回子进程PID给父进程 │ └─────────────────────────────────────┘// fork系统调用实现简化 long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; // 1. 分配新的task_struct p copy_process(clone_flags, stack_start, stack_size, parent_tidptr, child_tidptr, NULL); if (!IS_ERR(p)) { // 2. 唤醒新进程 wake_up_new_task(p); } return pid; }4.2 进程退出// 进程退出时的清理工作 void do_exit(long code) { struct task_struct *tsk current; // 1. 设置退出码 tsk-exit_code code; // 2. 释放资源 exit_mm(tsk); // 释放内存 exit_files(tsk); // 释放文件描述符 exit_fs(tsk); // 释放文件系统 exit_io(tsk); // 释放I/O // 3. 设置僵尸状态 tsk-exit_state EXIT_ZOMBIE; // 4. 通知父进程 notify hlist_empty(tsk-children); tsk-exit_signal SIGCHLD; write_lock_irq(tasklist_lock); tsk-state TASK_DEAD; write_unlock_irq(tasklist_lock); // 5. 调度其他进程 schedule(); }五、进程状态查看实践5.1 通过proc文件系统查看# 查看进程完整状态 cat /proc/$$/status # 关键字段说明 # Name: 命令名 # State: 进程状态 # Pid: 进程ID # PPid: 父进程ID # Uid/Gid: 用户/组ID # VmPeak/VmSize: 虚拟内存峰值/当前 # VmRSS: 物理内存占用 # Threads: 线程数 #FDSize: 文件描述符表大小5.2 结构化查看# 以更友好的格式查看进程信息 ps -eo pid,state,ppid,comm --sortpid # 状态说明 # R: 运行状态 # S: 睡眠状态 # D: 不可中断睡眠 # T: 停止状态 # Z: 僵尸状态 # 查看进程树 pstree -p # 查看进程详细信息 cat /proc/1/sched互动讨论进程vs线程Linux中进程和线程都使用task_struct表示它们本质上有什么区别在哪些场景下应该选择多进程而非多线程僵尸进程危害如果父进程没有正确调用wait()回收子进程会产生僵尸进程。在实际项目中您是否遇到过僵尸进程问题是如何排查和解决的请帮忙点赞收藏关注内容持续更新感谢大家~~~