问题:
任务调度在何时发生
任务调度的基本策略是什么
任务切换时怎么做到的
1. 隐含的睡眠队列
建立睡眠等待队列的原因,是因为有先后顺序等待某项资源,然后要按顺序唤醒进程,就要依照这里隐含的队列顺序进行
sched.c第171行
static inline void __sleep_on(struct task_struct **p, int state)
{
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = state;
repeat: schedule();
//这里的*p的内容,有可能不是上面的*p的内容即不是当前任务,也有可能是
//不是当前任务是因为在其它进程执行本函数时__sleep_on设置的
if (*p && *p != current) {
(**p).state = 0;
current->state = TASK_UNINTERRUPTIBLE;
goto repeat;
}
if (!*p)
printk("Warning: *P = NULL\n\r");
if (*p = tmp)
tmp->state=0;
}
以下这张图展示了这个队列的大概样子,上面方块代表__sleep_on函数块,需要了解这个机制,是因为很多其它的子系统也用到类似的方法,这也是了解睡眠机制的关键
2. 任务调度在何时发生
任务状态发生改变的时候都会要重新调度,比如睡眠了一个进程(任务),自然就要重新选一个进程继续运行,在者要是没有任务发生改变时,时间中断回调函数,会在每10ms到来时运行一次
sched.c第324行
void do_timer(long cpl)
{
...
//current->counter > 0意思是当前任务还有分配给他的时间片
if ((--current->counter)>0) return;
current->counter=0;
if (!cpl) return;
//重点:重新调度
schedule();
}
3. 任务调度的基本策略
sched.c第120行
void schedule(void)
{
...
/* this is the scheduler proper: */
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
//挑选一个就绪态运行的进程且时间片最大的那个进程
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c) c = (*p)->counter, next = i;
}
//没有找到怎么办?你猜猜
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
}
//切换进程
switch_to(next);
}
备注:
选取超时时间最少也就是分配的时间片最多的那个进程进行切换
调度性能与进程的数目成线性关系,进程越多性能越差
4. 切换任务的代码分析
先来看看TSS段描述符的格式:
然后再来看看_TSS宏,它是寻找GDT表中本进程的tss描述符的选择符号值,每个任务包含一个ldt选择符和tss选择符
//每个任务有一个8字节的tss选择符和8字节的ldt选择子一共16字节
//任务为n偏移2^4字节
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
切换代码分析
#define switch_to(n) {\
struct {long a,b;} __tmp; \
/*是不是当前任务,是就不用切换直接跳到下面标号为的地方*/
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
/* %dx装载下面的_TSS(n),也就是tss段描述符的值 放到%1,%1代表__tmp.b处 */
"movw %%dx,%1\n\t" \
/*%ecx为保存为切换出来的任务*/
"xchgl %%ecx,_current\n\t" \
/*长跳到__tmp.a处的任务,也就是上面tss保存到__tmp.b的进程*/
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
结束语:
0.12版本的进程调度策略比较简单,但给了我们理解进程调度的核心意思,最新的linux代码仍然使用switch_to宏,只是已经做了非常的优化工作