转载请注明出处:jiq?钦‘s
technical Blog
数据结构:
每个处理器维护一个运行队列,主要字段如图所示。
每个运行队列有两个优先级队列,一个活跃的(时间片未完),另一个是过期的(时间片已完)。每个队列都有一个位图,用于快速寻找到当前队列中的最高优先级。
Schedule()函数执行步骤如下:
(1)在活动优先级队列的位图数组中找到当前最高优先级;
(2)按照这个优先级数值查找优先级数组,然后在挂在该位置的进程队列(具有相同优先级)上面取下第一个;
(3)如果不是当前正在运行的进程,则做任务切换(调用context_switch()函数)。
何时调用schedule()函数?
(1)当前进程休眠(阻塞)(主动):将自己标记为休眠状态,把自己从可执行队列移除放入等待队列,然后调用schedule()函数;
(2)当前进程结束(主动):执行do_exit()函数的最后一个步骤,就是调用schedule()函数;
(3)被抢占(被动):这是最为复杂的部分,主要靠一个“全局标志”need_resched完成。
两种情况下这个标志会被设置:
(I)当某个进程时间片耗尽时scheduler_tick()函数设置;
(II)当一个高优先级任务进入就绪态时try_to_wake_up()函数设置;
标志虽然设置了,但是没有真正启动调度器,什么时候启动呢?
(I)用户抢占:
1 执行系统调用(利用中断实现)返回用户空间时检查need_resched标志,如果被设置了就启用调度器:
2 执行中断处理程序返回用户空间时检查need_resched标志,如果被设置了就启用调度器;
(II)内核抢占:
注:增加一个标志preempt_count表示当前运行任务(current)所持有锁的数量。
1 中断处理程序返回内核空间时检查如果need_resched标志被设置,并且preempt_count标志值为0,则启用调度器;
2 当前进程执行释放锁的代码会检查preempt_count值若为0(没有持有锁了)并且设置了need_resched标志,则启用调度器;
3 内核显示调用schedule()函数;
注:可以使用preempt_disable()/preempt_enable()函数对禁止和允许内核抢占。
Linux进程调度策略:
多任务,抢占式。
基于优先级调度,时间片轮转。
动态优先级,动态时间片。
或者说:
优先级高的先运行,低的后运行,相同优先级的按轮转方式调度。
调度程序总是选择时间片未用尽且优先级最高的进程运行
!!!
动态优先级如何计算?
每个任务两个优先级:
(1)一个静态优先级。作为初始优先级+作为计算动态优先级基础;
(2)一个动态优先级。其值
= 静态优先级+交互性强度(基于任务休眠时间来计算);
因为我们需要动态提高交互性强的I/O消耗型任务的优先级,I/O消耗型任务执行频率高,执行时间短,休眠时间长,交互性强度高,所以导致动态优先级高。
动态时间片如何计算?
优先级越高,时间片越长,nice值(优先级数值)越小,执行频率越高!!!
(比如I/O消耗型)
可见优先级和时间片长度成正比!!!
所以只需要将优先级按一定比例缩放,符合时间片数值范围,就是时间片值了。
如何保证交互性强的任务(如I/O消耗性)执行频率高?
在schedler_tick()函数中:
(1) 当前任务时间片减一操作;
(2)若减为零,判断如果不是交互性强的任务或者过期数组中存在“饥饿进程”,则将当前任务加入过期数组;
否则加入活跃数组(这样就保证了执行频率高些)。
也就是说每个时钟周期会检查一次,将交互性强的任务再放回到活跃数组,让其尽快有机会再次得到执行。