【进程与线程】进程控制块(Process Control Block, PCB)

进程控制块(Process Control Block, PCB)

进程控制块(PCB) 是操作系统中用来描述和管理进程的重要数据结构。每个进程在创建时,操作系统都会为其分配一个 PCB,PCB 中保存了该进程的所有重要信息,包括进程的状态、资源使用情况以及与其他进程的关系。

PCB 是操作系统管理进程的核心数据结构,每个进程都有一个唯一的 PCB

进程控制块的作用

进程控制块 的主要作用是维护进程的状态和信息,以便操作系统能够高效地管理和调度进程。 作用有很多,包括:

  1. 唯一标识进程:通过 进程控制块 操作系统可以区分不同的进程。
  2. 存储进程状态:保存进程当前的运行状态(如正在运行、等待等)。
  3. 资源管理:记录进程使用的 CPU、内存、文件等资源信息。
  4. 调度和切换:在进程切换时,操作系统通过 PCB 保存和恢复进程的上下文。
进程控制块的组成部分

PCB 是一个复杂的数据结构,通常包含以下几类信息:

  1. 进程标识信息:用于唯一标识一个进程。
    • 主要包括:
      • 进程 ID(PID):每个进程的唯一标识号。
      • 父进程 ID(PPID):创建该进程的父进程的 ID。
      • 用户 ID 和组 ID:进程所属用户和用户组的标识。
  2. 进程状态信息:用于描述进程的当前状态。
    • 常见的进程状态包括:
      • 新建(New):进程正在被创建。
      • 就绪(Ready):进程已准备好运行,等待 CPU 调度。
      • 运行(Running):进程正在使用 CPU 执行。
      • 等待(Waiting/Blocked):进程因等待资源(如 I/O)而暂停。
      • 终止(Terminated):进程已结束。
    • PCB 中保存着进程的状态以及状态变化的相关信息。
  3. 进程上下文信息:在进程切换时,操作系统需要保存和恢复进程的运行环境。
    • 上下文信息主要包括:
      • CPU 寄存器:保存进程的寄存器值(如程序计数器 PC、堆栈指针 SP)。
      • 程序计数器:指向进程当前执行的指令地址。
      • 处理器状态字(PSW):保存进程的中断、优先级、运行模式等状态。
  4. 内存管理信息:描述进程在内存中的布局和使用情况。
    • 主要包括:
    • 代码段、数据段、堆和栈的地址
    • 页表指针:用于虚拟内存管理,指向该进程的页表。
    • 内存限制:进程可用的内存范围。
  5. 资源管理信息:描述进程使用的系统资源。
    • 主要包括:
      • 打开的文件:文件描述符表,记录进程打开的文件。
      • I/O 设备:进程正在使用的输入/输出设备。
      • 信号量:与进程同步和通信相关的信息。
  6. 进程调度信息:与调度器相关的信息,用于控制进程的执行顺序。
    • 主要包括:
      • 优先级:进程的优先级,用于决定调度的顺序。
      • 时间片:进程能够占用 CPU 的时间长度。
      • 调度队列指针:指向进程在调度队列中的位置。
  7. 进程间通信信息:描述进程与其他进程之间的通信机制。
    • 主要包括:
      • 信号:进程接收和发送的信号。
      • 管道:进程间通过管道通信的相关信息。
      • 共享内存:进程共享的内存区域。

在 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 的主要部分解析:
  1. 进程标识信息:用于唯一标识进程:
    • pid:进程的唯一标识。
    • tgid:线程组 ID,用于线程管理(主线程的 tgid 等于 pid)。
    • parent:指向父进程的 task_struct
struct task_struct {
    pid_t pid;                // 进程 ID
    pid_t tgid;               // 线程组 ID(对于主线程,等于 pid)
    struct task_struct *parent; // 指向父进程的指针
};
  1. 父子关系:记录进程的父进程和子进程的指针:
    • parent:指向父进程的 task_struct
    • children:存储子进程的链表。
    • sibling:存储同一父进程中的其他进程。
  2. 进程状态信息:
    • state:进程当前的状态,可能取值:
      • TASK_RUNNING:运行中或就绪状态。
      • TASK_INTERRUPTIBLE:可被中断的等待状态。
      • TASK_UNINTERRUPTIBLE:不可被中断的等待状态。
      • TASK_STOPPED:进程停止。
      • TASK_DEAD:进程终止。
    • exit_state:表示进程退出时的状态。
    • exit_code:进程退出时的返回值。
    • flags:进程的标志位,用于描述特殊的属性(如是否为内核线程)。
