进程控制块(Process Control Block, PCB)
进程控制块(PCB) 是操作系统中用来描述和管理进程的重要数据结构。每个进程在创建时,操作系统都会为其分配一个 PCB,PCB 中保存了该进程的所有重要信息,包括进程的状态、资源使用情况以及与其他进程的关系。
PCB 是操作系统管理进程的核心数据结构,每个进程都有一个唯一的 PCB。
进程控制块的作用
进程控制块 的主要作用是维护进程的状态和信息,以便操作系统能够高效地管理和调度进程。 作用有很多,包括:
- 唯一标识进程:通过 进程控制块 操作系统可以区分不同的进程。
- 存储进程状态:保存进程当前的运行状态(如正在运行、等待等)。
- 资源管理:记录进程使用的 CPU、内存、文件等资源信息。
- 调度和切换:在进程切换时,操作系统通过 PCB 保存和恢复进程的上下文。
进程控制块的组成部分
PCB 是一个复杂的数据结构,通常包含以下几类信息:
- 进程标识信息:用于唯一标识一个进程。
- 主要包括:
- 进程 ID(PID):每个进程的唯一标识号。
- 父进程 ID(PPID):创建该进程的父进程的 ID。
- 用户 ID 和组 ID:进程所属用户和用户组的标识。
- 主要包括:
- 进程状态信息:用于描述进程的当前状态。
- 常见的进程状态包括:
- 新建(New):进程正在被创建。
- 就绪(Ready):进程已准备好运行,等待 CPU 调度。
- 运行(Running):进程正在使用 CPU 执行。
- 等待(Waiting/Blocked):进程因等待资源(如 I/O)而暂停。
- 终止(Terminated):进程已结束。
- PCB 中保存着进程的状态以及状态变化的相关信息。
- 常见的进程状态包括:
- 进程上下文信息:在进程切换时,操作系统需要保存和恢复进程的运行环境。
- 上下文信息主要包括:
- CPU 寄存器:保存进程的寄存器值(如程序计数器 PC、堆栈指针 SP)。
- 程序计数器:指向进程当前执行的指令地址。
- 处理器状态字(PSW):保存进程的中断、优先级、运行模式等状态。
- 上下文信息主要包括:
- 内存管理信息:描述进程在内存中的布局和使用情况。
- 主要包括:
- 代码段、数据段、堆和栈的地址。
- 页表指针:用于虚拟内存管理,指向该进程的页表。
- 内存限制:进程可用的内存范围。
- 资源管理信息:描述进程使用的系统资源。
- 主要包括:
- 打开的文件:文件描述符表,记录进程打开的文件。
- I/O 设备:进程正在使用的输入/输出设备。
- 信号量:与进程同步和通信相关的信息。
- 主要包括:
- 进程调度信息:与调度器相关的信息,用于控制进程的执行顺序。
- 主要包括:
- 优先级:进程的优先级,用于决定调度的顺序。
- 时间片:进程能够占用 CPU 的时间长度。
- 调度队列指针:指向进程在调度队列中的位置。
- 主要包括:
- 进程间通信信息:描述进程与其他进程之间的通信机制。
- 主要包括:
- 信号:进程接收和发送的信号。
- 管道:进程间通过管道通信的相关信息。
- 共享内存:进程共享的内存区域。
- 主要包括:
在 Linux 操作系统中,进程控制块(PCB)被定义为一个结构体,称为 task_struct,它是操作系统内核中用于描述和管理进程的核心数据结构。每个进程在内核中都有一个与之对应的 task_struct 实例。(需要注意!!!实际的 task_struct 定义非常庞大,且随不同的内核版本和配置可能有所变化。)
task_struct结构图:
struct task_struct
{
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
/*
表示进程的当前状态:
TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。
TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。
TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。
TASK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块。
TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。导致这种状态的原因有二,或者是对收到SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。
TASK_SWAPPING: 进程页面被交换出内存的进程。
*/
unsigned long flags; //进程标志,与管理有关,在调用fork()时给出
int sigpending; //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
/*用户线程空间地址: 0..0xBFFFFFFF。
内核线程空间地址: 0..0xFFFFFFFF */
struct exec_domain *exec_domain; //进程执行域
volatile long need_resched; //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
unsigned long ptrace;
int lock_depth; //锁深度
long counter; //进程的基本时间片,在轮转法调度时表示进程当前还可运行多久,在进程开始运行是被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调 度。重新调度将从run_queue队列选出counter值最大的就绪进程并给予CPU使用权,因此counter起到了进程的动态优先级的作用
long nice; //静态优先级
unsigned long policy; //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR,分时进程:SCHED_OTHER
//在Linux 中, 采用按需分页的策略解决进程的内存需求。task_struct的数据成员mm 指向关于存储管理的mm_struct结构。
struct mm_struct *mm; //进程内存管理信息
int has_cpu, processor;
unsigned long cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned long sleep_time; //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表,其根是init_task
//在Linux 中所有进程(以PCB 的形式)组成一个双向链表,next_task和prev_task是链表的前后向指针
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm; //active_mm 指向活动地址空间。
struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal; //父进程终止是向子进程发送的信号
unsigned long personality;
int dumpable:1;
int did_exec:1;
pid_t pid; //进程标识符,用来代表一个进程
pid_t pgrp; //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp; //进程控制终端所在的组标识
pid_t session; //进程的会话标识
pid_t tgid;
int leader; //表示进程是否为会话主管
<br> //指向最原始的进程任务指针,父进程任务指针,子进程任务指针,新兄弟进程任务指针,旧兄弟进程任务指针。
struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
struct list_head thread_group; //线程链表
<br> //用于将进程链入HASH表,系统进程除了链入双向链表外,还被加入到hash表中
struct task_struct *pidhash_next;
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit; //供wait4()使用
struct semaphore *vfork_sem; //供vfork()使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
<br> //it_real_value,it_real_incr用于REAL定时器,单位为jiffies,系统根据it_real_value
//设置定时器的第一个终止时间.在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据it_virt_incr重置初值
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer; //指向实时定时器的指针
struct tms times; //记录进程消耗的时间
unsigned long start_time; //进程创建的时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];//记录进程在每个CPU上所消耗的用户态时间和核心态时间
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copyon Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数);nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt,cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid,euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;//进程的权能,分别是有效位集合,继承位集合,允许位集合
int keep_capabilities:1;
struct user_struct *user; //代表进程所属的用户
struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息
unsigned short used_math; //是否使用FPU
char comm[16]; //进程正在运行的可执行文件名
//文件系统信息
int link_count;
struct tty_struct *tty; //进程所在的控制终端,如果不需要控制终端,则该指针为空
unsigned int locks; /* How many file locks are being held */
//进程间通信信息
struct sem_undo *semundo; //进程在信号量上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号量操作而挂起时,他在该队列中记录等待的操作
struct thread_struct thread; //进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct fs_struct *fs; //文件系统信息,fs保存了进程本身与VFS(虚拟文件系统)的关系信息
struct files_struct *files; //打开文件信息
//信号处理函数
spinlock_t sigmask_lock; /* Protects signal and blocked */
struct signal_struct *sig; //信号处理函数
sigset_t blocked; //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending; //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock; //用于申请空间时用的自旋锁。自旋锁的主要功能是临界区保护
};
task_struct
是Linux内核的一种数据结构,它会被装载到RAM里并且包含进程的信息,每个进程都把它的信息放在 task_struct
这个数据结构里。
task_struct
的简化定义(供此处为大家分析使用)
struct task_struct {
// 进程标识信息
pid_t pid; // 进程 ID
pid_t tgid; // 线程组 ID(对于主线程,等于 pid)
// 父子关系
struct task_struct __rcu *parent; // 指向父进程的指针
struct list_head children; // 子进程链表
struct list_head sibling; // 同一父进程中其他进程的链表
// 进程状态信息
volatile long state; // 进程状态 (TASK_RUNNING, TASK_INTERRUPTIBLE 等)
unsigned int flags; // 进程标志 (如 PF_* 标志)
// 调度信息
int prio; // 动态优先级
int static_prio; // 静态优先级
int normal_prio; // 标准化优先级
unsigned int rt_priority; // 实时优先级
struct sched_entity se; // 调度实体(CFS 调度器使用)
struct sched_rt_entity rt; // 实时调度实体
// CPU 和上下文切换
struct thread_struct thread; // CPU 特定的线程结构
struct mm_struct *mm; // 指向内存描述符
struct mm_struct *active_mm; // 活动内存描述符
// 文件系统
struct fs_struct *fs; // 文件系统信息(当前目录、根目录等)
struct files_struct *files; // 打开的文件描述符表
// 虚拟内存
unsigned long min_flt; // 次要缺页数
unsigned long maj_flt; // 主要缺页数
unsigned long total_vm; // 虚拟内存总量
unsigned long rss; // 驻留集大小
unsigned long shared_vm; // 共享内存大小
unsigned long text_vm; // 文本段大小
unsigned long stack_vm; // 栈大小
// 进程间通信
struct signal_struct *signal; // 信号描述符
struct sighand_struct *sighand; // 信号处理程序
// 计时器
u64 utime; // 用户态运行时间
u64 stime; // 内核态运行时间
u64 start_time; // 进程启动时间
// 进程链表
struct list_head tasks; // 所有任务的链表
struct list_head ptrace_entry; // ptrace 调试链表
// 调试和其他字段
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events; // IRQ 相关事件
#endif
#ifdef CONFIG_DEBUG_PREEMPT
unsigned int preempt_count; // 抢占计数器
#endif
// 安全凭据
const struct cred *real_cred; // 真实凭据
const struct cred *cred; // 有效凭据
// 命名空间和 cgroups
struct nsproxy *nsproxy; // 命名空间代理
struct cgroup_subsys_state *cgroups; // 控制组信息
// 其他字段省略...
};
task_struct 的主要部分解析:
- 进程标识信息:用于唯一标识进程:
pid
:进程的唯一标识。tgid
:线程组 ID,用于线程管理(主线程的tgid
等于pid
)。parent
:指向父进程的task_struct
。
struct task_struct {
pid_t pid; // 进程 ID
pid_t tgid; // 线程组 ID(对于主线程,等于 pid)
struct task_struct *parent; // 指向父进程的指针
};
- 父子关系:记录进程的父进程和子进程的指针:
parent
:指向父进程的task_struct
。children
:存储子进程的链表。sibling
:存储同一父进程中的其他进程。
- 进程状态信息:
- state:进程当前的状态,可能取值:
- TASK_RUNNING:运行中或就绪状态。
- TASK_INTERRUPTIBLE:可被中断的等待状态。
- TASK_UNINTERRUPTIBLE:不可被中断的等待状态。
- TASK_STOPPED:进程停止。
- TASK_DEAD:进程终止。
exit_state
:表示进程退出时的状态。exit_code
:进程退出时的返回值。flags
:进程的标志位,用于描述特殊的属性(如是否为内核线程)。
- state:进程当前的状态,可能取值:
long state; // 进程状态
long exit_state; // 进程退出状态
int exit_code; // 进程退出码
unsigned int flags; // 进程标志
- 进程调度信息:与进程调度相关的字段:
prio
:动态优先级,调度器根据运行情况动态调整。static_prio
:静态优先级,由用户设置,范围为 0-139(0-99 是实时优先级,100-139 是普通进程)。sched_entity
:存储调度实体,用于 CFS 调度器,包含进程在调度器中的统计数据。sched_class
:指向调度类的指针(如 CFS 调度、实时调度)。
int prio; // 动态优先级
int static_prio; // 静态优先级
struct sched_entity se; // 调度实体,用于存储调度相关信息
struct sched_class *sched_class; // 调度类
- 内存管理信息:记录进程的虚拟内存布局:
mm
:指向用户态内存描述符,描述进程的代码段、数据段、堆、栈等。active_mm
:用于内核线程,指向最近使用的用户态内存描述符。pgd
:页表的基址,指向进程的页目录。
struct mm_struct *mm; // 用户态内存描述符
struct mm_struct *active_mm; // 当前活动的内存描述符
unsigned long *pgd; // 页表的基址
- 文件系统信息:与文件操作相关的字段:
files
:指向打开文件的描述符表,包含所有已打开文件的文件描述符。fs
:描述文件系统信息(包括当前工作目录、根目录等)。
struct files_struct *files; // 打开文件的描述符表
struct fs_struct *fs; // 文件系统信息
- 进程计时信息:记录进程的运行时间和 CPU 使用情况:
utime
:进程在用户态运行的时间。stime
:进程在内核态运行的时间。start_time
:进程启动的时间戳。
u64 utime; // 用户态运行时间
u64 stime; // 内核态运行时间
u64 start_time; // 进程启动时间
- 进程间通信:记录进程之间的同步和通信信息:
signal
:保存进程接收到的信号信息。sighand
:指向信号处理函数的指针。
struct signal_struct *signal; // 信号描述符
struct sighand_struct *sighand; // 信号处理函数
- 调试与安全:保存进程的调试状态,用于调试器(如 GDB):
- 调试和安全相关的字段如
ptrace_entry
和cred
。
- 调试和安全相关的字段如
- 内核线程标志:
- 用于区分内核线程和用户进程。
task_struct
的字段结构大体如下:
+------------------------+
| pid | 进程 ID
+------------------------+
| state | 进程状态
+------------------------+
| prio | 优先级
+------------------------+
| parent | 父进程指针
+------------------------+
| mm | 内存管理信息
+------------------------+
| files | 打开文件表
+------------------------+
| fs | 文件系统信息
+------------------------+
| utime | 用户态运行时间
+------------------------+
| stime | 内核态运行时间
+------------------------+
| signal | 信号信息
+------------------------+
| thread | 线程上下文
+------------------------+
task_struct
的存储与访问
- 存储方式:所有进程的
task_struct
以链表形式存储在内核中。- 这些任务链表分为:
- 就绪队列:存储所有可运行的任务。
- 等待队列:存储正在等待资源的任务。
- 这些任务链表分为:
- 访问方式:当前运行进程的
task_struct
可以通过宏current
获取: - 与调度的关系:
- 就绪队列:
task_struct
被组织成双向链表,调度器通过就绪队列管理处于就绪状态的任务。 - 上下文切换:调度器保存当前进程的
task_struct
的上下文(如寄存器值、程序计数器),然后加载下一个进程的task_struct
的上下文。
- 就绪队列:
struct task_struct *current_task = current;
task_struct
的完整定义位于 Linux 内核源代码的 sched.h
文件中:
include/linux/sched.h
你可以从 Linux Kernel 源代码 下载对应版本,查看 sched.h 文件中的完整定义。
详细内容也可直接查阅书籍中的原文,链接为:linux_kernel_wiki/文章/进程管理/Linux内核进程管理原理详解(代码演示).md
总之,task_struct 是 Linux 内核中描述和管理进程的核心数据结构,其内容涵盖了进程的所有信息,包括标识、状态、调度、内存、文件、通信等。操作系统通过 task_struct 来管理进程的生命周期、调度和资源分配。
需要注意的是:
task_struct
是 Linux 内核中最复杂的数据结构之一。它紧密结合了内核的多个子系统(如调度器、内存管理、信号处理等)。- 版本差异:task_struct 的定义会随 Linux 内核版本发生变化,字段可能被添加、删除或重命名。使用时要注意版本号。
- 用户态不可用,用户态程序无法直接访问 task_struct,只能通过系统调用(如 getpid、getppid)间接获取进程信息。
虽然 task_struct 的结构复杂,但其核心作用是为进程提供全面的信息支持,使得多任务操作系统能够高效地运行。通过深入理解 task_struct
,可以更好地掌握进程管理的原理和 Linux 内核的实现。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!