《linux内核完全剖析》笔记04-任务调度

来源:互联网 发布:光翼网络班多少钱 编辑:程序博客网 时间:2024/06/05 14:46

问题:

  • 任务调度在何时发生
  • 任务调度的基本策略是什么
  • 任务切换时怎么做到的

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宏,只是已经做了非常的优化工作

0 0
原创粉丝点击