long state;                   // 进程状态
long exit_state;              // 进程退出状态
int exit_code;                // 进程退出码
unsigned int flags;           // 进程标志
  1. 进程调度信息:与进程调度相关的字段:
    • 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; // 调度类
  1. 内存管理信息:记录进程的虚拟内存布局:
    • mm:指向用户态内存描述符,描述进程的代码段、数据段、堆、栈等。
    • active_mm:用于内核线程,指向最近使用的用户态内存描述符。
    • pgd:页表的基址,指向进程的页目录。
struct mm_struct *mm;         // 用户态内存描述符
struct mm_struct *active_mm;  // 当前活动的内存描述符
unsigned long *pgd;           // 页表的基址
  1. 文件系统信息:与文件操作相关的字段:
    • files:指向打开文件的描述符表,包含所有已打开文件的文件描述符。
    • fs:描述文件系统信息(包括当前工作目录、根目录等)。
struct files_struct *files;   // 打开文件的描述符表
struct fs_struct *fs;         // 文件系统信息
  1. 进程计时信息:记录进程的运行时间和 CPU 使用情况:
    • utime:进程在用户态运行的时间。
    • stime:进程在内核态运行的时间。
    • start_time:进程启动的时间戳。
u64 utime;                   // 用户态运行时间
u64 stime;                   // 内核态运行时间
u64 start_time;              // 进程启动时间
  1. 进程间通信:记录进程之间的同步和通信信息:
    • signal:保存进程接收到的信号信息。
    • sighand:指向信号处理函数的指针。
struct signal_struct *signal; // 信号描述符
struct sighand_struct *sighand; // 信号处理函数
  1. 调试与安全:保存进程的调试状态,用于调试器(如 GDB):
    • 调试和安全相关的字段如 ptrace_entrycred
  2. 内核线程标志:
    • 用于区分内核线程和用户进程。

task_struct 的字段结构大体如下:

+------------------------+
| pid                   | 进程 ID
+------------------------+
| state                 | 进程状态
+------------------------+
| prio                  | 优先级
+------------------------+
| parent                | 父进程指针
+------------------------+
| mm                    | 内存管理信息
+------------------------+
| files                 | 打开文件表
+------------------------+
| fs                    | 文件系统信息
+------------------------+
| utime                 | 用户态运行时间
+------------------------+
| stime                 | 内核态运行时间
+------------------------+
| signal                | 信号信息
+------------------------+
| thread                | 线程上下文
+------------------------+

task_struct 的存储与访问

  1. 存储方式:所有进程的 task_struct 以链表形式存储在内核中。
    • 这些任务链表分为:
      • 就绪队列:存储所有可运行的任务。
      • 等待队列:存储正在等待资源的任务。
  2. 访问方式:当前运行进程的 task_struct 可以通过宏 current 获取:
  3. 与调度的关系:
    • 就绪队列: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 来管理进程的生命周期、调度和资源分配。

需要注意的是:

  1. task_struct 是 Linux 内核中最复杂的数据结构之一。它紧密结合了内核的多个子系统(如调度器、内存管理、信号处理等)。
  2. 版本差异:task_struct 的定义会随 Linux 内核版本发生变化,字段可能被添加、删除或重命名。使用时要注意版本号。
  3. 用户态不可用,用户态程序无法直接访问 task_struct,只能通过系统调用(如 getpid、getppid)间接获取进程信息。

虽然 task_struct 的结构复杂,但其核心作用是为进程提供全面的信息支持,使得多任务操作系统能够高效地运行。通过深入理解 task_struct,可以更好地掌握进程管理的原理和 Linux 内核的实现。

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫猫的小茶馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值