$cover

进程管理-1

进程管理应该可以说是操作系统最重要的部分了,几个月前学到这里也没学完就忙毕设去了,忙完毕设重新捡起来看看。

进程描述符

进程描述符task_struct由很大一块组成,定义在linux/include/linux/sched.h中。

进程基本信息

pid_t              pid;
pid_t               tgid;
char               comm[TASK_COMM_LEN];
unsigned int           __state;
    /*
     * Pointers to the (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->real_parent->pid)
     */

    /* Real parent process: */
    struct task_struct __rcu    *real_parent;

    /* Recipient of SIGCHLD, wait4() reports: */
    struct task_struct __rcu    *parent;

    /*
     * Children/sibling form the list of natural children:
     */
    struct list_head        children;
    struct list_head        sibling;
    struct task_struct      *group_leader;
  • pid:进程 ID,即进程的唯一标识符。
  • tgid:线程组 ID,对于线程来说,它与 PID 相同,但对于线程组中的其他线程,TGID 是相同的。通过tgid == pid判断是否为线程组主进程
  • comm:进程的名称(字符串),通常是程序名称,最长 16 字符。
  • __state:进程的当前状态(TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE 等)。
  • parent:指向父进程的指针(task_struct *parent)。指向​​逻辑父进程​​,可能因调试或信号处理被临时修改(如调试时指向调试器进程)
  • children:指向子进程链表的指针(list_head children)。
  • sibling: 兄弟进程链表指针,用于将当前进程插入父进程的children链表中
  • real_parent:指向进程创建者的指针(进程的实际父进程,可能是某个线程)。指向进程的​​实际父进程​​。即使父进程被调试器(如GDB)替换,该字段仍指向原始父进程
  • group_leader:指向线程组领导者(主进程)的指针。

此处与书中/大多数博客相比,将原来的volatile state变更为了unsigned int __state,下面是更改日志

Commit 2f064a5
sched: Change task_struct::state
Change the type and name of task_struct::state. Drop the volatile and
shrink it to an ‘unsigned int’. Rename it in order to find all uses
such that we can use READ_ONCE/WRITE_ONCE as appropriate.
Signed-off-by: Peter Zijlstra (Intel) peterz@infradead.org
Reviewed-by: Daniel Bristot de Oliveira bristot@redhat.com
Acked-by: Will Deacon will@kernel.org
Acked-by: Daniel Thompson daniel.thompson@linaro.org
Link: https://lore.kernel.org/r/20210611082838.550736351@infradead.org

  1. 目标背景
    task_struct 是 Linux 内核中的一个结构体,用于表示一个任务(或线程)。其中的 state 成员变量表示任务的当前状态(如就绪、运行、睡眠等)。传统上,这个变量被定义为 volatile 类型,目的是避免编译器优化,确保访问该变量时直接从内存中读取或写入。
    然而,volatile 关键字并不能提供真正的内存同步保障,在多核环境下存在缺陷,因此更现代的代码习惯是用 READ_ONCE 和 WRITE_ONCE 宏来确保变量的正确访问,而不是依赖 volatile。
  2. 具体任务
    a. 去掉 volatile 关键字
  • 传统的 volatile 使用已经不推荐,因为它无法保证多核同步或者避免数据竞争。
  • 改用更明确的机制(如 READ_ONCE 和 WRITE_ONCE),这些宏在 Linux 内核中提供了对内存访问的安全控制。
    b. 缩小变量类型为 unsigned int
  • 将变量类型改为 unsigned int(无符号整数)可以减小结构体的大小,同时满足状态的需求。
  • 一般来说,任务状态只需要几个位来表示,因此不需要使用较大的数据类型(如 long 或其他)。
    c. 重命名 state
  • 重命名 task_struct::state 是为了方便后续代码检查和替换。
  • 新的名字会使代码中对该变量的引用更容易被识别(通过搜索),从而确保在所有读/写访问处添加 READ_ONCE 和 WRITE_ONCE 宏。
  1. READ_ONCE 和 WRITE_ONCE
  • READ_ONCE(x):确保读取变量时,编译器不会优化读取操作,并从内存中正确地读取该值。
  • WRITE_ONCE(x, val):确保写入变量时,编译器不会优化写入操作,并将值正确地写入内存。
    这两个宏被广泛用于内核代码中以替代 volatile,特别是在涉及并发访问时,能提供更好的性能和正确性。

调度相关信息


    int             on_rq;

    int             prio;
    int             static_prio;
    int             normal_prio;
    unsigned int            rt_priority;
    unsigned int           policy;
    const struct sched_class   *sched_class;
    int                nr_cpus_allowed;
  • on_rq: 标记进程当前是否处于可运行队列中。取值:0(未在队列)或1(在队列)
  • prio:进程的优先级。范围:-20 (最高) 到 139 (最低)
  • static_prio:静态优先级(不受动态调整的影响)。用户空间设置的原始优先级.通过nice值转换:static_prio = nice + 120
  • normal_prio:常规优先级(经过调度器计算得到)。
  • 实时进程:normal_prio = 99 - rt_priority。
  • 普通进程:normal_prio = static_prio
  • rt_priority: 仅对实时进程有效,数值越大优先级越高.范围:1 (最低) 到 99 (最高)
  • policy:调度策略
  • SCHED_NORMAL (0):普通进程,使用CFS调度器
  • SCHED_FIFO (1):实时先进先出,无时间片限制
  • SCHED_RR (2):实时轮转调度,有时间片分配
  • SCHED_BATCH (3):批处理任务,减少上下文切换
  • SCHED_IDLE (5):最低优先级任务
  • sched_class:调度类,表示进程所属的调度类别(例如 CFS 调度类)。
  • nr_cpus_allowed: 进程可运行的CPU核心数量限制