笔记重点: 进程调度一直是linux系统开发中相当重要的部分,但在最初版本中的调度如此简单,以至于你并不相信,但是在不断的发展过程中,首先在2.6内核版本中发展了抢占式的调度算法,在经历几次重大版本的迭代后在开发出CFS(完全公平的调度算法),不过调度算法的重点是,如何选择需要运行的进程以及保证性能。进程的运行很多时候会被外部的资源分配不及时而阻塞,这个时候进程睡眠等待,以及这个时候的合理调度,对于CPU资源的充分利用,显得尤为重要,那调度就从进程的睡眠机制开始了解。 问题:
任务调度在何时发生
任务调度的基本策略是什么
任务切换时怎么做到的
1. 隐含的睡眠队列 建立睡眠等待队列的原因,是因为有先后顺序等待某项资源,然后要按顺序唤醒进程,就要依照这里隐含的队列顺序进行
sched.c第171行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 (); 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行
1 2 3 4 5 6 7 8 9 10 void do_timer (long cpl) { ... if ((--current->counter)>0 ) return ; current->counter=0 ; if (!cpl) return ; schedule (); }
3. 任务调度的基本策略 sched.c第120行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void schedule (void ) { ... 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选择符
1 2 3 #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
切换代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define switch_to(n) {\ struct {long a,b;} __tmp; \ __asm__("cmpl %%ecx,_current\n\t" \ "je 1f\n\t" \ "movw %%dx,%1\n\t" \ "xchgl %%ecx,_current\n\t" \ "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宏,只是已经做了非常的优化工作