(萊昂氏unix原始碼分析導讀-21)時鐘中斷處理
時鐘中斷是系統中最重要的中斷,每個時鐘滴答都會產生時鐘中斷,它的中斷向量為(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的部分留在後面討論。