当前位置: 首页 > news >正文

第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.h

2.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: 有效用户ID
2.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_structtasks成员连接所有进程。

struct task_struct { struct list_head tasks; // 全局任务链表节点 }; // 内核全局变量 struct task_struct *init_task; // init进程(PID=1) // 遍历所有进程 #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: 会话ID

3.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 --sort=pid # 状态说明 # R: 运行状态 # S: 睡眠状态 # D: 不可中断睡眠 # T: 停止状态 # Z: 僵尸状态 # 查看进程树 pstree -p # 查看进程详细信息 cat /proc/1/sched

互动讨论

  1. 进程vs线程:Linux中进程和线程都使用task_struct表示,它们本质上有什么区别?在哪些场景下应该选择多进程而非多线程?

  2. 僵尸进程危害:如果父进程没有正确调用wait()回收子进程,会产生僵尸进程。在实际项目中,您是否遇到过僵尸进程问题?是如何排查和解决的?


请帮忙点赞收藏+关注,内容持续更新,感谢大家~~~

http://www.jsqmd.com/news/1091001/

相关文章:

  • 企业官网的信息架构设计:从内容建模、导航到 URL 与内链
  • FreeRTOS源码详解(一)——申请和释放内存
  • MTEX工具箱:如何用5个关键功能解决材料科学家的晶体学分析难题
  • FreeRTOS源码详解(九)——Notification
  • Linux源码补充
  • 一线观察:激光焊接机器人自动上下料半年实录
  • 小红书SEO怎么做?关键词布局是第一步
  • AMD Ryzen处理器深度调试指南:5分钟掌握SMUDebugTool免费开源工具
  • [Android]appops
  • ❤️全景图鉴❤️武理计科:从C语言到毕业设计的四年技术栈演进
  • 2026沧州黄金回收白银回收铂金回收旧料回收怎么选?五家高实价铂金白银线下门店测评清单 + 联系方式
  • Claude Code强大是因为模型强还是agent实现细节?
  • 3分钟免费上手:可视化Kafka集群管理的完整解决方案
  • GlosSI:让Steam控制器支持所有Windows游戏的终极解决方案
  • 刮宫几天能洗澡洗头?刮宫术后洗护与科学子宫修护
  • League Akari 自动秒选终极指南:深度解析智能英雄选择系统架构与实战应用
  • 如何用3分钟掌握Calibre繁简中文转换插件:电子书阅读的终极语言解决方案
  • Java 线上排查标准手册:CPU 飙高、内存泄漏、接口慢,jstack/jmap/jstat 命令速查
  • 模型费用篇《DeepSeek V4-Flash 写代码“有点贵”?一文讲透模型费用真相与省心技巧》
  • 游戏公会推广系统怎么搭建?6个选型重点
  • Spring-Boot-4.0正式发布
  • Parsec VDD虚拟显示器终极指南:释放Windows显示潜能的完整解决方案
  • 预测性维护终极指南:从数据采集到机器学习落地的完整路径
  • FreeRTOS源码详解(七)——Counter
  • 应该很快就能搞定图片选择的问题了
  • TPA6140A2耳机放大器:Class-G与DirectPath技术解析与设计实践
  • 【无标题】当车间遇上比特流:我的《工业互联网组建与维护》修罗场实录
  • PROTECH 17-108-047211 PCB模块
  • 3个B站资源管理难题,用这个跨平台工具箱轻松解决
  • Prompt 工程实战——写好 prompt 的方法论:思维链、少样本示例、从差到好