http://damocles.ycool.com/post.2689967.htmlLinux Kernel 2.6内核进程调度复杂度为O(1)的代码分析Damocles 发表于 2007-08-09 21:07:06 主要看了linux2.6.x内核进程调度那一块,和大家share
一下。 #define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long)) 为什么要+1+7而 不是直接+8,上次和TA讨论了一下,豁然开朗。+1是为了把值加到140,+7是为了向上取整。同理对于sizeof(long)- 1,他的结果是5,也就是说 bitmap是32*5=160bit 的位数组。这也构成了我们要用O(1)查找一个优先级最高进程的基础。这也是2.6内核相对于2.4内核的巨大进步之 处。在bitmap中active的位是置1的,expired的位置0。我们使用 sched_find_first_bit()来找到第一个非0位,用 find_next_bit()来找下一个非0位。 sched_find_first_bit()对每种cpu都有其特殊的执行方式,我们以i386为例,其函数如下所示: static inline int sched_find_first_bit(const unsigned long *b)
{
if (unlikely(b[0]))
return __ffs(b[0]);
if (unlikely(b[1]))
return __ffs(b[1]) + 32;
if (unlikely(b[2]))
return __ffs(b[2]) + 64;
if (b[3])
return __ffs(b[3]) + 96;
return __ffs(b[4]) + 128;
}
参数b就是我们传入的bitmap,b[i]可以指的第 i 个long型非负整数,在这里我们视其为32*i~32*(i+1)的位。b[i]若为0,则说明这32位中无1,我们直接可以跳到b[i+1],若b [i]不为0,则说明其中有
置位。我们调用__ffs()来找到这一位的相对位置,再加上基地址就能得出第一个置位的位置所在。我们再来看一下__ffs
()函数,我们就可以知道为什么是O(1)了: static inline unsigned long __ffs(unsigned long word)
{
__asm__("bsfl %1,%0"
:"=r" (word)
:"rm" (word));
return word;
}这是一段AT&T格式的嵌入式汇编代码,意思很简单,就是执行bsfl指令。 bsfl汇编指令: intel汇编指令:bsf oprd1,oprd2; 顺向位扫描(bit scan forward) 从右向左(从位0-->位15或位31)扫描字或双字操作数 oprd2中第一个含"1"的位,并把扫描到的第一个含'1'的位的位号送操作数oprd1。AT&T格式汇编指令bsfl类似bsf,只是源操作数 和目的操作数顺序相反。正是使用了这条指令,linux把时间降到了O(1)。讲清楚了这一 点,我们再回过头看,调用sched_find_first_bit()后,我们得到了优先级最高(priority最低)的active进程array 的偏移idx。 我们利用这个idx,再queue中找到queue(idx),执行之,执行过程中根据timeslice更改dynamic priority。2.6.x中又一个特殊的设计出现了,当所有进程timeslice皆耗 尽时,bitmap全部被置0。此时active中没有进程,全部进程都在expired 中,此时我们只要交换两个的指针就可以进行新一轮的recalculation,我们来看代码: array = rq->active;
if (unlikely(!array->nr_active)) {
/*Switch the active and expired arrays.*/
schedstat_inc(rq, sched_switch);
rq->active = rq->expired;
rq->expired = array;
array = rq->active;
rq->expired_timestamp = 0;
rq->best_expired_prio = MAX_PRIO;
}
指针交换的代价是很小的:p
好了,先写到这里吧!
|
