一,前言
最近想看看进程的调度,觉得2.6的调度比较复杂,很难下手,于是从2.4开始学习。
觉得学习“历史”是为了把握好“现在”和“未来”吧,2.6的调度器的设计都是针对2.4调度器
的不足而提出来的。
-------------------------------------------------------------------------------------
二,task_struct结构中关于调度字段的简介。(2.4内核中的)
1,policy:调度策略。
调度策略在2.4内核中有三种选择,SCHED_OTHER,该策略用来常规的分时调度,
针对的是普通进程,SCHED_FIFO,该策略用来先进先出申请运行,除非有更高优先级的
进程申请运行,不然它会运行至结束才让出CPU,针对的是实时进程。SCHED_RR,时间片
轮转调度,该进程调度下来后将被置于就绪队列的末尾,以保证其他实时进程有机会运行。
该策略也是针对实时进程。
2,counter
记录进程的时间片内还允许运行的时间。主要用老作为进程调度的依据。
3,nice:优先级。
该值决定了进程的优先级,它决定了counter的初值。
-------------------------------------------------------------------------------------
三,调度器是如何选择下一个要运行的进程的。
-
585 still_running_back:
-
586 list_for_each(tmp, &runqueue_head) {
-
587 p = list_entry(tmp, struct task_struct, run_list);
-
588 if (can_schedule(p, this_cpu)) {
-
589 int weight = goodness(p, this_cpu, prev->active_mm);
-
590 if (weight > c)
-
591 c = weight, next = p;
-
592 }
-
593 }
2.4内核中就使用了一个单就绪队列,也就是一个双向循环链表来组织的。示意图如下:
调度器先遍历该队列,然后计算其每个进程的“权值”,每个进程的“权值”就是代码中的
weight,该值是由进程的调度策略和进程的优先级计算出来的。计算过程也比较简单,可以
分析函数goodness,就觉得计算很简单。
对于实时进程(调度策略为SCHED_FIFO或SCHED_RR):
weight = 1000 + p->rt_prioroty
对于普通进程(调度策略未SCHED_OTHER)。
weight = p->counter.
-----------------------------------------------------------------------------------
四,调度器选出下一个要执行的进程后的的切换工作。
1,进程地址空间的切换。我们都知道,每个进程都有3GB的用户空间,进程切换,
进程的地址空间也会随之切换。页表也会随之切换。假如现在要调度的是一个内核线程
该怎么办呢?内核线程是内核态的,他没有用户空间。2.4的处理是“借用”前一个进程
的地址空间。
-
switch_mm(oldmm,mm,next,this_cpu);
内核中使用该函数实现了进程地址空间的切换。
2,进程上下文切换。
该切换主要是切换进程的堆栈,使用的是几句很经典的汇编实现的。
实现的函数:
-
switch_to(prev,next,prev);
-------------------------------------------------------------------------------------
五,2.4内核调度器的缺点。
1,单就绪队列问题。调度算法与系统进程的数量呈相关,队列越长,选择下一个要执行的
进程的时间就越长,因为要遍历这个就绪队列,还有一些计算。
2,多处理器问题。多个处理器使用一个就绪队列,将就绪队列为临界资源,各个处理器要
等待才能进入就绪队列,使得多处理器的效率很低。
3,内核态的不可抢占问题。只要一个进程进入内核,即使一个很紧迫的任务到来,也只能
等待,只有那个进程返回用户态,紧迫的任务才能得到相应。
-------------------------------------------------------------------------------------
参考:
阅读(1569) | 评论(0) | 转发(0) |