Linux Kernel 2.6内核进程调度复杂度为O(1)的代码分析

主要看了linux2.6.x内核进程调度那一块,和大家share一下。
        每个cpu都有一个单独的runqueue,每个runqueue中有prio_array_t *active, *expired,他们是特定时间可以互换的两个指针。前者是有timeslice剩余的array,后者是耗尽timeslice的array。prio_array的结构定义如下:
                       struct prio_array 
                       {
                                   unsigned int nr_active;
                                   unsigned long bitmap[BITMAP_SIZE];
                                   struct list_head queue[MAX_PRIO];
                       };


          MAX_PRIO指的是优先级的数量,定义为139。queue是指定优先级进程list的指针,如queue[i]就是priority为 i 的进程的指针。bitmap是一张优先级的位图,或者可以说的位数组,每一位代表了一个优先级。BITMAP_SIZE宏定义如下所示:


          #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
    好了,先写到这里吧!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值