当前位置: 代码迷 >> 综合 >> 【INT的内核笔记】梳理sleep_avg,prio,activated,timestamp,last_ran等重要调度变量
  详细解决方案

【INT的内核笔记】梳理sleep_avg,prio,activated,timestamp,last_ran等重要调度变量

热度:7   发布时间:2023-12-06 11:34:01.0

1. sleep_avg

1.1 sleep_avg简介

sleep_avg处在task_struct数据结构中,

sleep其实和平均没有什么关系,是一个睡眠时间评估值,命名可能有历史原因。

直接关系到进程动态优先级prio的计算:

动态优先级prio = 静态优先级static_prio - CURRENT_BONUS(p) + 5
CURRENT_BONUS(p)和p->sleep_avg是正相关的,prio越小越好,因此:
sleep_avg越大,调用effective_prio(p)重新计算动态优先级时,动态优先级提高得越多。

1.2 sleep_avg相关数学关系

涉及到的一些数学关系(都是经验公式,不必太深究):

  • 平均睡眠时间sleep_avg ↑,CURRENT_BONUS§ ↑,prio的增量 ↑;

  • 平均睡眠时间sleep_avg ↓,CURRENT_BONUS§ ↓,

    sleep_avg增长量相关值MAX_BONUS - CURRENT_BONUS§ ↑;

  • 动态优先级prio = 静态优先级static_prio - CURRENT_BONUS§ + MAX_BONUS / 2,

    MAX_BONUS / 2 = 5,

    动态优先级prio = 静态优先级static_prio - CURRENT_BONUS§ + 5;

  • CURRENT_BONUS§就是ULK中所说的bonus,与sleep_avg正相关;

1.3 sleep_avg的更新

sleep_avg一般在recalc_task_prio()中被更新,也会在schedule中被直接更新。

sleep_avg的更新如下:

  • 被唤醒时,经由以下调用链在recalc_task_prio()中更新:

    ... --> 上层wake_up相关封装 --> try_to_wake_up() --> activate_task()--> recalc_task_prio()
    

    主要是在sleep_avg中加上睡眠时间sleep_time

    (会乘以一些倍率,也有不同的上限,具体见recalc_task_prio());

  • 从TASK_INTERRUPTIBLE被唤醒后,在正式被调度到时,经由以下调用链在recalc_task_prio()中更新:

    ... --> schedule() --> recalc_task_prio()
    

    主要是在sleep_avg中加上被唤醒之后,到被真正被调度到这一段时间delta

    (会根据是被中断等等唤醒,还是被系统调用等等唤醒(最典型就是信号唤醒),乘以一些倍率);

  • 当进程主动放弃CPU或被剥夺CPU,也就是在schedule()作为prev时,经由以下调用链在schedule()中更新:

    ... --> schedule()
    

    主要是减去运行时间run_time

    (会根据由sleep_avg计算出的CURRENT_BONUS,乘以一些倍率,
    sleep_avg越大,倍率越小,减掉的run_time越小)

  • 在sched_exit()时候,更新其父进程的平均睡眠时间p->parent->sleep_avg,还未研究清楚;

2. prio

2.1 prio简介

prio一般称作动态优先级或者实际优先级,

实际优先级其实更贴切,因为在实时进程中prio是不会随着进程运行时间而改变的,

不过之前的笔记都称之为动态优先级,因此还是继续沿用算了。

动态优先级prio正是用于在发生调度时挑选进程的。

如上一节所述,普通进程的prio计算公式为:

动态优先级prio = 静态优先级static_prio - CURRENT_BONUS(p)  + 5;

2.2 prio的更新

  • 伴随着sleep_avg,一起在recalc_task_prio()中更新,可参考上一节;

  • 在scheduler_tick()中,普通进程在消耗完时间片时直接调用effective_prio()进行更新。

    我之前很疑惑,为什么在scheduler_tick()要进行更新prio的操作?

    现在大致有了一点思路,分析schedule()函数可以得知,schedule()在

    prev->sleep_avg -= run_time;
    

    这一步之后,并没有任何关于更新prio的操作了,而schedule()关于prio更新的操作,也只限于next是被唤醒的。

    所以对于在TASK_RUNNING状态被切换的prev进程,重新切换回来之后,要怎么更新prio呢?

    我觉得scheduler_tick()中的这个prio更新步骤,就是为了解决这个问题的;

  • 在wake_up_new_task()中,直接调用effective_prio()进行更新。

    这个看名字就知道,是进程被创建之后计算prio了;

  • 在set_user_nice(),直接执行p->prio += delta进行更新。

    用户直接更新static_prio的一个系统调用,static_prio更新了,prio自然要一起更新;

