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内核》