1.进程有两个优先级,一个是静态优先级,一个是动态优先级.静态优先级是用来计算进程运行的时间片长度的,动态优先级是在调度器进行调度时用到的,调度器每次都选取动态优先级最高的进程运行.
静态优先级的计算:
nice值和静态优先级之间的关系是:静态优先级=100+nice+20
而nice值的范围是-20~19,所以普通进程的静态优先级的范围是100~139
进程运行的时间片长度的计算:
静态优先级<120,基本时间片=max((140-静态优先级)*20, MIN_TIMESLICE)
静态优先级>=120,基本时间片=max((140-静态优先级)*5, MIN_TIMESLICE)
其中MIN_TIMESLICE为系统规定的最小时间片.从该计算公式可以看出,静态优先级越高(值越低),进程得到的时间片越长
动态优先级的计算:
动态优先级=max(100 , min(静态优先级 – bonus + 5 , 139))
从上面看出,动态优先级的生成是以静态优先级为基础,再加上相应的惩罚或奖励(bonus).这个bonus并不是随机的产生,而是根据进程过去的平均睡眠时间做相应的惩罚或奖励.交互性强的进程会得到调度程序的奖励(bonus为正),而那些一直霸占CPU的进程会得到相应的惩罚(bonus为负).
普通进程的动态优先级的范围是100~139,实时进程的动态优先级的范围是0~99
2.几个重要的数据结构:
<运行队列>:每个cpu都有一个运行队列
struct runqueue{
spinlock_t lock; //保护进程链表的自旋锁
unsigned long nr_running;//运行队列链表中进程数量
unsigned long long nr_switches;//CPU执行进程切换的次数
unsigned long nr_uninterruptible;//之前在运行队列链表中而现在处于重度睡眠状态的进程总数
unsigned long expired_timestamp;//过期队列中最老的进程被插入队列的时间
unsigned long long timestamp_last_tick;//最近一次定时器终端的时间
task_t *curr;//指向本地CPU当前正在运行的进程的进程描述符,即current
task_t *idle;//指向本地CPU上的idle进程描述符的指针
struct mm_struct *prev_mm;//在进程进行切换时用来存放被替换进程内存描述符的地址
prio_array_t *active;//指向可运行队列中活动链表
prio_array_t *expired;//指向可运行队列中过期链表
prio_array_t arrays[2];//该数组的元素分别表示可运行队列中的活动进程集合和过期进程集合
int best_expired_prio;//过期进程中优先级最高的进程
...
}
<优先级数组>: 该结构体中有一个用来表示进程动态优先级的数组queue
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define MAX_PRIO (MAX_RT_PRIO + 40)
typedef struct prio_array prio_array_t;
struct prio_array {
unsigned int nr_active;
unsigned long bitmap[BITMAP_SIZE];
struct list_head queue[MAX_PRIO]; //0~139
};
queue数组中包含140个可运行状态的进程链表,每一条优先级链表上的进程都具有相同的优先级
prio_array结构中还包括一个优先级位图bitmap.该位图使用一个位(bit)来代表一个优先级. 比如第一个位为1则表示第一条优先级链表上有进程.
3.活动进程和过期进程:
O(1)调度算法将系统中的可运行进程分为两类:
活动进程: 那些还没有用完时间片的进程
过期进程: 那些已经用完时间片的进程
调度程序的工作就是在活动进程集合中选取一个最佳优先级的进程,如果该进程时间片恰好用完,就将该进程放入过期进程集合中.
不管是过期进程集合还是活跃进程集合,都将每个优先级的进程组成一个链表,因此每个集合就有140个不同优先级的进程链表.同时,两个集合中还采用优先级位图来标记每个优先级链表中是否存在进程.
在可运行队列结构中,arrays数组的两个元素分别用来表示刚才所述的活动进程集合和过期进程集合,active和expired两个指针分别直接指向这两个集合
4.O(1)调度算法的进程调度:
调度程序每次都是选取优先级最高而且还有剩余时间片的进程来运行.在选取最高优先级的进程时,首先利用优先级位图从高到低找到第一个被设置的位,该位对应着一条进程链表,这个链表中的进程是当前系统所有可运行进程中优先级最高的.在该优先级链表中选取头一个进程,它拥有最高的优先级,即为调度程序马上要执行的进程.
代码实现:
struct task_struct *prev, *next;
struct list_head *queue;
struct prio_array *array;
int idx;
prev = current;
array = rq->active;
idx = sehed_find_first_bit(array->bitmap); //找到位图中第一个不为0的位的序号
queue = array->queue + idx; //得到对应的队列链表头
next = list_entry(queue->next, struct task_struct, run_list); //得到进程描述符
if (prev != next) //如果选出的进程和当前进程不是同一个,则交换上下文
context_switch();
5.周期性调度函数schedule_tick()分析:
schedule_tick函数用来更新进程的时间片,它被调用时本地中断被禁止. 函数步骤如下:
1.首先通过相应的函数和宏获得当前处理器的编号、当前可运行队列和当前进程描述符.再通过sched_clock函数获得最近一次定时器中断的时间戳
int cpu = smp_processor_id();
runqueue_t *rq = this_rq();
task_t *p = current;
rq->timestamp_last_tick = sched_clock();
2.如果当前进程是普通进程,则只需递减当前进程的时间片.如果时间片还未用完,则函数返回.
3.如果当前进程时间片用完,首先从当前活动进程集合中调用dequeue_task()函数删除该进程,然后通过set_tsk_need_resched函数设置TIF_NEED_RESCHED标志,以便稍后重新进行进程调度.
4.接着通过effective_prio()函数更新当前进程的动态优先级,进程的动态优先级是以进程的静态优先级(static_prio)为基数,在通过bonus适当的对其惩罚或奖励:
动态优先级=max(100 , min(静态优先级 – bonus + 5, 139))
5.如果当前进程的时间片已经用完,则调用task_timeslice函数对当前进程重新分配时间片
2~5步的代码:
if (!--p->time_slice) {
dequeue_task(p, rq->active); //从活动进程中删除
set_tsk_need_resched(p); //设置TIF_NEED_RESCHED标志
p->prio = effective_prio(p); //计算动态优先级
p->time_slice = task_timeslice(p); //分配时间片
6.如果当前进程的时间片已经用完,则需要判断是否把该进程移入过期队列中:
1.如果当前进程不是交互性进程, 则在上述步骤中已经为该进程重新分配了时间片和动态优先级, 只需把该进程移
入相应的过期进程链表中. 加入队列的函数是enqueue_task().
2.如果当前进程是交互性进程, 如果下述两个条件都不满足的情况下才重新加入活动链表, 否则要加入相应的过
期链表队列中
1.如果第一个进入过期进程链表的进程时间离现在已经很久了
2.如果过期进程中静态优先级最高的进程比当前交互性进程的静态优先级高
当过期队列中的第一个进程加入过期队列中时,需要把时间记录在expired_timestamp变量中.
每当一个进程加入过期队列时,都要判断是否更新best_expired_prio变量,该变量是用来记录过期队列中静态优先
级的最高值
7.当活动进程链表都为空时,就把活动进程链表和过期进程链表交换过来,重新调度过期队列中的进程,此时先前的活动进程链表就变成了过期进程链表
8.O(1)调度算法的问题:
设想系统只有两个进程在运行,讲述两种不同情况下的运行结果:
第一个问题
1.两个进程的nice值都为0. 则按照前面所讲的方法,nice值为0的进程的静态优先级为100+0+20=120, 时间片为
(140-120)*5=100ms. 则进程的运行情况是, 第一个进程先运行100ms,第二个进程再运行100ms, 这样系统的进程切
换时间就是100ms.
2.两个进程的nice值都为19, 则静态优先级为139. 时间片是5ms. 则进程的运行情况是, 第一个进程先运行5ms,
第二个进程再运行5ms, 这样系统的进程切换时间就是5ms
但是nice值小的进程, 一般是交互性进程, nice值大的进程, 一般是后台运行的任务. 交互性的进程要求响应迅速,
运行时间短, 而上述情况是进程的切换时间相对太长, 即响应不迅速, 而且分配的时间片太长. 相反, 后台运行
的任务对响应速度不太敏感, 要求运行时间长一点, 而上述情况是切换时间短, 即响应迅速, 而分配的时间片太短.
综上所述, 当系统中的交互性程序过多时, 进程对交互性进程的响应速度不太理想. 因为当同一优先级的交互性进
程过多时, 排在队列后面的交互性进程需要等到前面所有进程的时间片都运行完才能运行.
第二个问题:
1.系统中一个进程的nice值是0, 一个是1, 那么时间片对应的是100ms和95ms. 这两个进程的运行时间相差不大.
2.系统中一个进程的nice值是18, 一个是19, 那么时间片对应的是10ms和5ms. 这两个进程的运行时间相差了2倍
综上所述, nice值的增加所带来的时间片的变化有时过大.
当然,O(1)调度算法最大的问题还是, 当系统中的交互性程序过多时,运行的不太理想, 比如桌面系统.
本文引述自:
http://edsionte.com/techblog/archives/2838
http://edsionte.com/techblog/archives/2851
http://edsionte.com/techblog/archives/2870