时钟中断是系统中最重要的中断,每个时钟滴答都会产生时钟中断,它的中断矢量为(0100)或(0103)。
0533: . = 100^.
0534: kwlp; br6
0535: kwlp; br6
0569: .globl _clock
0570: kwlp: jsr r0,call; _clock
显然,时钟中断会通过call例程调用_clock函数,对clock函数,11.1小节有详细的介绍。
我在这里只谈几个问题:
1.参数
clock(dev, sp, r1, nps, r0, pc, ps)有7个参数,想要逐一了解他们,请回头看看上面的栈图;
2.时钟中断是典型的“多用途”中断,它需要进行多个层次的处理。实际上,clock函数对时间进行
3个维度的处理:
(1)时钟滴答
修改p_cpu值,对于大多数进程来说,p_cpu就是占用的cpu滴答数,但如果进程执行时间较长的话,
p_cpu的值会有一个回归——其作用是防止进程的优先级变的太低而导致饿死。
检查定时器,看是否需要执行操作。
(2)秒
修改u.stime、u.utime、time[ ]等以秒为单位的计时器。
甚至还有以4s为单位的系统进程唤醒。
(3)时间片
此版本的unix正好使用1秒作为一个时间片,故与秒的处理发生了重叠。
3.Clock函数的各项工作的“Priority”
显然,在clock函数的多项工作中,优先级是不同的,而且各项工作对于时延的容忍程度是不同的。
对秒级、时间片级的工作——即使是推迟几个时钟滴答再处理对整体的影响也不大。
于是,clock函数内对低优先级、高耗时的工作进行了控制——会进行这样的检查:
if((ps&0340) != 0) return; /by pass following codes
即只有在“前ps”的cpu优先级为0时,才会从事下面那些工作。而一般情况下,
(1) user态cpu优先级为0;
(2) kernel态时,程序调用spl0将自己设置为优先级0。
受到控制的工作有:
(1) 时间片——重设进程优先级的工作;
(2) 计时器处理
(3) Etc
4.时间片处理
按我所学习的操作系统理论,时间片应该是很重要的进程切换的时机。但很遗憾,clock()函数中仅仅调用setpri(),
重新设置了进程的priority,并没有直接调用swtch()切换进程。
但是,且慢……
(1) 在clock()函数返回到call例程后,会有机会切换进程的,关键在runrun是否设置;
(2) setpri()函数是可以增加runrun计数的——还记得吗,那个诡异的判断?
2165: if(p > curpri)
2166: runrun++;
让我们仔细考察一下clock()和setpri()。
对大多数的进程,clock()会以更高的p_cpu调用setpri()函数,这样会降低进程的优先级,显然,对active
进程也是如此。而这正好会使2165那个判断成真——即增加runrun计数,也就引起了后面的进程切换。
【注】:对于2165谜题,希望读者能给出更好的答案。
5.time数组
由time[0]和time[1]组成,记录自1970年以来所经过的秒数。它使用2个16bit整数模拟1个32位整数,
time[0]为高16位bit,time[1]为低16位bit。因此才有以下的算法:
3801: if(++time[1] == 0)
3802: ++time[0];
6.定时器
callout数组记录了要执行的定时任务。
任务按照执行先后顺序排列,其c_time意义如下:
(1) callout[0].c_time ——“绝对时钟滴答数”,再过这么多滴答,即执行函数;
(2) callout[n] .c_time (n>0)——相对于callout[n-1]的相对时钟滴答数
7.tout的叫醒服务
显然,tout[ ]内记录的是下一个要被wakeup的时间,当与当前时间(time[])相同时,
会调用wakeup(tout)唤醒进程——显然,睡眠的进程使用tout[ ]数组首地址作为睡眠id。
莱昂针对tout的实现提出了自己的看法,但这部分涉及到system call,我们会在下面的章节进行讨论。
注意,我们将swap的部分留在后面讨论。
博客地址:http://blog.csdn.net/cszhao1980
博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html