3. activated

3.1 activated简介

activated表达的是从什么状态被唤醒,所以这是在什么时候设置的呢?

那当然是在被唤醒,也就是try_to_wake_up()函数中被设置的。

我之前混淆了这两个概念:

  • 从什么状态进入睡眠;
  • 从什么状态被唤醒;

activated有以下几个值:

  • 0,进程处于TASK_RUNNING状态;

  • 1,进程处于TASK_INTERRUPTIBLE或者TASK_STOPPED状态,

    而且正在被系统调用服务例程或内核线程唤醒。

    常见例子就是被信号唤醒;

  • 2,进程处于TASK_INTERRUPTIBLE或者TASK_STOPPED状态,

    而且正在被ISR或者可延迟函数唤醒。

    常见例子就是IO中断;

  • -1,表示从UNINTERRUPTIBLE状态被唤醒。

    据实验和源码,普通文件read()进入睡眠后,就是UNINTERRUPTIBLE状态;

3.2 activated的更新

  • 在try_to_wake_up()中设置,

    如果当前进程是被从TASK_UNINTERRUPTIBLE状态唤醒,则直接设置activated = -1;

    否在会在调用activate_task()后,在activated_task()中设置,具体如下:

    	recalc_task_prio(p, now);// 为何在recalc_task_prio()才设置呢?// 因为在recalc_task_prio()依靠0和-1来区分// TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLEif (!p->activated) {
          if (in_interrupt())// 如被中断处理等等唤醒p->activated = 2;else {
          // 如被系统调用等等唤醒p->activated = 1;}}p->timestamp = now;
    
  • 在schedule()中设置,

    当选定next进程后,也就是即将占用CPU的进程,会将其activated值置为0。

    	/*** 正式被选择了,所以activated清零。*/ next->activated = 0;
    

4. timestamp, last_ran

4.1 timestamp, last_ran简介

  • timestamp,时间戳,主要用于now - timestamp求出睡眠时长、运行时长等;

  • last_ran,最近被在schedule()作为prev进程被切换出去的时间,如何起作用还未详细研究,

    关于last_ran的更新不单独讲了,暂时只发现下面代码段中和timestamp一起被设置;

4.2 timestamp的更新

  • 在try_to_wake_up() --> activate_task()被设置,在recalc_task_prio()之后设置。

    这里更新timestamp作用之一是,之后在schedule()中计算唤醒后到被调度到的等待时间;

  • 在schedule()中,prev在被切换出去时会设置timestamp,如下:

    switch_tasks:.../*** 更新prev的时间戳,暂时发现了以下两点作用,可能不全:* 1.schedule()其中一个步骤是把TASK_INTERRUPTIBLE的进程移出可执行队列,* 但是,我们会发现在那个代码段中timestamp没有设置,而陷入睡眠的时间戳是需要设置的,* 答案就是,在这步中统一设置。* 内核的一点小优化,内核中不少代码在不同情境有种不同语义。* * 2.注意下面prev != next这个判断,当不满足条件也就是next == prev时,* 这时候的代码段中是没有对于next->timestamp的设置的。* 原因也是在于prev->timestamp这步,在这种情况下相当于设置next->timestamp。* 一句多义,感觉是挺巧妙的。*/prev->timestamp = prev->last_ran = now;sched_info_switch(prev, next);if (likely(prev != next)) {
          /* prev和next不同,需要切换 */next->timestamp = now;...}  else/* 如果prev和next是同一个进程,就不做进程切换。当prev仍然是当前活动集合中的最高优先级进程时,这是有可能发生的。 */spin_unlock_irq(&rq->lock)
    
  • 在schedule()中,next在被切换进来时会设置timestamp,如上;

  • 在新创建进程时的相关函数wake_up_new_task()和sched_fork()中被设置,还未详细研究;

  • 在多CPU间负载均衡的相关函数__migrate_task()和pull_task()中被设置,还未详细研究;

参考

baoyou.xie Linux 2.6.12代码注释
《深入理解linux内核》

  相关解决方案