【亚嵌】Linux进程调度算法分析(二)

接上文:【亚嵌】Linux进程调度算法分析(一)

2.1 基于实时进程调度

Linux2.4内核维护双向循环队列runqueue,一旦调度时机触发,内核重新计算当前队列中所有进程运行权值,并从中挑选出权值最高的进程作为当前进程投入运行。其弊端是显而易见的:

1)调度时机触发,重新计算runqueue中每个进程运行权值,复杂度为O(n) 且调度性能与内核负载相关。

2runqueue同时管理着实时进程与非实时进程(普通进程),内核通过进程属性,如实时或非实时、实时进程优先级、用户进程或内核线程相关因素来计算运行权值count,灵活性低,且不便于理解和维护。

Linux2.6早期版本开始,内核进程对实时进程调度重新设计了O1)调度器——SD/RSDLRSDL调度器是在SD调度器基础上的改进。Linux 2.6.26 内核在早期2.6内核基础上简化了RSDL调度器,把就绪进程队列和过期进程队列合并为就绪队列。下面结合内核代码,给与实时进程O1)调度器的实现(限于篇幅,本文给出核心数据结构关键成员的注释)。

1)就绪进程队列struct rq

struct rq {

              /* ...... */

              /* runqueue lock: */

              spinlock_t lock;

/* 就绪队列中进程个数 */

              unsigned long nr_running;

/* ...... */

              /* 普通进程就绪队列 */

       struct cfs_rq cfs;

/* 实时进程就绪队列 */

       struct rt_rq rt;

       /* ...... */

       /* 就绪队列工作时间 */

       u64 clock;

       /* ...... */

       /* used by load_balance */

       struct task_struct *migration_thread;

       struct list_head migration_queue;

       /* ...... */

}

内核为系统中每个CPU维护独立的struct rq数据结构,在SMP环境下,CPU之间互不影响。实时进程调度的核心数据结构是struct rt_rq,定义如下:

2)实时进程就绪队列struct rt_rq

struct rt_rq {

/* 实时进程优先级队列 */

struct rt_prio_array active;

/* 实时进程个数 */

unsigned long rt_nr_running;

/* ... */

/* 实时进程队列工作时间 */

u64 rt_time;

/* ... */

};

rt_rq中关键的数据结构在于prio_array_active,定义如下:

3)优先级队列struct rt_prio_array

struct rt_prio_array {

/* 优先级位图 */

       DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1);

       /* 优先级队列 */

       struct list_head queue[MAX_RT_PRIO];

};

4)进程运行信息结构sched_info

struct sched_info {

       /* cumulative counters */

       unsigned long pcount;          /* # of times run on this cpu */

       unsigned long long cpu_time,  /* time spent on the cpu */

       run_delay; /* time spent waiting on a runqueue */

 

       /* timestamps */

       unsigned long long last_arrival,/* when we last ran on a cpu */

   last_queued;                          /* when we were last queued to run */

};

       sched_info维护进程运行时的实时信息,代码作者的注释已比较详细,该结构数据在schedule进程切换发生时被更新。

       struct rt_prio_array成员bitmap是进程优先级队列位图,其大小是MAX_RT_PRIO + 1,如果某优先级就绪进程队列不空,那么bitmap相应的位置1,否则为0queue为进程优先级队列数组,每个进程优先级队列用双端循环链表来描述。内核寻找优先级最高的任务需要两个简单的BSFS汇编指令,查询优先级队列位图,然后从优先级队列数组中取出对应的优先级队列的对头所指向的进程,即为下一个投入运行的进程。当进程用完了自己的时间片后,被加入active数组优先级队列的末尾,调度任务从当前实时任务优先级队列中取出队首任务投入运行。实时进程调度核心数据结构之间的关系如图2

2

2.2 基于普通进程调度

       Linux 2.6.23 内核进程调度支持CFS调度器,它从RSDL/SD中吸取了完全公平的思想,不再跟踪进程的睡眠时间,也不再企图区分交互式进程。它将所有的进程(普通进程)都统一对待,这就是公平的含义。CFS调度器使用红黑树管理就绪进程,所有状态为TASK_RUNNING的进程都被插入红黑树。在每个调度点,CFS调度器都会选择红黑树的最左边的叶子节点作为下一个将获得CPU的进程。由于红黑树是平衡树,因此采用CFS调度器调度时间复杂度是O(lgn)。在CFS中,tick中断首先更新调度信息。然后调整当前进程在红黑树中的位置。调整完成后如果发现当前进程不再是最左边的叶子,就标记need_resched标志,中断返回时就会调用scheduler()完成进程切换。否则当前进程继续占用CPU。从这里可以看到CFS调度器带来的两点变换:1)抛弃了传统的时间片概念,进程运行权值的计算分散到tick中断发生时。tick中断只需更新红黑树,以前的所有调度器都在tick中断中递减时间片,当时间片或者配额被用完时才触发优先级调整并重新调度(参见函数update_curr()调用时机)。2CFS为内核抢占调度提供完美支持。

理解CFS的关键就是了解红黑树键值的计算方法。该键值由三个因子计算而得:一是进程已经占用的CPU时间;二是当前进程的nice值;三是当前的cpu负载。CFS调度器维护CPU级变量min_vruntime;同时,每个进程维护进程级变量vruntime。其中,min_vruntime = max (min_vruntime, vruntime),即调度前min_vruntime的数值和备选进程运行时间权值的大者。进程插入红黑树的键值为vruntime - min_vruntime。它们的差值代表了一个进程的公平程度。该值越大,代表当前进程相对于其它进程越不公平。因此该值越大,键值越大,从而使得当前进程向红黑树的右侧移动,越晚被选中。

       以上,介绍了Linux 2.6.26 内核两种主流调度器,2.6.26内核还为idle进程提供了专门的调度器(idle_sched_class)。需要指出的是,进程调度首先选择实时进程调度器,即进程总是以保证实时进程最高运行权限,如果系统中没有实时进程,那么才会选择CFS调度器进行进程调度。

       Additions notes

1 2.6.26 内核对实时进程队列运行时间进行了限制,如果某一实时进程队列运行时间超过最大限制,内核将限制此实时进程队列继续占用CPU(详见update_curr_rt函数)。

2 2.6.26 内核进程运行时间权值的分散计算点:

a.定时器中断调用调度器计算进程调度权值

b.schedule中进程调度点

       2.6内核对SMP支持的研究有待研究!

 

3. Linux进程调度实现代码分析

Linux 2.6.26 进程调度部分核心数据结构已在上文介绍,?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值