在一个操作系统中,最重要的部分就是任务调度,linux的0.11版本的任务调度函数比较简单,所以学习任务调度的原理非常适用。
下面我们贴出来schedule函数的代码。
1
2 /*
3 * 'schedule()' is the scheduler function. This is GOOD CODE! There
4 * probably won't be any reason to change this, as it should work well
5 * in all circumstances (ie gives IO-bound processes good response etc).
6 * The one thing you might take a look at is the signal-handler code here.
7 *
8 * NOTE!! Task 0 is the 'idle' task, which gets called when no other
9 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
10 * information in task[0] is never used.
11 */
12 void schedule(void)
13 {
14 int i,next,c;
15 struct task_struct ** p;
16
17 /* check alarm, wake up any interruptible tasks that have got a signal */
18
19 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
20 if (*p) {
21 if ((*p)->alarm && (*p)->alarm < jiffies) {
22 (*p)->signal |= (1<<(SIGALRM-1));
23 (*p)->alarm = 0;
24 }
25 if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
26 (*p)->state==TASK_INTERRUPTIBLE)
27 (*p)->state=TASK_RUNNING;
28 }
29
30 /* this is the scheduler proper: */
31
32 while (1) {
33 c = -1;
34 next = 0;
35 i = NR_TASKS;
36 p = &task[NR_TASKS];
37 while (--i) {
38 if (!*--p)
39 continue;
40 if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
41 c = (*p)->counter, next = i;
42 }
43 if (c) break;
44 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
45 if (*p)
46 (*p)->counter = ((*p)->counter >> 1) +
47 (*p)->priority;
48 }
49 switch_to(next);
50 }
从代码注释中,我们可以看到linus本人对这段代码是比较推崇的。这段代码可以运行在任何边界,没有理由修改这段代码。有一点需要注意的是信号处理的代码。任务是一个空闲任务,只有在其他任务没有运行时该空闲任务才会被运行。空闲任务不能 被杀死,也不能睡眠。它的tcb即task[0].state,任务状态信息从来不用的。
接下来我们来分析代码。
14,15行定义了一些变量,要说明的是p 这是一个双重指针,双重针指向的类型是任务数据块.数据块里面存放着一个任务的所有信息。
19-28行的代码作用是进行的报警定时值,唤醒任何已经得到信号的可中断任务。
19行开始for循环从最后一个任务开始向前开始遍历所有的任务。
20行判断该任务是否存在,不存在则跳过。
21行判断如果设置过该任务的定时值,且该定时值已经过期则,在信号位图中置位SIGALRM,并清alarm.
要说明的一点是jiffies变量是系统开机开始算起的计时数 10ms/滴答
25.26行如果信号位图中除了被阻塞的信号外还有其它信号,并且任务处于可中断状态,则任务置位就绪态。 ~(_BLOCKABLE & (*p)->blocked)为忽略阻塞信号。
32到48行是调度程序中主要的部分。
先对几个变量进行说明。
(*p)->counter 表示 任务状态的剩余滴答计数。
i表示任务标号,p表示任务数组的地址。
c和next用来保存(*p)->counter和i
这里需要对i和p进行说明。初始
i = NR_TASKS;
p =&task[NR_TASKS]
这两个都是无效值,因为NR_TASKS,用宏定义为64,也就是最多运行64个任务。但因为有任务0,所以任务数只能从0-63.
所以在37,38行判断前对i和p都进行了前减减操作。
39行也是为了判断任务存在,不存在的任务跳过。
40和41行是为了找到当前所有任务中即处于就绪态,且剩余的滴答计时最多的任务。
43行如果找到当前所有任务中即处于就绪态,且剩余的滴答计时最多的任务。跳出循环到49行进行任务切换。
如果当前就绪的所有任务的剩余的滴答数都为0,则重新为所有任务都分配滴答数。
45行为跳过不存在的任务。
46.47行为任务重新分配滴答数。
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
算法为counter = counter/2 + priority
44到48行是为所有的任务都重新分配滴答数。因为之前40行判断的时候可能处于非就绪状态的任务滴答数可能不为0,所以本次分配要对那些在40行判断非就绪状态的任务的滴答数加上之前剩余的一半 再加上自己的优先级。
此时系统中就绪态的任务的滴答数已经重新分配,所以循环回32行,可以重新找到就绪态的最大滴答数的任务。
然后在43行通过break退出循环,执行任务切换。