日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux内核的时钟中断

發(fā)布時(shí)間:2023/12/10 linux 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内核的时钟中断 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
前言?
時(shí)間在一個(gè)操作系統(tǒng)內(nèi)核中占據(jù)著重要的地位,它是驅(qū)動(dòng)一個(gè)OS內(nèi)核運(yùn)行的“起博器”。一般說(shuō)來(lái),內(nèi)核主要需要兩種類型的時(shí)間:?
1.?在內(nèi)核運(yùn)行期間持續(xù)記錄當(dāng)前的時(shí)間與日期,以便內(nèi)核對(duì)某些對(duì)象和事件作時(shí)間標(biāo)記(timestamp,也稱為“時(shí)間戳”),或供用戶通過(guò)時(shí)間syscall進(jìn)行檢索。?
2.?維持一個(gè)固定周期的定時(shí)器,以提醒內(nèi)核或用戶一段時(shí)間已經(jīng)過(guò)去了。?
PC機(jī)中的時(shí)間是有三種時(shí)鐘硬件提供的,而這些時(shí)鐘硬件又都基于固定頻率的晶體振蕩器來(lái)提供時(shí)鐘方波信號(hào)輸入。這三種時(shí)鐘硬件是:(1)實(shí)時(shí)時(shí)鐘(Real?Time?Clock,RTC);(2)可編程間隔定時(shí)器(Programmable?Interval?Timer,PIT);(3)時(shí)間戳計(jì)數(shù)器(Time?Stamp?Counter,TSC)。?

7.1?時(shí)鐘硬件?
7.1.1?實(shí)時(shí)時(shí)鐘RTC?
自從IBM?PC?AT起,所有的PC機(jī)就都包含了一個(gè)叫做實(shí)時(shí)時(shí)鐘(RTC)的時(shí)鐘芯片,以便在PC機(jī)斷電后仍然能夠繼續(xù)保持時(shí)間。顯然,RTC是通過(guò)主板上的電池來(lái)供電的,而不是通過(guò)PC機(jī)電源來(lái)供電的,因此當(dāng)PC機(jī)關(guān)掉電源后,RTC仍然會(huì)繼續(xù)工作。通常,CMOS?RAM和RTC被集成到一塊芯片上,因此RTC也稱作“CMOS?Timer”。最常見(jiàn)的RTC芯片是MC146818(Motorola)和DS12887(maxim),DS12887完全兼容于MC146818,并有一定的擴(kuò)展。本節(jié)內(nèi)容主要基于MC146818這一標(biāo)準(zhǔn)的RTC芯片。具體內(nèi)容可以參考MC146818的Datasheet。?

7.1.1.1?RTC寄存器?
MC146818?RTC芯片一共有64個(gè)寄存器。它們的芯片內(nèi)部地址編號(hào)為0x00~0x3F(不是I/O端口地址),這些寄存器一共可以分為三組:?
(1)時(shí)鐘與日歷寄存器組:共有10個(gè)(0x00~0x09),表示時(shí)間、日歷的具體信息。在PC機(jī)中,這些寄存器中的值都是以BCD格式來(lái)存儲(chǔ)的(比如23dec=0x23BCD)。?
(2)狀態(tài)和控制寄存器組:共有4個(gè)(0x0A~0x0D),控制RTC芯片的工作方式,并表示當(dāng)前的狀態(tài)。?
(3)CMOS配置數(shù)據(jù):通用的CMOS?RAM,它們與時(shí)間無(wú)關(guān),因此我們不關(guān)心它。?
時(shí)鐘與日歷寄存器組的詳細(xì)解釋如下:?
Address?Function?
00?Current?second?for?RTC?
01?Alarm?second?
02?Current?minute?
03?Alarm?minute?
04?Current?hour?
05?Alarm?hour?
06?Current?day?of?week(01=Sunday)?
07?Current?date?of?month?
08?Current?month?
09?Current?year(final?two?digits,eg:93)?

狀態(tài)寄存器A(地址0x0A)的格式如下:?
其中:?
(1)bit[7]——UIP標(biāo)志(Update?in?Progress),為1表示RTC正在更新日歷寄存器組中的值,此時(shí)日歷寄存器組是不可訪問(wèn)的(此時(shí)訪問(wèn)它們將得到一個(gè)無(wú)意義的漸變值)。?
(2)bit[6:4]——這三位是“除法器控制位”(divider-control?bits),用來(lái)定義RTC的操作頻率。各種可能的值如下:?
Divider?bits?Time-base?frequency?Divider?Reset?Operation?Mode?
DV2?DV1?DV0?
0?0?0?4.194304?MHZ?NO?YES?
0?0?1?1.048576?MHZ?NO?YES?
0?1?0?32.769?KHZ?NO?YES?
1?1?0/1?任何?YES?NO?
PC機(jī)通常將Divider?bits設(shè)置成“010”。?
(3)bit[3:0]——速率選擇位(Rate?Selection?bits),用于周期性或方波信號(hào)輸出。?
RS?bits?4.194304或1.048578?MHZ?32.768?KHZ?
RS3?RS2?RS1?RS0?周期性中斷?方波?周期性中斷?方波?
0?0?0?0?None?None?None?None?
0?0?0?1?30.517μs?32.768?KHZ?3.90625ms?256?HZ?
0?0?1?0?61.035μs?16.384?KHZ?
0?0?1?1?122.070μs?8.192KHZ?
0?1?0?0?244.141μs?4.096KHZ?
0?1?0?1?488.281μs?2.048KHZ?
0?1?1?0?976.562μs?1.024KHZ?
0?1?1?1?1.953125ms?512HZ?
1?0?0?0?3.90625ms?256HZ?
1?0?0?1?7.8125ms?128HZ?
1?0?1?0?15.625ms?64HZ?
1?0?1?1?31.25ms?32HZ?
1?1?0?0?62.5ms?16HZ?
1?1?0?1?125ms?8HZ?
1?1?1?0?250ms?4HZ?
1?1?1?1?500ms?2HZ?
PC機(jī)BIOS對(duì)其默認(rèn)的設(shè)置值是“0110”。?

狀態(tài)寄存器B的格式如下所示:?
各位的含義如下:?
(1)bit[7]——SET標(biāo)志。為1表示RTC的所有更新過(guò)程都將終止,用戶程序隨后馬上對(duì)日歷寄存器組中的值進(jìn)行初始化設(shè)置。為0表示將允許更新過(guò)程繼續(xù)。?
(2)bit[6]——PIE標(biāo)志,周期性中斷使能標(biāo)志。?
(3)bit[5]——AIE標(biāo)志,告警中斷使能標(biāo)志。?
(4)bit[4]——UIE標(biāo)志,更新結(jié)束中斷使能標(biāo)志。?
(5)bit[3]——SQWE標(biāo)志,方波信號(hào)使能標(biāo)志。?
(6)bit[2]——DM標(biāo)志,用來(lái)控制日歷寄存器組的數(shù)據(jù)模式,0=BCD,1=BINARY。BIOS總是將它設(shè)置為0。?
(7)bit[1]——24/12標(biāo)志,用來(lái)控制hour寄存器,0表示12小時(shí)制,1表示24小時(shí)制。PC機(jī)BIOS總是將它設(shè)置為1。?
(8)bit[0]——DSE標(biāo)志。BIOS總是將它設(shè)置為0。?

狀態(tài)寄存器C的格式如下:?
(1)bit[7]——IRQF標(biāo)志,中斷請(qǐng)求標(biāo)志,當(dāng)該位為1時(shí),說(shuō)明寄存器B中斷請(qǐng)求發(fā)生。?
(2)bit[6]——PF標(biāo)志,周期性中斷標(biāo)志,為1表示發(fā)生周期性中斷請(qǐng)求。?
(3)bit[5]——AF標(biāo)志,告警中斷標(biāo)志,為1表示發(fā)生告警中斷請(qǐng)求。?
(4)bit[4]——UF標(biāo)志,更新結(jié)束中斷標(biāo)志,為1表示發(fā)生更新結(jié)束中斷請(qǐng)求。?

狀態(tài)寄存器D的格式如下:?
(1)bit[7]——VRT標(biāo)志(Valid?RAM?and?Time),為1表示OK,為0表示RTC已經(jīng)掉電。?
(2)bit[6:0]——總是為0,未定義。?

7.1.1.2?通過(guò)I/O端口訪問(wèn)RTC?
在PC機(jī)中可以通過(guò)I/O端口0x70和0x71來(lái)讀寫(xiě)RTC芯片中的寄存器。其中,端口0x70是RTC的寄存器地址索引端口,0x71是數(shù)據(jù)端口。?
讀RTC芯片寄存器的步驟是:?
mov?al,?addr?
out?70h,?al?;?Select?reg_addr?in?RTC?chip?
jmp?$+2?;?a?slight?delay?to?settle?thing?
in?al,?71h?;?
寫(xiě)RTC寄存器的步驟如下:?
mov?al,?addr?
out?70h,?al?;?Select?reg_addr?in?RTC?chip?
jmp?$+2?;?a?slight?delay?to?settle?thing?
mov?al,?value?
out?71h,?al?

7.1.2?可編程間隔定時(shí)器PIT?
每個(gè)PC機(jī)中都有一個(gè)PIT,以通過(guò)IRQ0產(chǎn)生周期性的時(shí)鐘中斷信號(hào)。當(dāng)前使用最普遍的是Intel?8254?PIT芯片,它的I/O端口地址是0x40~0x43。?
Intel?8254?PIT有3個(gè)計(jì)時(shí)通道,每個(gè)通道都有其不同的用途:?
(1)?通道0用來(lái)負(fù)責(zé)更新系統(tǒng)時(shí)鐘。每當(dāng)一個(gè)時(shí)鐘滴答過(guò)去時(shí),它就會(huì)通過(guò)IRQ0向系統(tǒng)產(chǎn)生一次時(shí)鐘中斷。?
(2)?通道1通常用于控制DMAC對(duì)RAM的刷新。?
(3)?通道2被連接到PC機(jī)的揚(yáng)聲器,以產(chǎn)生方波信號(hào)。?
每個(gè)通道都有一個(gè)向下減小的計(jì)數(shù)器,8254?PIT的輸入時(shí)鐘信號(hào)的頻率是1193181HZ,也即一秒鐘輸入1193181個(gè)clock-cycle。每輸入一個(gè)clock-cycle其時(shí)間通道的計(jì)數(shù)器就向下減1,一直減到0值。因此對(duì)于通道0而言,當(dāng)他的計(jì)數(shù)器減到0時(shí),PIT就向系統(tǒng)產(chǎn)生一次時(shí)鐘中斷,表示一個(gè)時(shí)鐘滴答已經(jīng)過(guò)去了。當(dāng)各通道的計(jì)數(shù)器減到0時(shí),我們就說(shuō)該通道處于“Terminal?count”狀態(tài)。?
通道計(jì)數(shù)器的最大值是10000h,所對(duì)應(yīng)的時(shí)鐘中斷頻率是1193181/(65536)=18.2HZ,也就是說(shuō),此時(shí)一秒鐘之內(nèi)將產(chǎn)生18.2次時(shí)鐘中斷。?

7.1.2.1?PIT的I/O端口?
在i386平臺(tái)上,8254芯片的各寄存器的I/O端口地址如下:?
Port?Description?
40h?Channel?0?counter(read/write)?
41h?Channel?1?counter(read/write)?
42h?Channel?2?counter(read/write)?
43h?PIT?control?word(write?only)?
其中,由于通道0、1、2的計(jì)數(shù)器是一個(gè)16位寄存器,而相應(yīng)的端口卻都是8位的,因此讀寫(xiě)通道計(jì)數(shù)器必須進(jìn)行進(jìn)行兩次I/O端口讀寫(xiě)操作,分別對(duì)應(yīng)于計(jì)數(shù)器的高字節(jié)和低字節(jié),至于是先讀寫(xiě)高字節(jié)再讀寫(xiě)低字節(jié),還是先讀寫(xiě)低字節(jié)再讀寫(xiě)高字節(jié),則由PIT的控制寄存器來(lái)決定。8254?PIT的控制寄存器的格式如下:?
(1)bit[7:6]——Select?Counter,選擇對(duì)那個(gè)計(jì)數(shù)器進(jìn)行操作。“00”表示選擇Counter?0,“01”表示選擇Counter?1,“10”表示選擇Counter?2,“11”表示Read-Back?Command(僅對(duì)于8254,對(duì)于8253無(wú)效)。?
(2)bit[5:4]——Read/Write/Latch格式位。“00”表示鎖存(Latch)當(dāng)前計(jì)數(shù)器的值;“01”只讀寫(xiě)計(jì)數(shù)器的高字節(jié)(MSB);“10”只讀寫(xiě)計(jì)數(shù)器的低字節(jié)(LSB);“11”表示先讀寫(xiě)計(jì)數(shù)器的LSB,再讀寫(xiě)MSB。?
(3)bit[3:1]——Mode?bits,控制各通道的工作模式。“000”對(duì)應(yīng)Mode?0;“001”對(duì)應(yīng)Mode?1;“010”對(duì)應(yīng)Mode?2;“011”對(duì)應(yīng)Mode?3;“100”對(duì)應(yīng)Mode?4;“101”對(duì)應(yīng)Mode?5。?
(4)bit[0]——控制計(jì)數(shù)器的存儲(chǔ)模式。0表示以二進(jìn)制格式存儲(chǔ),1表示計(jì)數(shù)器中的值以BCD格式存儲(chǔ)。?

7.1.2.2?PIT通道的工作模式?
PIT各通道可以工作在下列6種模式下:?
1.?Mode?0:當(dāng)通道處于“Terminal?count”狀態(tài)時(shí)產(chǎn)生中斷信號(hào)。?
2.?Mode?1:Hardware?retriggerable?one-shot。?
3.?Mode?2:Rate?Generator。這種模式典型地被用來(lái)產(chǎn)生實(shí)時(shí)時(shí)鐘中斷。此時(shí)通道的信號(hào)輸出管腳OUT初始時(shí)被設(shè)置為高電平,并以此持續(xù)到計(jì)數(shù)器的值減到1。然后在接下來(lái)的這個(gè)clock-cycle期間,OUT管腳將變?yōu)榈碗娖?#xff0c;直到計(jì)數(shù)器的值減到0。當(dāng)計(jì)數(shù)器的值被自動(dòng)地重新加載后,OUT管腳又變成高電平,然后重復(fù)上述過(guò)程。通道0通常工作在這個(gè)模式下。?
4.?Mode?3:方波信號(hào)發(fā)生器。?
5.?Mode?4:Software?triggered?strobe。?
6.?Mode?5:Hardware?triggered?strobe。?

7.1.2.3?鎖存計(jì)數(shù)器(Latch?Counter)?
當(dāng)控制寄存器中的bit[5:4]設(shè)置成0時(shí),將把當(dāng)前通道的計(jì)數(shù)器值鎖存。此時(shí)通過(guò)I/O端口可以讀到一個(gè)穩(wěn)定的計(jì)數(shù)器值,因?yàn)橛?jì)數(shù)器表面上已經(jīng)停止向下計(jì)數(shù)(PIT芯片內(nèi)部并沒(méi)有停止向下計(jì)數(shù))。NOTE!一旦發(fā)出了鎖存命令,就要馬上讀計(jì)數(shù)器的值。?

7.1.3?時(shí)間戳記數(shù)器TSC?
從Pentium開(kāi)始,所有的Intel?80x86?CPU就都又包含一個(gè)64位的時(shí)間戳記數(shù)器(TSC)的寄存器。該寄存器實(shí)際上是一個(gè)不斷增加的計(jì)數(shù)器,它在CPU的每個(gè)時(shí)鐘信號(hào)到來(lái)時(shí)加1(也即每一個(gè)clock-cycle輸入CPU時(shí),該計(jì)數(shù)器的值就加1)。?
匯編指令rdtsc可以用于讀取TSC的值。利用CPU的TSC,操作系統(tǒng)通常可以得到更為精準(zhǔn)的時(shí)間度量。假如clock-cycle的頻率是400MHZ,那么TSC就將每2.5納秒增加一次。


?dreamice 回復(fù)于:2008-11-06 17:56:02

.2?Linux內(nèi)核對(duì)RTC的編程?
MC146818?RTC芯片(或其他兼容芯片,如DS12887)可以在IRQ8上產(chǎn)生周期性的中斷,中斷的頻率在2HZ~8192HZ之間。與MC146818?RTC對(duì)應(yīng)的設(shè)備驅(qū)動(dòng)程序?qū)崿F(xiàn)在include/linux/rtc.h和drivers/char/rtc.c文件中,對(duì)應(yīng)的設(shè)備文件是/dev/rtc(major=10,minor=135,只讀字符設(shè)備)。因此用戶進(jìn)程可以通過(guò)對(duì)她進(jìn)行編程以使得當(dāng)RTC到達(dá)某個(gè)特定的時(shí)間值時(shí)激活I(lǐng)RQ8線,從而將RTC當(dāng)作一個(gè)鬧鐘來(lái)用。?
而Linux內(nèi)核對(duì)RTC的唯一用途就是把RTC用作“離線”或“后臺(tái)”的時(shí)間與日期維護(hù)器。當(dāng)Linux內(nèi)核啟動(dòng)時(shí),它從RTC中讀取時(shí)間與日期的基準(zhǔn)值。然后再運(yùn)行期間內(nèi)核就完全拋開(kāi)RTC,從而以軟件的形式維護(hù)系統(tǒng)的當(dāng)前時(shí)間與日期,并在需要時(shí)將時(shí)間回寫(xiě)到RTC芯片中。?
Linux在include/linux/mc146818rtc.h和include/asm-i386/mc146818rtc.h頭文件中分別定義了mc146818?RTC芯片各寄存器的含義以及RTC芯片在i386平臺(tái)上的I/O端口操作。而通用的RTC接口則聲明在include/linux/rtc.h頭文件中。?

7.2.1?RTC芯片的I/O端口操作?
Linux在include/asm-i386/mc146818rtc.h頭文件中定義了RTC芯片的I/O端口操作。端口0x70被稱為“RTC端口0”,端口0x71被稱為“RTC端口1”,如下所示:?
#ifndef?RTC_PORT?
#define?RTC_PORT(x)?(0x70?+?(x))?
#define?RTC_ALWAYS_BCD?1?/*?RTC?operates?in?binary?mode?*/?
#endif?
顯然,RTC_PORT(0)就是指端口0x70,RTC_PORT(1)就是指I/O端口0x71。?
端口0x70被用作RTC芯片內(nèi)部寄存器的地址索引端口,而端口0x71則被用作RTC芯片內(nèi)部寄存器的數(shù)據(jù)端口。再讀寫(xiě)一個(gè)RTC寄存器之前,必須先把該寄存器在RTC芯片內(nèi)部的地址索引值寫(xiě)到端口0x70中。根據(jù)這一點(diǎn),讀寫(xiě)一個(gè)RTC寄存器的宏定義CMOS_READ()和CMOS_WRITE()如下:?
#define?CMOS_READ(addr)?({?\?
outb_p((addr),RTC_PORT(0));?\?
inb_p(RTC_PORT(1));?\?
})?
#define?CMOS_WRITE(val,?addr)?({?\?
outb_p((addr),RTC_PORT(0));?\?
outb_p((val),RTC_PORT(1));?\?
})?
#define?RTC_IRQ?8?
在上述宏定義中,參數(shù)addr是RTC寄存器在芯片內(nèi)部的地址值,取值范圍是0x00~0x3F,參數(shù)val是待寫(xiě)入寄存器的值。宏RTC_IRQ是指RTC芯片所連接的中斷請(qǐng)求輸入線號(hào),通常是8。?

7.2.2?對(duì)RTC寄存器的定義?
Linux在include/linux/mc146818rtc.h這個(gè)頭文件中定義了RTC各寄存器的含義。?

(1)寄存器內(nèi)部地址索引的定義?
Linux內(nèi)核僅使用RTC芯片的時(shí)間與日期寄存器組和控制寄存器組,地址為0x00~0x09之間的10個(gè)時(shí)間與日期寄存器的定義如下:?
#define?RTC_SECONDS?0?
#define?RTC_SECONDS_ALARM?1?
#define?RTC_MINUTES?2?
#define?RTC_MINUTES_ALARM?3?
#define?RTC_HOURS?4?
#define?RTC_HOURS_ALARM?5?
/*?RTC_*_alarm?is?always?true?if?2?MSBs?are?set?*/?
#?define?RTC_ALARM_DONT_CARE?0xC0?

#define?RTC_DAY_OF_WEEK?6?
#define?RTC_DAY_OF_MONTH?7?
#define?RTC_MONTH?8?
#define?RTC_YEAR?9?

四個(gè)控制寄存器的地址定義如下:?
#define?RTC_REG_A?10?
#define?RTC_REG_B?11?
#define?RTC_REG_C?12?
#define?RTC_REG_D?13?

(2)各控制寄存器的狀態(tài)位的詳細(xì)定義?
控制寄存器A(0x0A)主要用于選擇RTC芯片的工作頻率,因此也稱為RTC頻率選擇寄存器。因此Linux用一個(gè)宏別名RTC_FREQ_SELECT來(lái)表示控制寄存器A,如下:?
#define?RTC_FREQ_SELECT?RTC_REG_A?
RTC頻率寄存器中的位被分為三組:①bit[7]表示UIP標(biāo)志;②bit[6:4]用于除法器的頻率選擇;③bit[3:0]用于速率選擇。它們的定義如下:?
#?define?RTC_UIP?0x80?
#?define?RTC_DIV_CTL?0x70?
/*?Periodic?intr.?/?Square?wave?rate?select.?0=none,?1=32.8kHz,...?15=2Hz?*/?
#?define?RTC_RATE_SELECT?0x0F?
正如7.1.1.1節(jié)所介紹的那樣,bit[6:4]有5中可能的取值,分別為除法器選擇不同的工作頻率或用于重置除法器,各種可能的取值如下定義所示:?
/*?divider?control:?refclock?values?4.194?/?1.049?MHz?/?32.768?kHz?*/?
#?define?RTC_REF_CLCK_4MHZ?0x00?
#?define?RTC_REF_CLCK_1MHZ?0x10?
#?define?RTC_REF_CLCK_32KHZ?0x20?
/*?2?values?for?divider?stage?reset,?others?for?"testing?purposes?only"?*/?
#?define?RTC_DIV_RESET1?0x60?
#?define?RTC_DIV_RESET2?0x70?

寄存器B中的各位用于使能/禁止RTC的各種特性,因此控制寄存器B(0x0B)也稱為“控制寄存器”,Linux用宏別名RTC_CONTROL來(lái)表示控制寄存器B,它與其中的各標(biāo)志位的定義如下所示:?
#define?RTC_CONTROL?RTC_REG_B?
#?define?RTC_SET?0x80?/*?disable?updates?for?clock?setting?*/?
#?define?RTC_PIE?0x40?/*?periodic?interrupt?enable?*/?
#?define?RTC_AIE?0x20?/*?alarm?interrupt?enable?*/?
#?define?RTC_UIE?0x10?/*?update-finished?interrupt?enable?*/?
#?define?RTC_SQWE?0x08?/*?enable?square-wave?output?*/?
#?define?RTC_DM_BINARY?0x04?/*?all?time/date?values?are?BCD?if?clear?*/?
#?define?RTC_24H?0x02?/*?24?hour?mode?-?else?hours?bit?7?means?pm?*/?
#?define?RTC_DST_EN?0x01?/*?auto?switch?DST?-?works?f.?USA?only?*/?

寄存器C是RTC芯片的中斷請(qǐng)求狀態(tài)寄存器,Linux用宏別名RTC_INTR_FLAGS來(lái)表示寄存器C,它與其中的各標(biāo)志位的定義如下所示:?
#define?RTC_INTR_FLAGS?RTC_REG_C?
/*?caution?-?cleared?by?read?*/?
#?define?RTC_IRQF?0x80?/*?any?of?the?following?3?is?active?*/?
#?define?RTC_PF?0x40?
#?define?RTC_AF?0x20?
#?define?RTC_UF?0x10?

寄存器D僅定義了其最高位bit[7],以表示RTC芯片是否有效。因此寄存器D也稱為RTC的有效寄存器。Linux用宏別名RTC_VALID來(lái)表示寄存器D,如下:?
#define?RTC_VALID?RTC_REG_D?
#?define?RTC_VRT?0x80?/*?valid?RAM?and?time?*/?

(3)二進(jìn)制格式與BCD格式的相互轉(zhuǎn)換?
由于時(shí)間與日期寄存器中的值可能以BCD格式存儲(chǔ),也可能以二進(jìn)制格式存儲(chǔ),因此需要定義二進(jìn)制格式與BCD格式之間的相互轉(zhuǎn)換宏,以方便編程。如下:?
#ifndef?BCD_TO_BIN?
#define?BCD_TO_BIN(val)?((val)=((val)&15)?+?((val)>>4)*10)?
#endif?

#ifndef?BIN_TO_BCD?
#define?BIN_TO_BCD(val)?((val)=(((val)/10)<<4)?+?(val)%10)?
#endif?

7.2.3?內(nèi)核對(duì)RTC的操作?
如前所述,Linux內(nèi)核與RTC進(jìn)行互操作的時(shí)機(jī)只有兩個(gè):(1)內(nèi)核在啟動(dòng)時(shí)從RTC中讀取啟動(dòng)時(shí)的時(shí)間與日期;(2)內(nèi)核在需要時(shí)將時(shí)間與日期回寫(xiě)到RTC中。為此,Linux內(nèi)核在arch/i386/kernel/time.c文件中實(shí)現(xiàn)了函數(shù)get_cmos_time()來(lái)進(jìn)行對(duì)RTC的第一種操作。顯然,get_cmos_time()函數(shù)僅僅在內(nèi)核啟動(dòng)時(shí)被調(diào)用一次。而對(duì)于第二種操作,Linux則同樣在arch/i386/kernel/time.c文件中實(shí)現(xiàn)了函數(shù)set_rtc_mmss(),以支持向RTC中回寫(xiě)當(dāng)前時(shí)間與日期。下面我們將來(lái)分析這二個(gè)函數(shù)的實(shí)現(xiàn)。?
在分析get_cmos_time()函數(shù)之前,我們先來(lái)看看RTC芯片對(duì)其時(shí)間與日期寄存器組的更新原理。?

(1)Update?In?Progress?
當(dāng)控制寄存器B中的SET標(biāo)志位為0時(shí),MC146818芯片每秒都會(huì)在芯片內(nèi)部執(zhí)行一個(gè)“更新周期”(Update?Cycle),其作用是增加秒寄存器的值,并檢查秒寄存器是否溢出。如果溢出,則增加分鐘寄存器的值,如此一致下去直到年寄存器。在“更新周期”期間,時(shí)間與日期寄存器組(0x00~0x09)是不可用的,此時(shí)如果讀取它們的值將得到未定義的值,因?yàn)镸C146818在整個(gè)更新周期期間會(huì)把時(shí)間與日期寄存器組從CPU總線上脫離,從而防止軟件程序讀到一個(gè)漸變的數(shù)據(jù)。?
在MC146818的輸入時(shí)鐘頻率(也即晶體增蕩器的頻率)為4.194304MHZ或1.048576MHZ的情況下,“更新周期”需要花費(fèi)248us,而對(duì)于輸入時(shí)鐘頻率為32.768KHZ的情況,“更新周期”需要花費(fèi)1984us=1.984ms。控制寄存器A中的UIP標(biāo)志位用來(lái)表示MC146818是否正處于更新周期中,當(dāng)UIP從0變?yōu)?的那個(gè)時(shí)刻,就表示MC146818將在稍后馬上就開(kāi)更新周期。在UIP從0變到1的那個(gè)時(shí)刻與MC146818真正開(kāi)始Update?Cycle的那個(gè)時(shí)刻之間時(shí)有一段時(shí)間間隔的,通常是244us。也就是說(shuō),在UIP從0變到1的244us之后,時(shí)間與日期寄存器組中的值才會(huì)真正開(kāi)始改變,而在這之間的244us間隔內(nèi),它們的值并不會(huì)真正改變。如下圖所示:?

(2)get_cmos_time()函數(shù)?
該函數(shù)只被內(nèi)核的初始化例程time_init()和內(nèi)核的APM模塊所調(diào)用。其源碼如下:?
/*?not?static:?needed?by?APM?*/?
unsigned?long?get_cmos_time(void)?
{?
unsigned?int?year,?mon,?day,?hour,?min,?sec;?
int?i;?

/*?The?Linux?interpretation?of?the?CMOS?clock?register?contents:?
*?When?the?Update-In-Progress?(UIP)?flag?goes?from?1?to?0,?the?
*?RTC?registers?show?the?second?which?has?precisely?just?started.?
*?Let's?hope?other?operating?systems?interpret?the?RTC?the?same?way.?
*/?
/*?read?RTC?exactly?on?falling?edge?of?update?flag?*/?
for?(i?=?0?;?i?<?1000000?;?i++)?/*?may?take?up?to?1?second...?*/?
if?(CMOS_READ(RTC_FREQ_SELECT)?&?RTC_UIP)?
break;?
for?(i?=?0?;?i?<?1000000?;?i++)?/*?must?try?at?least?2.228?ms?*/?
if?(!(CMOS_READ(RTC_FREQ_SELECT)?&?RTC_UIP))?
break;?
do?{?/*?Isn't?this?overkill???UIP?above?should?guarantee?consistency?*/?
sec?=?CMOS_READ(RTC_SECONDS);?
min?=?CMOS_READ(RTC_MINUTES);?
hour?=?CMOS_READ(RTC_HOURS);?
day?=?CMOS_READ(RTC_DAY_OF_MONTH);?
mon?=?CMOS_READ(RTC_MONTH);?
year?=?CMOS_READ(RTC_YEAR);?
}?while?(sec?!=?CMOS_READ(RTC_SECONDS));?
if?(!(CMOS_READ(RTC_CONTROL)?&?RTC_DM_BINARY)?||?RTC_ALWAYS_BCD)?
{?
BCD_TO_BIN(sec);?
BCD_TO_BIN(min);?
BCD_TO_BIN(hour);?
BCD_TO_BIN(day);?
BCD_TO_BIN(mon);?
BCD_TO_BIN(year);?
}?
if?((year?+=?1900)?<?1970)?
year?+=?100;?
return?mktime(year,?mon,?day,?hour,?min,?sec);?
}?
對(duì)該函數(shù)的注釋如下:?
(1)在從RTC中讀取時(shí)間時(shí),由于RTC存在Update?Cycle,因此軟件發(fā)出讀操作的時(shí)機(jī)是很重要的。對(duì)此,get_cmos_time()函數(shù)通過(guò)UIP標(biāo)志位來(lái)解決這個(gè)問(wèn)題:第一個(gè)for循環(huán)不停地讀取RTC頻率選擇寄存器中的UIP標(biāo)志位,并且只要讀到UIP的值為1就馬上退出這個(gè)for循環(huán)。第二個(gè)for循環(huán)同樣不停地讀取UIP標(biāo)志位,但他只要一讀到UIP的值為0就馬上退出這個(gè)for循環(huán)。這兩個(gè)for循環(huán)的目的就是要在軟件邏輯上同步RTC的Update?Cycle,顯然第二個(gè)for循環(huán)最大可能需要2.228ms(TBUC+max(TUC)=244us+1984us=2.228ms)?
(2)從第二個(gè)for循環(huán)退出后,RTC的Update?Cycle已經(jīng)結(jié)束。此時(shí)我們就已經(jīng)把當(dāng)前時(shí)間邏輯定準(zhǔn)在RTC的當(dāng)前一秒時(shí)間間隔內(nèi)。也就是說(shuō),這是我們就可以開(kāi)始從RTC寄存器中讀取當(dāng)前時(shí)間值。但是要注意,讀操作應(yīng)該保證在244us內(nèi)完成(準(zhǔn)確地說(shuō),讀操作要在RTC的下一個(gè)更新周期開(kāi)始之前完成,244us的限制是過(guò)分偏執(zhí)的:-)。所以,get_cmos_time()函數(shù)接下來(lái)通過(guò)CMOS_READ()宏從RTC中依次讀取秒、分鐘、小時(shí)、日期、月份和年分。這里的do{}while(sec!=CMOS_READ(RTC_SECOND))循環(huán)就是用來(lái)確保上述6個(gè)讀操作必須在下一個(gè)Update?Cycle開(kāi)始之前完成。?
(3)接下來(lái)判定時(shí)間的數(shù)據(jù)格式,PC機(jī)中一般總是使用BCD格式的時(shí)間,因此需要通過(guò)BCD_TO_BIN()宏把BCD格式轉(zhuǎn)換為二進(jìn)制格式。?
(4)接下來(lái)對(duì)年分進(jìn)行修正,以將年份轉(zhuǎn)換為“19XX”的格式,如果是1970以前的年份,則將其加上100。?
(5)最后調(diào)用mktime()函數(shù)將當(dāng)前時(shí)間與日期轉(zhuǎn)換為相對(duì)于1970-01-01?00:00:00的秒數(shù)值,并將其作為函數(shù)返回值返回。?

函數(shù)mktime()定義在include/linux/time.h頭文件中,它用來(lái)根據(jù)Gauss算法將以year/mon/day/hour/min/sec(如1980-12-31?23:59:59)格式表示的時(shí)間轉(zhuǎn)換為相對(duì)于1970-01-01?00:00:00這個(gè)UNIX時(shí)間基準(zhǔn)以來(lái)的相對(duì)秒數(shù)。其源碼如下:?
static?inline?unsigned?long?
mktime?(unsigned?int?year,?unsigned?int?mon,?
unsigned?int?day,?unsigned?int?hour,?
unsigned?int?min,?unsigned?int?sec)?
{?
if?(0?>=?(int)?(mon?-=?2))?{?/*?1..12?->?11,12,1..10?*/?
mon?+=?12;?/*?Puts?Feb?last?since?it?has?leap?day?*/?
year?-=?1;?
}?

return?(((?
(unsigned?long)?(year/4?-?year/100?+?year/400?+?367*mon/12?+?day)?+?
year*365?-?719499?
)*24?+?hour?/*?now?have?hours?*/?
)*60?+?min?/*?now?have?minutes?*/?
)*60?+?sec;?/*?finally?seconds?*/?
}?

(3)set_rtc_mmss()函數(shù)?
該函數(shù)用來(lái)更新RTC中的時(shí)間,它僅有一個(gè)參數(shù)nowtime,是以秒數(shù)表示的當(dāng)前時(shí)間,其源碼如下:?
static?int?set_rtc_mmss(unsigned?long?nowtime)?
{?
int?retval?=?0;?
int?real_seconds,?real_minutes,?cmos_minutes;?
unsigned?char?save_control,?save_freq_select;?

/*?gets?recalled?with?irq?locally?disabled?*/?
spin_lock(&rtc_lock);?
save_control?=?CMOS_READ(RTC_CONTROL);?/*?tell?the?clock?it's?being?set?*/?
CMOS_WRITE((save_control|RTC_SET),?RTC_CONTROL);?

save_freq_select?=?CMOS_READ(RTC_FREQ_SELECT);?/*?stop?and?reset?prescaler?*/?
CMOS_WRITE((save_freq_select|RTC_DIV_RESET2),?RTC_FREQ_SELECT);?

cmos_minutes?=?CMOS_READ(RTC_MINUTES);?
if?(!(save_control?&?RTC_DM_BINARY)?||?RTC_ALWAYS_BCD)?
BCD_TO_BIN(cmos_minutes);?

/*?
*?since?we're?only?adjusting?minutes?and?seconds,?
*?don't?interfere?with?hour?overflow.?This?avoids?
*?messing?with?unknown?time?zones?but?requires?your?
*?RTC?not?to?be?off?by?more?than?15?minutes?
*/?
real_seconds?=?nowtime?%?60;?
real_minutes?=?nowtime?/?60;?
if?(((abs(real_minutes?-?cmos_minutes)?+?15)/30)?&?1)?
real_minutes?+=?30;?/*?correct?for?half?hour?time?zone?*/?
real_minutes?%=?60;?

if?(abs(real_minutes?-?cmos_minutes)?<?30)?{?
if?(!(save_control?&?RTC_DM_BINARY)?||?RTC_ALWAYS_BCD)?{?
BIN_TO_BCD(real_seconds);?
BIN_TO_BCD(real_minutes);?
}?
CMOS_WRITE(real_seconds,RTC_SECONDS);?
CMOS_WRITE(real_minutes,RTC_MINUTES);?
}?else?{?
printk(KERN_WARNING?
"set_rtc_mmss:?can't?update?from?%d?to?%d\n",?
cmos_minutes,?real_minutes);?
retval?=?-1;?
}?

/*?The?following?flags?have?to?be?released?exactly?in?this?order,?
*?otherwise?the?DS12887?(popular?MC146818A?clone?with?integrated?
*?battery?and?quartz)?will?not?reset?the?oscillator?and?will?not?
*?update?precisely?500?ms?later.?You?won't?find?this?mentioned?in?
*?the?Dallas?Semiconductor?data?sheets,?but?who?believes?data?
*?sheets?anyway?...?--?Markus?Kuhn?
*/?
CMOS_WRITE(save_control,?RTC_CONTROL);?
CMOS_WRITE(save_freq_select,?RTC_FREQ_SELECT);?
spin_unlock(&rtc_lock);?

return?retval;?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先對(duì)自旋鎖rtc_lock進(jìn)行加鎖。定義在arch/i386/kernel/time.c文件中的全局自旋鎖rtc_lock用來(lái)串行化所有CPU對(duì)RTC的操作。?
(2)接下來(lái),在RTC控制寄存器中設(shè)置SET標(biāo)志位,以便通知RTC軟件程序隨后馬上將要更新它的時(shí)間與日期。為此先把RTC_CONTROL寄存器的當(dāng)前值讀到變量save_control中,然后再把值(save_control?|?RTC_SET)回寫(xiě)到寄存器RTC_CONTROL中。?
(3)然后,通過(guò)RTC_FREQ_SELECT寄存器中bit[6:4]重啟RTC芯片內(nèi)部的除法器。為此,類似地先把RTC_FREQ_SELECT寄存器的當(dāng)前值讀到變量save_freq_select中,然后再把值(save_freq_select?|?RTC_DIV_RESET2)回寫(xiě)到RTC_FREQ_SELECT寄存器中。?
(4)接著將RTC_MINUTES寄存器的當(dāng)前值讀到變量cmos_minutes中,并根據(jù)需要將它從BCD格式轉(zhuǎn)化為二進(jìn)制格式。?
(5)從nowtime參數(shù)中得到當(dāng)前時(shí)間的秒數(shù)和分鐘數(shù)。分別保存到real_seconds和real_minutes變量。注意,這里對(duì)于半小時(shí)區(qū)的情況要修正分鐘數(shù)real_minutes的值。?
(6)然后,在real_minutes與RTC_MINUTES寄存器的原值cmos_minutes二者相差不超過(guò)30分鐘的情況下,將real_seconds和real_minutes所表示的時(shí)間值寫(xiě)到RTC的秒寄存器和分鐘寄存器中。當(dāng)然,在回寫(xiě)之前要記得把二進(jìn)制轉(zhuǎn)換為BCD格式。?
(7)最后,恢復(fù)RTC_CONTROL寄存器和RTC_FREQ_SELECT寄存器原來(lái)的值。這二者的先后次序是:先恢復(fù)RTC_CONTROL寄存器,再恢復(fù)RTC_FREQ_SELECT寄存器。然后在解除自旋鎖rtc_lock后就可以返回了。?

最后,需要說(shuō)明的一點(diǎn)是,set_rtc_mmss()函數(shù)盡可能在靠近一秒時(shí)間間隔的中間位置(也即500ms處)左右被調(diào)用。此外,Linux內(nèi)核對(duì)每一次成功的更新RTC時(shí)間都留下時(shí)間軌跡,它用一個(gè)系統(tǒng)全局變量last_rtc_update來(lái)表示內(nèi)核最近一次成功地對(duì)RTC進(jìn)行更新的時(shí)間(單位是秒數(shù))。該變量定義在arch/i386/kernel/time.c文件中:?
/*?last?time?the?cmos?clock?got?updated?*/?
static?long?last_rtc_update;?
每一次成功地調(diào)用set_rtc_mmss()函數(shù)后,內(nèi)核都會(huì)馬上將last_rtc_update更新為當(dāng)前時(shí)間(具體請(qǐng)見(jiàn)7.4.3節(jié))。


?dreamice 回復(fù)于:2008-11-06 17:56:21

7.3?Linux對(duì)時(shí)間的表示?
通常,操作系統(tǒng)可以使用三種方法來(lái)表示系統(tǒng)的當(dāng)前時(shí)間與日期:①最簡(jiǎn)單的一種方法就是直接用一個(gè)64位的計(jì)數(shù)器來(lái)對(duì)時(shí)鐘滴答進(jìn)行計(jì)數(shù)。②第二種方法就是用一個(gè)32位計(jì)數(shù)器來(lái)對(duì)秒進(jìn)行計(jì)數(shù),同時(shí)還用一個(gè)32位的輔助計(jì)數(shù)器對(duì)時(shí)鐘滴答計(jì)數(shù),之子累積到一秒為止。因?yàn)?32超過(guò)136年,因此這種方法直至22世紀(jì)都可以讓系統(tǒng)工作得很好。③第三種方法也是按時(shí)鐘滴答進(jìn)行計(jì)數(shù),但是是相對(duì)于系統(tǒng)啟動(dòng)以來(lái)的滴答次數(shù),而不是相對(duì)于相對(duì)于某個(gè)確定的外部時(shí)刻;當(dāng)讀外部后備時(shí)鐘(如RTC)或用戶輸入實(shí)際時(shí)間時(shí),根據(jù)當(dāng)前的滴答次數(shù)計(jì)算系統(tǒng)當(dāng)前時(shí)間。?
UNIX類操作系統(tǒng)通常都采用第三種方法來(lái)維護(hù)系統(tǒng)的時(shí)間與日期。?

7.3.1?基本概念?
首先,有必要明確一些Linux內(nèi)核時(shí)鐘驅(qū)動(dòng)中的基本概念。?
(1)時(shí)鐘周期(clock?cycle)的頻率:8253/8254?PIT的本質(zhì)就是對(duì)由晶體振蕩器產(chǎn)生的時(shí)鐘周期進(jìn)行計(jì)數(shù),晶體振蕩器在1秒時(shí)間內(nèi)產(chǎn)生的時(shí)鐘脈沖個(gè)數(shù)就是時(shí)鐘周期的頻率。Linux用宏CLOCK_TICK_RATE來(lái)表示8254?PIT的輸入時(shí)鐘脈沖的頻率(在PC機(jī)中這個(gè)值通常是1193180HZ),該宏定義在include/asm-i386/timex.h頭文件中:?
#define?CLOCK_TICK_RATE?1193180?/*?Underlying?HZ?*/?
(2)時(shí)鐘滴答(clock?tick):我們知道,當(dāng)PIT通道0的計(jì)數(shù)器減到0值時(shí),它就在IRQ0上產(chǎn)生一次時(shí)鐘中斷,也即一次時(shí)鐘滴答。PIT通道0的計(jì)數(shù)器的初始值決定了要過(guò)多少時(shí)鐘周期才產(chǎn)生一次時(shí)鐘中斷,因此也就決定了一次時(shí)鐘滴答的時(shí)間間隔長(zhǎng)度。?
(3)時(shí)鐘滴答的頻率(HZ):也即1秒時(shí)間內(nèi)PIT所產(chǎn)生的時(shí)鐘滴答次數(shù)。類似地,這個(gè)值也是由PIT通道0的計(jì)數(shù)器初值決定的(反過(guò)來(lái)說(shuō),確定了時(shí)鐘滴答的頻率值后也就可以確定8254?PIT通道0的計(jì)數(shù)器初值)。Linux內(nèi)核用宏HZ來(lái)表示時(shí)鐘滴答的頻率,而且在不同的平臺(tái)上HZ有不同的定義值。對(duì)于ALPHA和IA62平臺(tái)HZ的值是1024,對(duì)于SPARC、MIPS、ARM和i386等平臺(tái)HZ的值都是100。該宏在i386平臺(tái)上的定義如下(include/asm-i386/param.h):?
#ifndef?HZ?
#define?HZ?100?
#endif?
根據(jù)HZ的值,我們也可以知道一次時(shí)鐘滴答的具體時(shí)間間隔應(yīng)該是(1000ms/HZ)=10ms。?
(4)時(shí)鐘滴答的時(shí)間間隔:Linux用全局變量tick來(lái)表示時(shí)鐘滴答的時(shí)間間隔長(zhǎng)度,該變量定義在kernel/timer.c文件中,如下:?
long?tick?=?(1000000?+?HZ/2)?/?HZ;?/*?timer?interrupt?period?*/?
tick變量的單位是微妙(μs),由于在不同平臺(tái)上宏HZ的值會(huì)有所不同,因此方程式tick=1000000÷HZ的結(jié)果可能會(huì)是個(gè)小數(shù),因此將其進(jìn)行四舍五入成一個(gè)整數(shù),所以Linux將tick定義成(1000000+HZ/2)/HZ,其中被除數(shù)表達(dá)式中的HZ/2的作用就是用來(lái)將tick值向上圓整成一個(gè)整型數(shù)。?
另外,Linux還用宏TICK_SIZE來(lái)作為tick變量的引用別名(alias),其定義如下(arch/i386/kernel/time.c):?
#define?TICK_SIZE?tick?
(5)宏LATCH:Linux用宏LATCH來(lái)定義要寫(xiě)到PIT通道0的計(jì)數(shù)器中的值,它表示PIT將沒(méi)隔多少個(gè)時(shí)鐘周期產(chǎn)生一次時(shí)鐘中斷。顯然LATCH應(yīng)該由下列公式計(jì)算:?
LATCH=(1秒之內(nèi)的時(shí)鐘周期個(gè)數(shù))÷(1秒之內(nèi)的時(shí)鐘中斷次數(shù))=(CLOCK_TICK_RATE)÷(HZ)?
類似地,上述公式的結(jié)果可能會(huì)是個(gè)小數(shù),應(yīng)該對(duì)其進(jìn)行四舍五入。所以,Linux將LATCH定義為(include/linux/timex.h):?
/*?LATCH?is?used?in?the?interval?timer?and?ftape?setup.?*/?
#define?LATCH?((CLOCK_TICK_RATE?+?HZ/2)?/?HZ)?/*?For?divider?*/?
類似地,被除數(shù)表達(dá)式中的HZ/2也是用來(lái)將LATCH向上圓整成一個(gè)整數(shù)。?

7.3.2?表示系統(tǒng)當(dāng)前時(shí)間的內(nèi)核數(shù)據(jù)結(jié)構(gòu)?
作為一種UNIX類操作系統(tǒng),Linux內(nèi)核顯然采用本節(jié)一開(kāi)始所述的第三種方法來(lái)表示系統(tǒng)的當(dāng)前時(shí)間。Linux內(nèi)核在表示系統(tǒng)當(dāng)前時(shí)間時(shí)用到了三個(gè)重要的數(shù)據(jù)結(jié)構(gòu):?
①全局變量jiffies:這是一個(gè)32位的無(wú)符號(hào)整數(shù),用來(lái)表示自內(nèi)核上一次啟動(dòng)以來(lái)的時(shí)鐘滴答次數(shù)。每發(fā)生一次時(shí)鐘滴答,內(nèi)核的時(shí)鐘中斷處理函數(shù)timer_interrupt()都要將該全局變量jiffies加1。該變量定義在kernel/timer.c源文件中,如下所示:?
unsigned?long?volatile?jiffies;?
C語(yǔ)言限定符volatile表示jiffies是一個(gè)易該變的變量,因此編譯器將使對(duì)該變量的訪問(wèn)從不通過(guò)CPU內(nèi)部cache來(lái)進(jìn)行。?
②全局變量xtime:它是一個(gè)timeval結(jié)構(gòu)類型的變量,用來(lái)表示當(dāng)前時(shí)間距UNIX時(shí)間基準(zhǔn)1970-01-01?00:00:00的相對(duì)秒數(shù)值。結(jié)構(gòu)timeval是Linux內(nèi)核表示時(shí)間的一種格式(Linux內(nèi)核對(duì)時(shí)間的表示有多種格式,每種格式都有不同的時(shí)間精度),其時(shí)間精度是微秒。該結(jié)構(gòu)是內(nèi)核表示時(shí)間時(shí)最常用的一種格式,它定義在頭文件include/linux/time.h中,如下所示:?
struct?timeval?{?
time_t?tv_sec;?/*?seconds?*/?
suseconds_t?tv_usec;?/*?microseconds?*/?
};?
其中,成員tv_sec表示當(dāng)前時(shí)間距UNIX時(shí)間基準(zhǔn)的秒數(shù)值,而成員tv_usec則表示一秒之內(nèi)的微秒值,且1000000>tv_usec>=0。?
Linux內(nèi)核通過(guò)timeval結(jié)構(gòu)類型的全局變量xtime來(lái)維持當(dāng)前時(shí)間,該變量定義在kernel/timer.c文件中,如下所示:?
/*?The?current?time?*/?
volatile?struct?timeval?xtime?__attribute__?((aligned?(16)));?
但是,全局變量xtime所維持的當(dāng)前時(shí)間通常是供用戶來(lái)檢索和設(shè)置的,而其他內(nèi)核模塊通常很少使用它(其他內(nèi)核模塊用得最多的是jiffies),因此對(duì)xtime的更新并不是一項(xiàng)緊迫的任務(wù),所以這一工作通常被延遲到時(shí)鐘中斷的底半部分(bottom?half)中來(lái)進(jìn)行。由于bottom?half的執(zhí)行時(shí)間帶有不確定性,因此為了記住內(nèi)核上一次更新xtime是什么時(shí)候,Linux內(nèi)核定義了一個(gè)類似于jiffies的全局變量wall_jiffies,來(lái)保存內(nèi)核上一次更新xtime時(shí)的jiffies值。時(shí)鐘中斷的底半部分每一次更新xtime的時(shí)侯都會(huì)將wall_jiffies更新為當(dāng)時(shí)的jiffies值。全局變量wall_jiffies定義在kernel/timer.c文件中:?
/*?jiffies?at?the?most?recent?update?of?wall?time?*/?
unsigned?long?wall_jiffies;?
③全局變量sys_tz:它是一個(gè)timezone結(jié)構(gòu)類型的全局變量,表示系統(tǒng)當(dāng)前的時(shí)區(qū)信息。結(jié)構(gòu)類型timezone定義在include/linux/time.h頭文件中,如下所示:?
struct?timezone?{?
int?tz_minuteswest;?/*?minutes?west?of?Greenwich?*/?
int?tz_dsttime;?/*?type?of?dst?correction?*/?
};?
基于上述結(jié)構(gòu),Linux在kernel/time.c文件中定義了全局變量sys_tz表示系統(tǒng)當(dāng)前所處的時(shí)區(qū)信息,如下所示:?
struct?timezone?sys_tz;?

7.3.3?Linux對(duì)TSC的編程實(shí)現(xiàn)?
Linux用定義在arch/i386/kernel/time.c文件中的全局變量use_tsc來(lái)表示內(nèi)核是否使用CPU的TSC寄存器,use_tsc=1表示使用TSC,use_tsc=0表示不使用TSC。該變量的值是在time_init()初始化函數(shù)中被初始化的(詳見(jiàn)下一節(jié))。該變量的定義如下:?
static?int?use_tsc;?
宏cpu_has_tsc可以確定當(dāng)前系統(tǒng)的CPU是否配置有TSC寄存器。此外,宏CONFIG_X86_TSC也表示是否存在TSC寄存器。?

7.3.3.1?讀TSC寄存器的宏操作?
x86?CPU的rdtsc指令將TSC寄存器的高32位值讀到EDX寄存器中、低32位讀到EAX寄存器中。Linux根據(jù)不同的需要,在rdtsc指令的基礎(chǔ)上封裝幾個(gè)高層宏操作,以讀取TSC寄存器的值。它們均定義在include/asm-i386/msr.h頭文件中,如下:?
#define?rdtsc(low,high)?\?
__asm__?__volatile__("rdtsc"?:?"=a"?(low),?"=d"?(high))?

#define?rdtscl(low)?\?
__asm__?__volatile__?("rdtsc"?:?"=a"?(low)?:?:?"edx")?

#define?rdtscll(val)?\?
__asm__?__volatile__?("rdtsc"?:?"=A"?(val))?
宏rdtsc()同時(shí)讀取TSC的LSB與MSB,并分別保存到宏參數(shù)low和high中。宏rdtscl則只讀取TSC寄存器的LSB,并保存到宏參數(shù)low中。宏rdtscll讀取TSC的當(dāng)前64位值,并將其保存到宏參數(shù)val這個(gè)64位變量中。?

7.3.3.2?校準(zhǔn)TSC?
與可編程定時(shí)器PIT相比,用TSC寄存器可以獲得更精確的時(shí)間度量。但是在可以使用TSC之前,它必須精確地確定1個(gè)TSC計(jì)數(shù)值到底代表多長(zhǎng)的時(shí)間間隔,也即到底要過(guò)多長(zhǎng)時(shí)間間隔TSC寄存器才會(huì)加1。Linux內(nèi)核用全局變量fast_gettimeoffset_quotient來(lái)表示這個(gè)值,其定義如下(arch/i386/kernel/time.c):?
/*?Cached?*multiplier*?to?convert?TSC?counts?to?microseconds.?
*?(see?the?equation?below).?
*?Equal?to?2^32?*?(1?/?(clocks?per?usec)?).?
*?Initialized?in?time_init.?
*/?
unsigned?long?fast_gettimeoffset_quotient;?
根據(jù)上述定義的注釋我們可以看出,這個(gè)變量的值是通過(guò)下述公式來(lái)計(jì)算的:?
fast_gettimeoffset_quotient?=?(2^32)?/?(每微秒內(nèi)的時(shí)鐘周期個(gè)數(shù))?
定義在arch/i386/kernel/time.c文件中的函數(shù)calibrate_tsc()就是根據(jù)上述公式來(lái)計(jì)算fast_gettimeoffset_quotient的值的。顯然這個(gè)計(jì)算過(guò)程必須在內(nèi)核啟動(dòng)時(shí)完成,因此,函數(shù)calibrate_tsc()只被初始化函數(shù)time_init()所調(diào)用。?

用TSC實(shí)現(xiàn)高精度的時(shí)間服務(wù)?
在擁有TSC(TimeStamp?Counter)的x86?CPU上,Linux內(nèi)核可以實(shí)現(xiàn)微秒級(jí)的高精度定時(shí)服務(wù),也即可以確定兩次時(shí)鐘中斷之間的某個(gè)時(shí)刻的微秒級(jí)時(shí)間值。如下圖所示:?
圖7-7?TSC時(shí)間關(guān)系?

從上圖中可以看出,要確定時(shí)刻x的微秒級(jí)時(shí)間值,就必須確定時(shí)刻x距上一次時(shí)鐘中斷產(chǎn)生時(shí)刻的時(shí)間間隔偏移offset_usec的值(以微秒為單位)。為此,內(nèi)核定義了以下兩個(gè)變量:?
(1)中斷服務(wù)執(zhí)行延遲delay_at_last_interrupt:由于從產(chǎn)生時(shí)鐘中斷的那個(gè)時(shí)刻到內(nèi)核時(shí)鐘中斷服務(wù)函數(shù)timer_interrupt真正在CPU上執(zhí)行的那個(gè)時(shí)刻之間是有一段延遲間隔的,因此,Linux內(nèi)核用變量delay_at_last_interrupt來(lái)表示這一段時(shí)間延遲間隔,其定義如下(arch/i386/kernel/time.c):?
/*?Number?of?usecs?that?the?last?interrupt?was?delayed?*/?
static?int?delay_at_last_interrupt;?
關(guān)于delay_at_last_interrupt的計(jì)算步驟我們將在分析timer_interrupt()函數(shù)時(shí)討論。?
(2)全局變量last_tsc_low:它表示中斷服務(wù)timer_interrupt真正在CPU上執(zhí)行時(shí)刻的TSC寄存器值的低32位(LSB)。?
顯然,通過(guò)delay_at_last_interrupt、last_tsc_low和時(shí)刻x處的TSC寄存器值,我們就可以完全確定時(shí)刻x距上一次時(shí)鐘中斷產(chǎn)生時(shí)刻的時(shí)間間隔偏移offset_usec的值。實(shí)現(xiàn)在arch/i386/kernel/time.c中的函數(shù)do_fast_gettimeoffset()就是這樣計(jì)算時(shí)間間隔偏移的,當(dāng)然它僅在CPU配置有TSC寄存器時(shí)才被使用,后面我們會(huì)詳細(xì)分析這個(gè)函數(shù)。


?dreamice 回復(fù)于:2008-11-06 17:56:41

7.4?時(shí)鐘中斷的驅(qū)動(dòng)?
如前所述,8253/8254?PIT的通道0通常被用來(lái)在IRQ0上產(chǎn)生周期性的時(shí)鐘中斷。對(duì)時(shí)鐘中斷的驅(qū)動(dòng)是絕大數(shù)操作系統(tǒng)內(nèi)核實(shí)現(xiàn)time-keeping的關(guān)鍵所在。不同的OS對(duì)時(shí)鐘驅(qū)動(dòng)的要求也不同,但是一般都包含下列要求內(nèi)容:?
1.?維護(hù)系統(tǒng)的當(dāng)前時(shí)間與日期。?
2.?防止進(jìn)程運(yùn)行時(shí)間超出其允許的時(shí)間。?
3.?對(duì)CPU的使用情況進(jìn)行記帳統(tǒng)計(jì)。?
4.?處理用戶進(jìn)程發(fā)出的時(shí)間系統(tǒng)調(diào)用。?
5.?對(duì)系統(tǒng)某些部分提供監(jiān)視定時(shí)器。?
其中,第一項(xiàng)功能是所有OS都必須實(shí)現(xiàn)的基礎(chǔ)功能,它是OS內(nèi)核的運(yùn)行基礎(chǔ)。通常有三種方法可用來(lái)維護(hù)系統(tǒng)的時(shí)間與日期:(1)最簡(jiǎn)單的一種方法就是用一個(gè)64位的計(jì)數(shù)器來(lái)對(duì)時(shí)鐘滴答進(jìn)行計(jì)數(shù)。(2)第二種方法就是用一個(gè)32位計(jì)數(shù)器來(lái)對(duì)秒進(jìn)行計(jì)數(shù)。用一個(gè)32位的輔助計(jì)數(shù)器來(lái)對(duì)時(shí)鐘滴答計(jì)數(shù)直至累計(jì)一秒為止。因?yàn)?32超過(guò)136年,因此這種方法直至22世紀(jì)都可以工作得很好。(3)第三種方法也是按滴答進(jìn)行計(jì)數(shù),但卻是相對(duì)于系統(tǒng)啟動(dòng)以來(lái)的滴答次數(shù),而不是相對(duì)于一個(gè)確定的外部時(shí)刻。當(dāng)讀后備時(shí)鐘(如RTC)或用戶輸入實(shí)際時(shí)間時(shí),根據(jù)當(dāng)前的滴答次數(shù)計(jì)算系統(tǒng)當(dāng)前時(shí)間。?
UNIX類的OS通常都采用第三種方法來(lái)維護(hù)系統(tǒng)的時(shí)間與日期。?

7.4.1?Linux對(duì)時(shí)鐘中斷的初始化?
Linux對(duì)時(shí)鐘中斷的初始化是分為幾個(gè)步驟來(lái)進(jìn)行的:(1)首先,由init_IRQ()函數(shù)通過(guò)調(diào)用init_ISA_IRQ()函數(shù)對(duì)中斷向量32~256所對(duì)應(yīng)的中斷向量描述符進(jìn)行初始化設(shè)置。顯然,這其中也就把IRQ0(也即中斷向量32)的中斷向量描述符初始化了。(2)然后,init_IRQ()函數(shù)設(shè)置中斷向量32~256相對(duì)應(yīng)的中斷門(mén)。(3)init_IRQ()函數(shù)對(duì)PIT進(jìn)行初始化編程;(4)sched_init()函數(shù)對(duì)計(jì)數(shù)器、時(shí)間中斷的Bottom?Half進(jìn)行初始化。(5)最后,由time_init()函數(shù)對(duì)Linux內(nèi)核的時(shí)鐘中斷機(jī)制進(jìn)行初始化。這三個(gè)初始化函數(shù)都是由init/main.c文件中的start_kernel()函數(shù)調(diào)用的,如下:?
asmlinkage?void?__init?start_kernel()?
{?
…?
trap_init();?
init_IRQ();?
sched_init();?
time_init();?
softirq_init();?
…?
}?

(1)init_IRQ()函數(shù)對(duì)8254?PIT的初始化編程?
函數(shù)init_IRQ()函數(shù)在完成中斷門(mén)的初始化后,就對(duì)8254?PIT進(jìn)行初始化編程設(shè)置,設(shè)置的步驟如下:(1)設(shè)置8254?PIT的控制寄存器(端口0x43)的值為“01100100”,也即選擇通道0、先讀寫(xiě)LSB再讀寫(xiě)MSB、工作模式2、二進(jìn)制存儲(chǔ)格式。(2)將宏LATCH的值寫(xiě)入通道0的計(jì)數(shù)器中(端口0x40),注意要先寫(xiě)LATCH的LSB,再寫(xiě)LATCH的高字節(jié)。其源碼如下所示(arch/i386/kernel/i8259.c):?
void?__init?init_IRQ(void)?
{?
……?
/*?
*?Set?the?clock?to?HZ?Hz,?we?already?have?a?valid?
*?vector?now:?
*/?
outb_p(0x34,0x43);?/*?binary,?mode?2,?LSB/MSB,?ch?0?*/?
outb_p(LATCH?&?0xff?,?0x40);?/*?LSB?*/?
outb(LATCH?>>?8?,?0x40);?/*?MSB?*/?
……?
}?

(2)sched_init()對(duì)定時(shí)器機(jī)制和時(shí)鐘中斷的Bottom?Half的初始化?
函數(shù)sched_init()中與時(shí)間相關(guān)的初始化過(guò)程主要有兩步:(1)調(diào)用init_timervecs()函數(shù)初始化內(nèi)核定時(shí)器機(jī)制;(2)調(diào)用init_bh()函數(shù)將BH向量TIMER_BH、TQUEUE_BH和IMMEDIATE_BH所對(duì)應(yīng)的BH函數(shù)分別設(shè)置成timer_bh()、tqueue_bh()和immediate_bh()函數(shù)。如下所示(kernel/sched.c):?
void?__init?sched_init(void)?
{?
……?
init_timervecs();?

init_bh(TIMER_BH,?timer_bh);?
init_bh(TQUEUE_BH,?tqueue_bh);?
init_bh(IMMEDIATE_BH,?immediate_bh);?
……?
}?

(3)time_init()函數(shù)對(duì)內(nèi)核時(shí)鐘中斷機(jī)制的初始化?
前面兩個(gè)函數(shù)所進(jìn)行的初始化步驟都是為時(shí)間中斷機(jī)制做好準(zhǔn)備而已。在執(zhí)行完init_IRQ()函數(shù)和sched_init()函數(shù)后,CPU已經(jīng)可以為IRQ0上的時(shí)鐘中斷進(jìn)行服務(wù)了,因?yàn)镮RQ0所對(duì)應(yīng)的中斷門(mén)已經(jīng)被設(shè)置好指向中斷服務(wù)函數(shù)IRQ0x20_interrupt()。但是由于此時(shí)中斷向量0x20的中斷向量描述符irq_desc[0]還是處于初始狀態(tài)(其status成員的值為IRQ_DISABLED),并未掛接任何具體的中斷服務(wù)描述符,因此這時(shí)CPU對(duì)IRQ0的中斷服務(wù)并沒(méi)有任何具體意義,而只是按照規(guī)定的流程空跑一趟。但是當(dāng)CPU執(zhí)行完time_init()函數(shù)后,情形就大不一樣了。?
函數(shù)time_init()主要做三件事:(1)從RTC中獲取內(nèi)核啟動(dòng)時(shí)的時(shí)間與日期;(2)在CPU有TSC的情況下校準(zhǔn)TSC,以便為后面使用TSC做好準(zhǔn)備;(3)在IRQ0的中斷請(qǐng)求描述符中掛接具體的中斷服務(wù)描述符。其源碼如下所示(arch/i386/kernel/time.c):?
void?__init?time_init(void)?
{?
extern?int?x86_udelay_tsc;?

xtime.tv_sec?=?get_cmos_time();?
xtime.tv_usec?=?0;?

/*?
*?If?we?have?APM?enabled?or?the?CPU?clock?speed?is?variable?
*?(CPU?stops?clock?on?HLT?or?slows?clock?to?save?power)?
*?then?the?TSC?timestamps?may?diverge?by?up?to?1?jiffy?from?
*?'real?time'?but?nothing?will?break.?
*?The?most?frequent?case?is?that?the?CPU?is?"woken"?from?a?halt?
*?state?by?the?timer?interrupt?itself,?so?we?get?0?error.?In?the?
*?rare?cases?where?a?driver?would?"wake"?the?CPU?and?request?a?
*?timestamp,?the?maximum?error?is?<?1?jiffy.?But?timestamps?are?
*?still?perfectly?ordered.?
*?Note?that?the?TSC?counter?will?be?reset?if?APM?suspends?
*?to?disk;?this?won't?break?the?kernel,?though,?'cuz?we're?
*?smart.?See?arch/i386/kernel/apm.c.?
*/?
/*?
*?Firstly?we?have?to?do?a?CPU?check?for?chips?with?
*?a?potentially?buggy?TSC.?At?this?point?we?haven't?run?
*?the?ident/bugs?checks?so?we?must?run?this?hook?as?it?
*?may?turn?off?the?TSC?flag.?
*?
*?NOTE:?this?doesnt?yet?handle?SMP?486?machines?where?only?
*?some?CPU's?have?a?TSC.?Thats?never?worked?and?nobody?has?
*?moaned?if?you?have?the?only?one?in?the?world?-?you?fix?it!?
*/?

dodgy_tsc();?

if?(cpu_has_tsc)?{?
unsigned?long?tsc_quotient?=?calibrate_tsc();?
if?(tsc_quotient)?{?
fast_gettimeoffset_quotient?=?tsc_quotient;?
use_tsc?=?1;?
/*?
*?We?could?be?more?selective?here?I?suspect?
*?and?just?enable?this?for?the?next?intel?chips???
*/?
x86_udelay_tsc?=?1;?
#ifndef?do_gettimeoffset?
do_gettimeoffset?=?do_fast_gettimeoffset;?
#endif?
do_get_fast_time?=?do_gettimeofday;?

/*?report?CPU?clock?rate?in?Hz.?
*?The?formula?is?(10^6?*?2^32)?/?(2^32?*?1?/?(clocks/us))?=?
*?clock/second.?Our?precision?is?about?100?ppm.?
*/?
{?unsigned?long?eax=0,?edx=1000;?
__asm__("divl?%2"?
:"=a"?(cpu_khz),?"=d"?(edx)?
:"r"?(tsc_quotient),?
"0"?(eax),?"1"?(edx));?
printk("Detected?%lu.%03lu?MHz?processor.\n",?cpu_khz?/?1000,?cpu_khz?%?1000);?
}?
}?
}?

#ifdef?CONFIG_VISWS?
printk("Starting?Cobalt?Timer?system?clock\n");?

/*?Set?the?countdown?value?*/?
co_cpu_write(CO_CPU_TIMEVAL,?CO_TIME_HZ/HZ);?

/*?Start?the?timer?*/?
co_cpu_write(CO_CPU_CTRL,?co_cpu_read(CO_CPU_CTRL)?|?CO_CTRL_TIMERUN);?

/*?Enable?(unmask)?the?timer?interrupt?*/?
co_cpu_write(CO_CPU_CTRL,?co_cpu_read(CO_CPU_CTRL)?&?~CO_CTRL_TIMEMASK);?

/*?Wire?cpu?IDT?entry?to?s/w?handler?(and?Cobalt?APIC?to?IDT)?*/?
setup_irq(CO_IRQ_TIMER,?&irq0);?
#else?
setup_irq(0,?&irq0);?
#endif?
}?
對(duì)該函數(shù)的注解如下:?
(1)調(diào)用函數(shù)get_cmos_time()從RTC中得到系統(tǒng)啟動(dòng)時(shí)的時(shí)間與日期,它返回的是當(dāng)前時(shí)間相對(duì)于1970-01-01?00:00:00這個(gè)UNIX時(shí)間基準(zhǔn)的秒數(shù)值。因此這個(gè)秒數(shù)值就被保存在系統(tǒng)全局變量xtime的tv_sec成員中。而xtime的另一個(gè)成員tv_usec則被初始化為0。?
(2)通過(guò)dodgy_tsc()函數(shù)檢測(cè)CPU是否存在時(shí)間戳記數(shù)器BUG(I?know?nothing?about?it:-)?
(3)通過(guò)宏cpu_has_tsc來(lái)確定系統(tǒng)中CPU是否存在TSC計(jì)數(shù)器。如果存在TSC,那么內(nèi)核就可以用TSC來(lái)獲得更為精確的時(shí)間。為了能夠用TSC來(lái)修正內(nèi)核時(shí)間。這里必須作一些初始化工作:①調(diào)用calibrate_tsc()來(lái)確定TSC的每一次計(jì)數(shù)真正代表多長(zhǎng)的時(shí)間間隔(單位為us),也即一個(gè)時(shí)鐘周期的真正時(shí)間間隔長(zhǎng)度。②將calibrate_tsc()函數(shù)所返回的值保存在全局變量fast_gettimeoffset_quotient中,該變量被用來(lái)快速地計(jì)算時(shí)間偏差;同時(shí)還將另一個(gè)全局變量use_tsc設(shè)置為1,表示內(nèi)核可以使用TSC。這兩個(gè)變量都定義在arch/i386/kernel/time.c文件中,如下:?
/*?Cached?*multiplier*?to?convert?TSC?counts?to?microseconds.?
*?(see?the?equation?below).?
*?Equal?to?2^32?*?(1?/?(clocks?per?usec)?).?
*?Initialized?in?time_init.?
*/?
unsigned?long?fast_gettimeoffset_quotient;?
……?
static?int?use_tsc;?
③接下來(lái),將系統(tǒng)全局變量x86_udelay_tsc設(shè)置為1,表示可以通過(guò)TSC來(lái)實(shí)現(xiàn)微妙級(jí)的精確延時(shí)。該變量定義在arch/i386/lib/delay.c文件中。④將函數(shù)指針do_gettimeoffset強(qiáng)制性地指向函數(shù)do_fast_gettimeoffset()(與之對(duì)應(yīng)的是do_slow_gettimeoffset()函數(shù)),從而使內(nèi)核在計(jì)算時(shí)間偏差時(shí)可以用TSC這種快速的方法來(lái)進(jìn)行。⑤將函數(shù)指針do_get_fast_time指向函數(shù)do_gettimeofday(),從而可以讓其他內(nèi)核模塊通過(guò)do_gettimeofday()函數(shù)來(lái)獲得更精準(zhǔn)的當(dāng)前時(shí)間。⑥計(jì)算并報(bào)告根據(jù)TSC所算得的CPU時(shí)鐘頻率。?
(4)不考慮CONFIG_VISWS的情況,因此time_init()的最后一個(gè)步驟就是調(diào)用setup_irq()函數(shù)來(lái)為IRQ0掛接具體的中斷服務(wù)描述符irq0。全局變量irq0是時(shí)鐘中斷請(qǐng)求的中斷服務(wù)描述符,其定義如下(arch/i386/kernel/time.c):?
static?struct?irqaction?irq0?=?{?timer_interrupt,?SA_INTERRUPT,?0,?"timer",?NULL,?NULL};?
顯然,函數(shù)timer_interrupt()將成為時(shí)鐘中斷的服務(wù)程序(ISR),而SA_INTERRUPT標(biāo)志也指定了timer_interrupt()函數(shù)將是在CPU關(guān)中斷的條件下執(zhí)行的。結(jié)構(gòu)irq0中的next指針被設(shè)置為NULL,因此IRQ0所對(duì)應(yīng)的中斷服務(wù)隊(duì)列中只有irq0這唯一的一個(gè)元素,且IRQ0不允許中斷共享。?

7.4.2?時(shí)鐘中斷服務(wù)例程timer_interrupt()?
中斷服務(wù)描述符irq0一旦被鉤掛到IRQ0的中斷服務(wù)隊(duì)列中去后,Linux內(nèi)核就可以通過(guò)irq0->handler函數(shù)指針?biāo)赶虻膖imer_interrupt()函數(shù)對(duì)時(shí)鐘中斷請(qǐng)求進(jìn)行真正的服務(wù),而不是向前面所說(shuō)的那樣只是讓CPU“空跑”一趟。此時(shí),Linux內(nèi)核可以說(shuō)是真正的“跳動(dòng)”起來(lái)了。?
在本節(jié)一開(kāi)始所述的對(duì)時(shí)鐘中斷驅(qū)動(dòng)的5項(xiàng)要求中,通常只有第一項(xiàng)(即timekeeping)是最為迫切的,因此必須在時(shí)鐘中斷服務(wù)例程中完成。而其余的幾個(gè)要求可以稍緩,因此可以放在時(shí)鐘中斷的Bottom?Half中去執(zhí)行。這樣,Linux內(nèi)核就是timer_interrupt()函數(shù)的執(zhí)行時(shí)間盡可能的短,因?yàn)樗窃贑PU關(guān)中斷的條件下執(zhí)行的。?
函數(shù)timer_interrupt()的源碼如下(arch/i386/kernel/time.c):?
/*?
*?This?is?the?same?as?the?above,?except?we?_also_?save?the?current?
*?Time?Stamp?Counter?value?at?the?time?of?the?timer?interrupt,?so?that?
*?we?later?on?can?estimate?the?time?of?day?more?exactly.?
*/?
static?void?timer_interrupt(int?irq,?void?*dev_id,?struct?pt_regs?*regs)?
{?
int?count;?

/*?
*?Here?we?are?in?the?timer?irq?handler.?We?just?have?irqs?locally?
*?disabled?but?we?don't?know?if?the?timer_bh?is?running?on?the?other?
*?CPU.?We?need?to?avoid?to?SMP?race?with?it.?NOTE:?we?don'?t?need?
*?the?irq?version?of?write_lock?because?as?just?said?we?have?irq?
*?locally?disabled.?-arca?
*/?
write_lock(&xtime_lock);?

if?(use_tsc)?
{?
/*?
*?It?is?important?that?these?two?operations?happen?almost?at?
*?the?same?time.?We?do?the?RDTSC?stuff?first,?since?it's?
*?faster.?To?avoid?any?inconsistencies,?we?need?interrupts?
*?disabled?locally.?
*/?

/*?
*?Interrupts?are?just?disabled?locally?since?the?timer?irq?
*?has?the?SA_INTERRUPT?flag?set.?-arca?
*/?

/*?read?Pentium?cycle?counter?*/?

rdtscl(last_tsc_low);?

spin_lock(&i8253_lock);?
outb_p(0x00,?0x43);?/*?latch?the?count?ASAP?*/?

count?=?inb_p(0x40);?/*?read?the?latched?count?*/?
count?|=?inb(0x40)?<<?8;?
spin_unlock(&i8253_lock);?

count?=?((LATCH-1)?-?count)?*?TICK_SIZE;?
delay_at_last_interrupt?=?(count?+?LATCH/2)?/?LATCH;?
}?

do_timer_interrupt(irq,?NULL,?regs);?

write_unlock(&xtime_lock);?

}?
對(duì)該函數(shù)的注釋如下:?
(1)由于函數(shù)執(zhí)行期間要訪問(wèn)全局時(shí)間變量xtime,因此一開(kāi)就對(duì)自旋鎖xtime_lock進(jìn)行加鎖。?
(2)如果內(nèi)核使用CPU的TSC寄存器(use_tsc變量非0),那么通過(guò)TSC寄存器來(lái)計(jì)算從時(shí)間中斷的產(chǎn)生到timer_interrupt()函數(shù)真正在CPU上執(zhí)行這之間的時(shí)間延遲:?
l?調(diào)用宏rdtscl()將64位的TSC寄存器值中的低32位(LSB)讀到變量last_tsc_low中,以供do_fast_gettimeoffset()函數(shù)計(jì)算時(shí)間偏差之用。這一步的實(shí)質(zhì)就是將CPU?TSC寄存器的值更新到內(nèi)核對(duì)TSC的緩存變量last_tsc_low中。?
l?通過(guò)讀8254?PIT的通道0的計(jì)數(shù)器的當(dāng)前值來(lái)計(jì)算時(shí)間延遲,為此:首先,對(duì)自旋鎖i8253_lock進(jìn)行加鎖。自旋鎖i8253_lock的作用就是用來(lái)串行化對(duì)8254?PIT的讀寫(xiě)訪問(wèn)。其次,向8254的控制寄存器(端口0x43)中寫(xiě)入值0x00,以便對(duì)通道0的計(jì)數(shù)器進(jìn)行鎖存。最后,通過(guò)端口0x40將通道0的計(jì)數(shù)器的當(dāng)前值讀到局部變量count中,并解鎖i8253_lock。?
l?顯然,從時(shí)間中斷的產(chǎn)生到timer_interrupt()函數(shù)真正執(zhí)行這段時(shí)間內(nèi),以一共流逝了((LATCH-1)-count)個(gè)時(shí)鐘周期,因此這個(gè)延時(shí)長(zhǎng)度可以用如下公式計(jì)算:?
delay_at_last_interrupt=(((LATCH-1)-count)÷LATCH)﹡TICK_SIZE?
顯然,上述公式的結(jié)果是個(gè)小數(shù),應(yīng)對(duì)其進(jìn)行四舍五入,為此,Linux用下述表達(dá)式來(lái)計(jì)算delay_at_last_interrupt變量的值:?
(((LATCH-1)-count)*TICK_SIZE+LATCH/2)/LATCH?
上述被除數(shù)表達(dá)式中的LATCH/2就是用來(lái)將結(jié)果向上圓整成整數(shù)的。?
(3)在計(jì)算出時(shí)間延遲后,最后調(diào)用函數(shù)do_timer_interrupt()執(zhí)行真正的時(shí)鐘服務(wù)。?

函數(shù)do_timer_interrupt()的源碼如下(arch/i386/kernel/time.c):?
/*?
*?timer_interrupt()?needs?to?keep?up?the?real-time?clock,?
*?as?well?as?call?the?"do_timer()"?routine?every?clocktick?
*/?
static?inline?void?do_timer_interrupt(int?irq,?void?*dev_id,?struct?pt_regs?*regs)?
{?
。。。。。。?
do_timer(regs);?
。。。。。。。?
/*?
*?If?we?have?an?externally?synchronized?Linux?clock,?then?update?
*?CMOS?clock?accordingly?every?~11?minutes.?Set_rtc_mmss()?has?to?be?
*?called?as?close?as?possible?to?500?ms?before?the?new?second?starts.?
*/?
if?((time_status?&?STA_UNSYNC)?==?0?&&?
xtime.tv_sec?>?last_rtc_update?+?660?&&?
xtime.tv_usec?>=?500000?-?((unsigned)?tick)?/?2?&&?
xtime.tv_usec?<=?500000?+?((unsigned)?tick)?/?2)?{?
if?(set_rtc_mmss(xtime.tv_sec)?==?0)?
last_rtc_update?=?xtime.tv_sec;?
else?
last_rtc_update?=?xtime.tv_sec?-?600;?/*?do?it?again?in?60?s?*/?
}?
……?
}?
上述代碼中省略了許多與SMP相關(guān)的代碼,因?yàn)槲覀儾魂P(guān)心SMP。從上述代碼我們可以看出,do_timer_interrupt()函數(shù)主要作兩件事:?
(1)調(diào)用do_timer()函數(shù)。?
(2)判斷是否需要更新CMOS時(shí)鐘(即RTC)中的時(shí)間。Linux僅在下列三個(gè)條件同時(shí)成立時(shí)才更新CMOS時(shí)鐘:①系統(tǒng)全局時(shí)間狀態(tài)變量time_status中沒(méi)有設(shè)置STA_UNSYNC標(biāo)志,也即說(shuō)明Linux有一個(gè)外部同步時(shí)鐘。實(shí)際上全局時(shí)間狀態(tài)變量time_status僅在一種情況下會(huì)被清除STA_SYNC標(biāo)志,那就是執(zhí)行adjtimex()系統(tǒng)調(diào)用時(shí)(這個(gè)syscall與NTP有關(guān))。②自從上次CMOS時(shí)鐘更新已經(jīng)過(guò)去了11分鐘。全局變量last_rtc_update保存著上次更新CMOS時(shí)鐘的時(shí)間。③由于RTC存在Update?Cycle,因此最好在一秒時(shí)間間隔的中間位置500ms左右調(diào)用set_rtc_mmss()函數(shù)來(lái)更新CMOS時(shí)鐘。因此Linux規(guī)定僅當(dāng)全局變量xtime的微秒數(shù)tv_usec在500000±(tick/2)微秒范圍范圍之內(nèi)時(shí),才調(diào)用set_rtc_mmss()函數(shù)。如果上述條件均成立,那就調(diào)用set_rtc_mmss()將當(dāng)前時(shí)間xtime.tv_sec更新回寫(xiě)到RTC中。?
如果上面是的set_rtc_mmss()函數(shù)返回0值,則表明更新成功。于是就將“最近一次RTC更新時(shí)間”變量last_rtc_update更新為當(dāng)前時(shí)間xtime.tv_sec。如果返回非0值,說(shuō)明更新失敗,于是就讓last_rtc_update=xtime.tv_sec-600(相當(dāng)于last_rtc_update+=60),以便在在60秒之后再次對(duì)RTC進(jìn)行更新。?

函數(shù)do_timer()實(shí)現(xiàn)在kernel/timer.c文件中,其源碼如下:?
void?do_timer(struct?pt_regs?*regs)?
{?
(*(unsigned?long?*)&jiffies)++;?
#ifndef?CONFIG_SMP?
/*?SMP?process?accounting?uses?the?local?APIC?timer?*/?

update_process_times(user_mode(regs));?
#endif?
mark_bh(TIMER_BH);?
if?(TQ_ACTIVE(tq_timer))?
mark_bh(TQUEUE_BH);?
}?
該函數(shù)的核心是完成三個(gè)任務(wù):?
(1)將表示自系統(tǒng)啟動(dòng)以來(lái)的時(shí)鐘滴答計(jì)數(shù)變量jiffies加1。?
(2)調(diào)用update_process_times()函數(shù)更新當(dāng)前進(jìn)程的時(shí)間統(tǒng)計(jì)信息。注意,該函數(shù)的參數(shù)原型是“int?user_tick”,如果本次時(shí)鐘中斷(即時(shí)鐘滴答)發(fā)生時(shí)CPU正處于用戶態(tài)下執(zhí)行,則user_tick參數(shù)應(yīng)該為1;否則如果本次時(shí)鐘中斷發(fā)生時(shí)CPU正處于核心態(tài)下執(zhí)行時(shí),則user_tick參數(shù)應(yīng)改為0。所以這里我們以宏user_mode(regs)來(lái)作為update_process_times()函數(shù)的調(diào)用參數(shù)。該宏定義在include/asm-i386/ptrace.h頭文件中,它根據(jù)regs指針?biāo)赶虻暮诵亩褩<拇嫫鹘Y(jié)構(gòu)來(lái)判斷CPU進(jìn)入中斷服務(wù)之前是處于用戶態(tài)下還是處于核心態(tài)下。如下所示:?
#ifdef?__KERNEL__?
#define?user_mode(regs)?((VM_MASK?&?(regs)->eflags)?||?(3?&?(regs)->xcs))?
……?
#endif?
(3)調(diào)用mark_bh()函數(shù)激活時(shí)鐘中斷的Bottom?Half向量TIMER_BH和TQUEUE_BH(注意,TQUEUE_BH僅在任務(wù)隊(duì)列tq_timer不為空的情況下才會(huì)被激活)。?

至此,內(nèi)核對(duì)時(shí)鐘中斷的服務(wù)流程宣告結(jié)束,下面我們?cè)敿?xì)分析一下update_process_times()函數(shù)的實(shí)現(xiàn)。?

7.4.3?更新時(shí)間記帳信息——CPU分時(shí)的實(shí)現(xiàn)?
函數(shù)update_process_times()被用來(lái)在發(fā)生時(shí)鐘中斷時(shí)更新當(dāng)前進(jìn)程以及內(nèi)核中與時(shí)間相關(guān)的統(tǒng)計(jì)信息,并根據(jù)這些信息作出相應(yīng)的動(dòng)作,比如:重新進(jìn)行調(diào)度,向當(dāng)前進(jìn)程發(fā)出信號(hào)等。該函數(shù)僅有一個(gè)參數(shù)user_tick,取值為1或0,其含義在前面已經(jīng)敘述過(guò)。?
該函數(shù)的源代碼如下(kernel/timer.c):?
/*?
*?Called?from?the?timer?interrupt?handler?to?charge?one?tick?to?the?current?
*?process.?user_tick?is?1?if?the?tick?is?user?time,?0?for?system.?
*/?
void?update_process_times(int?user_tick)?
{?
struct?task_struct?*p?=?current;?
int?cpu?=?smp_processor_id(),?system?=?user_tick?^?1;?

update_one_process(p,?user_tick,?system,?cpu);?
if?(p->pid)?{?
if?(--p->counter?<=?0)?{?
p->counter?=?0;?
p->need_resched?=?1;?
}?
if?(p->nice?>?0)?
kstat.per_cpu_nice[cpu]?+=?user_tick;?
else?
kstat.per_cpu_user[cpu]?+=?user_tick;?
kstat.per_cpu_system[cpu]?+=?system;?
}?else?if?(local_bh_count(cpu)?||?local_irq_count(cpu)?>?1)?
kstat.per_cpu_system[cpu]?+=?system;?
}?
(1)首先,用smp_processor_id()宏得到當(dāng)前進(jìn)程的CPU?ID。?
(2)然后,讓局部變量system=user_tick^1,表示當(dāng)發(fā)生時(shí)鐘中斷時(shí)CPU是否正處于核心態(tài)下。因此,如果user_tick=1,則system=0;如果user_tick=0,則system=1。?
(3)調(diào)用update_one_process()函數(shù)來(lái)更新當(dāng)前進(jìn)程的task_struct結(jié)構(gòu)中的所有與時(shí)間相關(guān)的統(tǒng)計(jì)信息以及成員變量。該函數(shù)還會(huì)視需要向當(dāng)前進(jìn)程發(fā)送相應(yīng)的信號(hào)(signal)。?
(4)如果當(dāng)前進(jìn)程的PID非0,則執(zhí)行下列步驟來(lái)決定是否重新進(jìn)行調(diào)度,并更新內(nèi)核時(shí)間統(tǒng)計(jì)信息:?
l?將當(dāng)前進(jìn)程的可運(yùn)行時(shí)間片長(zhǎng)度(由task_struct結(jié)構(gòu)中的counter成員表示,其單位是時(shí)鐘滴答次數(shù))減1。如果減到0值,則說(shuō)明當(dāng)前進(jìn)程已經(jīng)用完了系統(tǒng)分配給它的的運(yùn)行時(shí)間片,因此必須重新進(jìn)行調(diào)度。于是將當(dāng)前進(jìn)程的task_struct結(jié)構(gòu)中的need_resched成員變量設(shè)置為1,表示需要重新執(zhí)行調(diào)度。?
l?如果當(dāng)前進(jìn)程的task_struct結(jié)構(gòu)中的nice成員值大于0,那么將內(nèi)核全局統(tǒng)計(jì)信息變量kstat中的per_cpu_nice[cpu]值將上user_tick。否則就將user_tick值加到內(nèi)核全局統(tǒng)計(jì)信息變量kstat中的per_cpu_user[cpu]成員上。?
l?將system變量值加到內(nèi)核全局統(tǒng)計(jì)信息kstat.per_cpu_system[cpu]上。?
(5)否則,就判斷當(dāng)前CPU在服務(wù)時(shí)鐘中斷前是否處于softirq軟中斷服務(wù)的執(zhí)行中,或則正在服務(wù)一次低優(yōu)先級(jí)別的硬件中斷中。如果是這樣的話,則將system變量的值加到內(nèi)核全局統(tǒng)計(jì)信息kstat.per_cpu.system[cpu]上。?

l?update_one_process()函數(shù)?
實(shí)現(xiàn)在kernel/timer.c文件中的update_one_process()函數(shù)用來(lái)在時(shí)鐘中斷發(fā)生時(shí)更新一個(gè)進(jìn)程的task_struc結(jié)構(gòu)中的時(shí)間統(tǒng)計(jì)信息。其源碼如下(kernel/timer.c):?

void?update_one_process(struct?task_struct?*p,?unsigned?long?user,?
unsigned?long?system,?int?cpu)?
{?
p->per_cpu_utime[cpu]?+=?user;?
p->per_cpu_stime[cpu]?+=?system;?
do_process_times(p,?user,?system);?
do_it_virt(p,?user);?
do_it_prof(p);?
}?
注釋如下:?
(1)由于在一個(gè)進(jìn)程的整個(gè)生命期(Lifetime)中,它可能會(huì)在不同的CPU上執(zhí)行,也即一個(gè)進(jìn)程可能一開(kāi)始在CPU1上執(zhí)行,當(dāng)它用完在CPU1上的運(yùn)行時(shí)間片后,它可能又會(huì)被調(diào)度到CPU2上去執(zhí)行。另外,當(dāng)進(jìn)程在某個(gè)CPU上執(zhí)行時(shí),它可能又會(huì)在用戶態(tài)和內(nèi)核態(tài)下分別各執(zhí)行一段時(shí)間。所以為了統(tǒng)計(jì)這些事件信息,進(jìn)程task_struct結(jié)構(gòu)中的per_cpu_utime[NR_CPUS]數(shù)組就表示該進(jìn)程在各CPU的用戶臺(tái)下執(zhí)行的累計(jì)時(shí)間長(zhǎng)度,per_cpu_stime[NR_CPUS]數(shù)組就表示該進(jìn)程在各CPU的核心態(tài)下執(zhí)行的累計(jì)時(shí)間長(zhǎng)度;它們都以時(shí)鐘滴答次數(shù)為單位。?
所以,update_one_process()函數(shù)的第一個(gè)步驟就是更新進(jìn)程在當(dāng)前CPU上的用戶態(tài)執(zhí)行時(shí)間統(tǒng)計(jì)per_cpu_utime[cpu]和核心態(tài)執(zhí)行時(shí)間統(tǒng)計(jì)per_cpu_stime[cpu]。?
(2)調(diào)用do_process_times()函數(shù)更新當(dāng)前進(jìn)程的總時(shí)間統(tǒng)計(jì)信息。?
(3)調(diào)用do_it_virt()函數(shù)為當(dāng)前進(jìn)程的ITIMER_VIRTUAL軟件定時(shí)器更新時(shí)間間隔。?
(4)調(diào)用do_it_prof()函數(shù)為當(dāng)前進(jìn)程的ITIMER_PROF軟件定時(shí)器更新時(shí)間間隔。?

l?do_process_times()函數(shù)?
函數(shù)do_process_times()將更新指定進(jìn)程的總時(shí)間統(tǒng)計(jì)信息。每個(gè)進(jìn)程task_struct結(jié)構(gòu)中都有一個(gè)成員times,它是一個(gè)tms結(jié)構(gòu)類型(include/linux/times.h):?
struct?tms?{?
clock_t?tms_utime;?/*?本進(jìn)程在用戶臺(tái)下的執(zhí)行時(shí)間總和?*/?
clock_t?tms_stime;?/*?本進(jìn)程在核心態(tài)下的執(zhí)行時(shí)間總和?*/?
clock_t?tms_cutime;?/*?所有子進(jìn)程在用戶態(tài)下的執(zhí)行時(shí)間總和?*/?
clock_t?tms_cstime;?/*?所有子進(jìn)程在核心態(tài)下的執(zhí)行時(shí)間總和?*/?
};?
上述結(jié)構(gòu)的所有成員都以時(shí)鐘滴答次數(shù)為單位。?
函數(shù)do_process_times()的源碼如下(kernel/timer.c):?
static?inline?void?do_process_times(struct?task_struct?*p,?
unsigned?long?user,?unsigned?long?system)?
{?
unsigned?long?psecs;?

psecs?=?(p->times.tms_utime?+=?user);?
psecs?+=?(p->times.tms_stime?+=?system);?
if?(psecs?/?HZ?>?p->rlim[RLIMIT_CPU].rlim_cur)?{?
/*?Send?SIGXCPU?every?second..?*/?
if?(!(psecs?%?HZ))?
send_sig(SIGXCPU,?p,?1);?
/*?and?SIGKILL?when?we?go?over?max..?*/?
if?(psecs?/?HZ?>?p->rlim[RLIMIT_CPU].rlim_max)?
send_sig(SIGKILL,?p,?1);?
}?
}?
注釋如下:?
(1)根據(jù)參數(shù)user更新指定進(jìn)程task_struct結(jié)構(gòu)中的times.tms_utime值。根據(jù)參數(shù)system更新指定進(jìn)程task_struct結(jié)構(gòu)中的times.tms_stime值。?
(2)將更新后的times.tms_utime值與times.tms_stime值的和保存到局部變量psecs中,因此psecs就表示了指定進(jìn)程p到目前為止已經(jīng)運(yùn)行的總時(shí)間長(zhǎng)度(以時(shí)鐘滴答次數(shù)計(jì))。如果這一總運(yùn)行時(shí)間長(zhǎng)超過(guò)進(jìn)程P的資源限額,那就每隔1秒給進(jìn)程發(fā)送一個(gè)信號(hào)SIGXCPU;如果運(yùn)行時(shí)間長(zhǎng)度超過(guò)了進(jìn)程資源限額的最大值,那就發(fā)送一個(gè)SIGKILL信號(hào)殺死該進(jìn)程。?

l?do_it_virt()函數(shù)?
每個(gè)進(jìn)程都有一個(gè)用戶態(tài)執(zhí)行時(shí)間的itimer軟件定時(shí)器。進(jìn)程任務(wù)結(jié)構(gòu)task_struct中的it_virt_value成員是這個(gè)軟件定時(shí)器的時(shí)間計(jì)數(shù)器。當(dāng)進(jìn)程在用戶態(tài)下執(zhí)行時(shí),每一次時(shí)鐘滴答都使計(jì)數(shù)器it_virt_value減1,當(dāng)減到0時(shí)內(nèi)核向進(jìn)程發(fā)送SIGVTALRM信號(hào),并重置初值。初值保存在進(jìn)程的task_struct結(jié)構(gòu)的it_virt_incr成員中。?
函數(shù)do_it_virt()的源碼如下(kernel/timer.c):?
static?inline?void?do_it_virt(struct?task_struct?*?p,?unsigned?long?ticks)?
{?
unsigned?long?it_virt?=?p->it_virt_value;?

if?(it_virt)?{?
it_virt?-=?ticks;?
if?(!it_virt)?{?
it_virt?=?p->it_virt_incr;?
send_sig(SIGVTALRM,?p,?1);?
}?
p->it_virt_value?=?it_virt;?
}?
}?

l?do_it_prof()函數(shù)?
類似地,每個(gè)進(jìn)程也都有一個(gè)itimer軟件定時(shí)器ITIMER_PROF。進(jìn)程task_struct中的it_prof_value成員就是這個(gè)定時(shí)器的時(shí)間計(jì)數(shù)器。不管進(jìn)程是在用戶態(tài)下還是在內(nèi)核態(tài)下運(yùn)行,每個(gè)時(shí)鐘滴答都使it_prof_value減1。當(dāng)減到0時(shí)內(nèi)核就向進(jìn)程發(fā)送SIGPROF信號(hào),并重置初值。初值保存在進(jìn)程task_struct結(jié)構(gòu)中的it_prof_incr成員中。?
函數(shù)do_it_prof()就是用來(lái)完成上述功能的,其源碼如下(kernel/timer.c):?
static?inline?void?do_it_prof(struct?task_struct?*p)?
{?
unsigned?long?it_prof?=?p->it_prof_value;?

if?(it_prof)?{?
if?(--it_prof?==?0)?{?
it_prof?=?p->it_prof_incr;?
send_sig(SIGPROF,?p,?1);?
}?
p->it_prof_value?=?it_prof;?
}?
}


?dreamice 回復(fù)于:2008-11-06 17:57:10

7.5?時(shí)鐘中斷的Bottom?Half?
與時(shí)鐘中斷相關(guān)的Bottom?Half向兩主要有兩個(gè):TIMER_BH和TQUEUE_BH。與TIMER_BH相對(duì)應(yīng)的BH函數(shù)是timer_bh(),與TQUEUE_BH對(duì)應(yīng)的函數(shù)是tqueue_bh()。它們均實(shí)現(xiàn)在kernel/timer.c文件中。?

7.5.1?TQUEUE_BH向量?
TQUEUE_BH的作用是用來(lái)運(yùn)行tq_timer這個(gè)任務(wù)隊(duì)列中的任務(wù)。因此do_timer()函數(shù)僅僅在tq_timer任務(wù)隊(duì)列不為空的情況才激活TQUEUE_BH向量。函數(shù)tqueue_bh()的實(shí)現(xiàn)非常簡(jiǎn)單,它只是簡(jiǎn)單地調(diào)用run_task_queue()函數(shù)來(lái)運(yùn)行任務(wù)隊(duì)列tq_timer。如下所示:?
void?tqueue_bh(void)?
{?
run_task_queue(&tq_timer);?
}?
任務(wù)對(duì)列tq_timer也是定義在kernel/timer.c文件中,如下所示:?
DECLARE_TASK_QUEUE(tq_timer);?

7.5.2?TIMER_BH向量?
TIMER_BH這個(gè)Bottom?Half向量是Linux內(nèi)核時(shí)鐘中斷驅(qū)動(dòng)的一個(gè)重要輔助部分。內(nèi)核在每一次對(duì)時(shí)鐘中斷的服務(wù)快要結(jié)束時(shí),都會(huì)無(wú)條件地激活一個(gè)TIMER_BH向量,以使得內(nèi)核在稍后一段延遲后執(zhí)行相應(yīng)的BH函數(shù)——timer_bh()。該任務(wù)的源碼如下:?
void?timer_bh(void)?
{?
update_times();?
run_timer_list();?
}?
從上述源碼可以看出,內(nèi)核在時(shí)鐘中斷驅(qū)動(dòng)的底半部分主要有兩個(gè)任務(wù):(1)調(diào)用update_times()函數(shù)來(lái)更新系統(tǒng)全局時(shí)間xtime;(2)調(diào)用run_timer_list()函數(shù)來(lái)執(zhí)行定時(shí)器。關(guān)于定時(shí)器我們將在下一節(jié)討論。本節(jié)我們主要討論TIMER_BH的第一個(gè)任務(wù)——對(duì)內(nèi)核時(shí)間xtime的更新。?
我們都知道,內(nèi)核局部時(shí)間xtime是用來(lái)供用戶程序通過(guò)時(shí)間syscall來(lái)檢索或設(shè)置當(dāng)前系統(tǒng)時(shí)間的,而內(nèi)核代碼在大多數(shù)情況下都引用jiffies變量,而很少使用xtime(偶爾也會(huì)有引用xtime的情況,比如更新inode的時(shí)間標(biāo)記)。因此,對(duì)于時(shí)鐘中斷服務(wù)程序timer_interrupt()而言,jiffies變量的更新是最緊迫的,而xtime的更新則可以延遲到中斷服務(wù)的底半部分來(lái)進(jìn)行。?
由于Bottom?Half機(jī)制在執(zhí)行時(shí)間具有某些不確定性,因此在timer_bh()函數(shù)得到真正執(zhí)行之前,期間可能又會(huì)有幾次時(shí)鐘中斷發(fā)生。這樣就會(huì)造成時(shí)鐘滴答的丟失現(xiàn)象。為了處理這種情況,Linux內(nèi)核使用了一個(gè)輔助全局變量wall_jiffies,來(lái)表示上一次更新xtime時(shí)的jiffies值。其定義如下(kernel/timer.c):?
/*?jiffies?at?the?most?recent?update?of?wall?time?*/?
unsigned?long?wall_jiffies;?
而timer_bh()函數(shù)真正執(zhí)行時(shí)的jiffies值與wall_jiffies的差就是在timer_bh()真正執(zhí)行之前所發(fā)生的時(shí)鐘中斷次數(shù)。?
函數(shù)update_times()的源碼如下(kernel/timer.c):?
static?inline?void?update_times(void)?
{?
unsigned?long?ticks;?

/*?
*?update_times()?is?run?from?the?raw?timer_bh?handler?so?we?
*?just?know?that?the?irqs?are?locally?enabled?and?so?we?don't?
*?need?to?save/restore?the?flags?of?the?local?CPU?here.?-arca?
*/?
write_lock_irq(&xtime_lock);?

ticks?=?jiffies?-?wall_jiffies;?
if?(ticks)?{?
wall_jiffies?+=?ticks;?
update_wall_time(ticks);?
}?
write_unlock_irq(&xtime_lock);?
calc_load(ticks);?
}?
(1)首先,根據(jù)jiffies和wall_jiffies的差值計(jì)算在此之前一共發(fā)生了幾次時(shí)鐘滴答,并將這個(gè)值保存到局部變量ticks中。并在ticks值大于0的情況下(ticks大于等于1,一般情況下為1):①更新wall_jiffies為jiffies變量的當(dāng)前值(wall_jiffies+=ticks等價(jià)于wall_jiffies=jiffies)。②以參數(shù)ticks調(diào)用update_wall_time()函數(shù)去真正地更新全局時(shí)間xtime。?
(2)調(diào)用calc_load()函數(shù)去計(jì)算系統(tǒng)負(fù)載情況。這里我們不去深究它。?

函數(shù)update_wall_time()函數(shù)根據(jù)參數(shù)ticks所指定的時(shí)鐘滴答次數(shù)相應(yīng)地更新內(nèi)核全局時(shí)間變量xtime。其源碼如下(kernel/timer.c):?
/*?
*?Using?a?loop?looks?inefficient,?but?"ticks"?is?
*?usually?just?one?(we?shouldn't?be?losing?ticks,?
*?we're?doing?this?this?way?mainly?for?interrupt?
*?latency?reasons,?not?because?we?think?we'll?
*?have?lots?of?lost?timer?ticks?
*/?
static?void?update_wall_time(unsigned?long?ticks)?
{?
do?{?
ticks--;?
update_wall_time_one_tick();?
}?while?(ticks);?

if?(xtime.tv_usec?>=?1000000)?{?
xtime.tv_usec?-=?1000000;?
xtime.tv_sec++;?
second_overflow();?
}?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先,用一個(gè)do{}循環(huán)來(lái)根據(jù)參數(shù)ticks的值一次一次調(diào)用update_wall_time_one_tick()函數(shù)來(lái)為一次時(shí)鐘滴答更新xtime中的tv_usec成員。?
(2)根據(jù)需要調(diào)整xtime中的秒數(shù)成員tv_usec和微秒數(shù)成員tv_usec。如果微秒數(shù)成員tv_usec的值超過(guò)106,則說(shuō)明已經(jīng)過(guò)了一秒鐘。因此將tv_usec的值減去1000000,并將秒數(shù)成員tv_sec的值加1,然后調(diào)用second_overflow()函數(shù)來(lái)處理微秒數(shù)成員溢出的情況。?

函數(shù)update_wall_time_one_tick()用來(lái)更新一次時(shí)鐘滴答對(duì)系統(tǒng)全局時(shí)間xtime的影響。由于tick全局變量表示了一次時(shí)鐘滴答的時(shí)間間隔長(zhǎng)度(以u(píng)s為單位),因此該函數(shù)的實(shí)現(xiàn)中最核心的代碼就是將xtime的tv_usec成員增加tick微秒。這里我們不去關(guān)心函數(shù)實(shí)現(xiàn)中與NTP(Network?Time?Protocol)和系統(tǒng)調(diào)用adjtimex()的相關(guān)部分。其源碼如下(kernel/timer.c):?
/*?in?the?NTP?reference?this?is?called?"hardclock()"?*/?
static?void?update_wall_time_one_tick(void)?
{?
if?(?(time_adjust_step?=?time_adjust)?!=?0?)?{?
/*?We?are?doing?an?adjtime?thing.?
*?
*?Prepare?time_adjust_step?to?be?within?bounds.?
*?Note?that?a?positive?time_adjust?means?we?want?the?clock?
*?to?run?faster.?
*?
*?Limit?the?amount?of?the?step?to?be?in?the?range?
*?-tickadj?..?+tickadj?
*/?
if?(time_adjust?>?tickadj)?
time_adjust_step?=?tickadj;?
else?if?(time_adjust?<?-tickadj)?
time_adjust_step?=?-tickadj;?

/*?Reduce?by?this?step?the?amount?of?time?left?*/?
time_adjust?-=?time_adjust_step;?
}?
xtime.tv_usec?+=?tick?+?time_adjust_step;?
/*?
*?Advance?the?phase,?once?it?gets?to?one?microsecond,?then?
*?advance?the?tick?more.?
*/?
time_phase?+=?time_adj;?
if?(time_phase?<=?-FINEUSEC)?{?
long?ltemp?=?-time_phase?>>?SHIFT_SCALE;?
time_phase?+=?ltemp?<<?SHIFT_SCALE;?
xtime.tv_usec?-=?ltemp;?
}?
else?if?(time_phase?>=?FINEUSEC)?{?
long?ltemp?=?time_phase?>>?SHIFT_SCALE;?
time_phase?-=?ltemp?<<?SHIFT_SCALE;?
xtime.tv_usec?+=?ltemp;?
}?
}


?dreamice 回復(fù)于:2008-11-06 17:57:33

7.6?內(nèi)核定時(shí)器機(jī)制?
Linux內(nèi)核2.4版中去掉了老版本內(nèi)核中的靜態(tài)定時(shí)器機(jī)制,而只留下動(dòng)態(tài)定時(shí)器。相應(yīng)地在timer_bh()函數(shù)中也不再通過(guò)run_old_timers()函數(shù)來(lái)運(yùn)行老式的靜態(tài)定時(shí)器。動(dòng)態(tài)定時(shí)器與靜態(tài)定時(shí)器這二個(gè)概念是相對(duì)于Linux內(nèi)核定時(shí)器機(jī)制的可擴(kuò)展功能而言的,動(dòng)態(tài)定時(shí)器是指內(nèi)核的定時(shí)器隊(duì)列是可以動(dòng)態(tài)變化的,然而就定時(shí)器本身而言,二者并無(wú)本質(zhì)的區(qū)別。考慮到靜態(tài)定時(shí)器機(jī)制的能力有限,因此Linux內(nèi)核2.4版中完全去掉了以前的靜態(tài)定時(shí)器機(jī)制。?

7.6.1?Linux內(nèi)核對(duì)定時(shí)器的描述?
Linux在include/linux/timer.h頭文件中定義了數(shù)據(jù)結(jié)構(gòu)timer_list來(lái)描述一個(gè)內(nèi)核定時(shí)器:?
struct?timer_list?{?
struct?list_head?list;?
unsigned?long?expires;?
unsigned?long?data;?
void?(*function)(unsigned?long);?
};?
各數(shù)據(jù)成員的含義如下:?
(1)雙向鏈表元素list:用來(lái)將多個(gè)定時(shí)器連接成一條雙向循環(huán)隊(duì)列。?
(2)expires:指定定時(shí)器到期的時(shí)間,這個(gè)時(shí)間被表示成自系統(tǒng)啟動(dòng)以來(lái)的時(shí)鐘滴答計(jì)數(shù)(也即時(shí)鐘節(jié)拍數(shù))。當(dāng)一個(gè)定時(shí)器的expires值小于或等于jiffies變量時(shí),我們就說(shuō)這個(gè)定時(shí)器已經(jīng)超時(shí)或到期了。在初始化一個(gè)定時(shí)器后,通常把它的expires域設(shè)置成當(dāng)前expires變量的當(dāng)前值加上某個(gè)時(shí)間間隔值(以時(shí)鐘滴答次數(shù)計(jì))。?
(3)函數(shù)指針function:指向一個(gè)可執(zhí)行函數(shù)。當(dāng)定時(shí)器到期時(shí),內(nèi)核就執(zhí)行function所指定的函數(shù)。而data域則被內(nèi)核用作function函數(shù)的調(diào)用參數(shù)。?

內(nèi)核函數(shù)init_timer()用來(lái)初始化一個(gè)定時(shí)器。實(shí)際上,這個(gè)初始化函數(shù)僅僅將結(jié)構(gòu)中的list成員初始化為空。如下所示(include/linux/timer.h):?
static?inline?void?init_timer(struct?timer_list?*?timer)?
{?
timer->list.next?=?timer->list.prev?=?NULL;?
}?
由于定時(shí)器通常被連接在一個(gè)雙向循環(huán)隊(duì)列中等待執(zhí)行(此時(shí)我們說(shuō)定時(shí)器處于pending狀態(tài))。因此函數(shù)time_pending()就可以用list成員是否為空來(lái)判斷一個(gè)定時(shí)器是否處于pending狀態(tài)。如下所示(include/linux/timer.h):?
static?inline?int?timer_pending?(const?struct?timer_list?*?timer)?
{?
return?timer->list.next?!=?NULL;?
}?

l?時(shí)間比較操作?
在定時(shí)器應(yīng)用中經(jīng)常需要比較兩個(gè)時(shí)間值,以確定timer是否超時(shí),所以Linux內(nèi)核在timer.h頭文件中定義了4個(gè)時(shí)間關(guān)系比較操作宏。這里我們說(shuō)時(shí)刻a在時(shí)刻b之后,就意味著時(shí)間值a≥b。Linux強(qiáng)烈推薦用戶使用它所定義的下列4個(gè)時(shí)間比較操作宏(include/linux/timer.h):?
#define?time_after(a,b)?((long)(b)?-?(long)(a)?<?0)?
#define?time_before(a,b)?time_after(b,a)?

#define?time_after_eq(a,b)?((long)(a)?-?(long)(b)?>=?0)?
#define?time_before_eq(a,b)?time_after_eq(b,a)?

7.6.2?動(dòng)態(tài)內(nèi)核定時(shí)器機(jī)制的原理?
Linux是怎樣為其內(nèi)核定時(shí)器機(jī)制提供動(dòng)態(tài)擴(kuò)展能力的呢?其關(guān)鍵就在于“定時(shí)器向量”的概念。所謂“定時(shí)器向量”就是指這樣一條雙向循環(huán)定時(shí)器隊(duì)列(對(duì)列中的每一個(gè)元素都是一個(gè)timer_list結(jié)構(gòu)):對(duì)列中的所有定時(shí)器都在同一個(gè)時(shí)刻到期,也即對(duì)列中的每一個(gè)timer_list結(jié)構(gòu)都具有相同的expires值。顯然,可以用一個(gè)timer_list結(jié)構(gòu)類型的指針來(lái)表示一個(gè)定時(shí)器向量。?
顯然,定時(shí)器expires成員的值與jiffies變量的差值決定了一個(gè)定時(shí)器將在多長(zhǎng)時(shí)間后到期。在32位系統(tǒng)中,這個(gè)時(shí)間差值的最大值應(yīng)該是0xffffffff。因此如果是基于“定時(shí)器向量”基本定義,內(nèi)核將至少要維護(hù)0xffffffff個(gè)timer_list結(jié)構(gòu)類型的指針,這顯然是不現(xiàn)實(shí)的。?
另一方面,從內(nèi)核本身這個(gè)角度看,它所關(guān)心的定時(shí)器顯然不是那些已經(jīng)過(guò)期而被執(zhí)行過(guò)的定時(shí)器(這些定時(shí)器完全可以被丟棄),也不是那些要經(jīng)過(guò)很長(zhǎng)時(shí)間才會(huì)到期的定時(shí)器,而是那些當(dāng)前已經(jīng)到期或者馬上就要到期的定時(shí)器(注意!時(shí)間間隔是以滴答次數(shù)為計(jì)數(shù)單位的)。?
基于上述考慮,并假定一個(gè)定時(shí)器要經(jīng)過(guò)interval個(gè)時(shí)鐘滴答后才到期(interval=expires-jiffies),則Linux采用了下列思想來(lái)實(shí)現(xiàn)其動(dòng)態(tài)內(nèi)核定時(shí)器機(jī)制:對(duì)于那些0≤interval≤255的定時(shí)器,Linux嚴(yán)格按照定時(shí)器向量的基本語(yǔ)義來(lái)組織這些定時(shí)器,也即Linux內(nèi)核最關(guān)心那些在接下來(lái)的255個(gè)時(shí)鐘節(jié)拍內(nèi)就要到期的定時(shí)器,因此將它們按照各自不同的expires值組織成256個(gè)定時(shí)器向量。而對(duì)于那些256≤interval≤0xffffffff的定時(shí)器,由于他們離到期還有一段時(shí)間,因此內(nèi)核并不關(guān)心他們,而是將它們以一種擴(kuò)展的定時(shí)器向量語(yǔ)義(或稱為“松散的定時(shí)器向量語(yǔ)義”)進(jìn)行組織。所謂“松散的定時(shí)器向量語(yǔ)義”就是指:各定時(shí)器的expires值可以互不相同的一個(gè)定時(shí)器隊(duì)列。?
具體的組織方案可以分為兩大部分:?
(1)對(duì)于內(nèi)核最關(guān)心的、interval值在[0,255]之間的前256個(gè)定時(shí)器向量,內(nèi)核是這樣組織它們的:這256個(gè)定時(shí)器向量被組織在一起組成一個(gè)定時(shí)器向量數(shù)組,并作為數(shù)據(jù)結(jié)構(gòu)timer_vec_root的一部分,該數(shù)據(jù)結(jié)構(gòu)定義在kernel/timer.c文件中,如下述代碼段所示:?
/*?
*?Event?timer?code?
*/?
#define?TVN_BITS?6?
#define?TVR_BITS?8?
#define?TVN_SIZE?(1?<<?TVN_BITS)?
#define?TVR_SIZE?(1?<<?TVR_BITS)?
#define?TVN_MASK?(TVN_SIZE?-?1)?
#define?TVR_MASK?(TVR_SIZE?-?1)?

struct?timer_vec?{?
int?index;?
struct?list_head?vec[TVN_SIZE];?
};?

struct?timer_vec_root?{?
int?index;?
struct?list_head?vec[TVR_SIZE];?
};?

static?struct?timer_vec?tv5;?
static?struct?timer_vec?tv4;?
static?struct?timer_vec?tv3;?
static?struct?timer_vec?tv2;?
static?struct?timer_vec_root?tv1;?

static?struct?timer_vec?*?const?tvecs[]?=?{?
(struct?timer_vec?*)&tv1,?&tv2,?&tv3,?&tv4,?&tv5?
};?

#define?NOOF_TVECS?(sizeof(tvecs)?/?sizeof(tvecs[0]))?
基于數(shù)據(jù)結(jié)構(gòu)timer_vec_root,Linux定義了一個(gè)全局變量tv1,以表示內(nèi)核所關(guān)心的前256個(gè)定時(shí)器向量。這樣內(nèi)核在處理是否有到期定時(shí)器時(shí),它就只從定時(shí)器向量數(shù)組tv1.vec[256]中的某個(gè)定時(shí)器向量?jī)?nèi)進(jìn)行掃描。而tv1的index字段則指定當(dāng)前正在掃描定時(shí)器向量數(shù)組tv1.vec[256]中的哪一個(gè)定時(shí)器向量,也即該數(shù)組的索引,其初值為0,最大值為255(以256為模)。每個(gè)時(shí)鐘節(jié)拍時(shí)index字段都會(huì)加1。顯然,index字段所指定的定時(shí)器向量tv1.vec[index]中包含了當(dāng)前時(shí)鐘節(jié)拍內(nèi)已經(jīng)到期的所有動(dòng)態(tài)定時(shí)器。而定時(shí)器向量tv1.vec[index+k]則包含了接下來(lái)第k個(gè)時(shí)鐘節(jié)拍時(shí)刻將到期的所有動(dòng)態(tài)定時(shí)器。當(dāng)index值又重新變?yōu)?時(shí),就意味著內(nèi)核已經(jīng)掃描了tv1變量中的所有256個(gè)定時(shí)器向量。在這種情況下就必須將那些以松散定時(shí)器向量語(yǔ)義來(lái)組織的定時(shí)器向量補(bǔ)充到tv1中來(lái)。?
(2)而對(duì)于內(nèi)核不關(guān)心的、interval值在[0xff,0xffffffff]之間的定時(shí)器,它們的到期緊迫程度也隨其interval值的不同而不同。顯然interval值越小,定時(shí)器緊迫程度也越高。因此在將它們以松散定時(shí)器向量進(jìn)行組織時(shí)也應(yīng)該區(qū)別對(duì)待。通常,定時(shí)器的interval值越小,它所處的定時(shí)器向量的松散度也就越低(也即向量中的各定時(shí)器的expires值相差越小);而interval值越大,它所處的定時(shí)器向量的松散度也就越大(也即向量中的各定時(shí)器的expires值相差越大)。?
內(nèi)核規(guī)定,對(duì)于那些滿足條件:0x100≤interval≤0x3fff的定時(shí)器,只要表達(dá)式(interval>>8)具有相同值的定時(shí)器都將被組織在同一個(gè)松散定時(shí)器向量中。因此,為組織所有滿足條件0x100≤interval≤0x3fff的定時(shí)器,就需要26=64個(gè)松散定時(shí)器向量。同樣地,為方便起見(jiàn),這64個(gè)松散定時(shí)器向量也放在一起形成數(shù)組,并作為數(shù)據(jù)結(jié)構(gòu)timer_vec的一部分。基于數(shù)據(jù)結(jié)構(gòu)timer_vec,Linux定義了全局變量tv2,來(lái)表示這64條松散定時(shí)器向量。如上述代碼段所示。?
對(duì)于那些滿足條件0x4000≤interval≤0xfffff的定時(shí)器,只要表達(dá)式(interval>>8+6)的值相同的定時(shí)器都將被放在同一個(gè)松散定時(shí)器向量中。同樣,要組織所有滿足條件0x4000≤interval≤0xfffff的定時(shí)器,也需要26=64個(gè)松散定時(shí)器向量。類似地,這64個(gè)松散定時(shí)器向量也可以用一個(gè)timer_vec結(jié)構(gòu)來(lái)描述,相應(yīng)地Linux定義了tv3全局變量來(lái)表示這64個(gè)松散定時(shí)器向量。?
對(duì)于那些滿足條件0x100000≤interval≤0x3ffffff的定時(shí)器,只要表達(dá)式(interval>>8+6+6)的值相同的定時(shí)器都將被放在同一個(gè)松散定時(shí)器向量中。同樣,要組織所有滿足條件0x100000≤interval≤0x3ffffff的定時(shí)器,也需要26=64個(gè)松散定時(shí)器向量。類似地,這64個(gè)松散定時(shí)器向量也可以用一個(gè)timer_vec結(jié)構(gòu)來(lái)描述,相應(yīng)地Linux定義了tv4全局變量來(lái)表示這64個(gè)松散定時(shí)器向量。?
對(duì)于那些滿足條件0x4000000≤interval≤0xffffffff的定時(shí)器,只要表達(dá)式(interval>>8+6+6+6)的值相同的定時(shí)器都將被放在同一個(gè)松散定時(shí)器向量中。同樣,要組織所有滿足條件0x4000000≤interval≤0xffffffff的定時(shí)器,也需要26=64個(gè)松散定時(shí)器向量。類似地,這64個(gè)松散定時(shí)器向量也可以用一個(gè)timer_vec結(jié)構(gòu)來(lái)描述,相應(yīng)地Linux定義了tv5全局變量來(lái)表示這64個(gè)松散定時(shí)器向量。?

最后,為了引用方便,Linux定義了一個(gè)指針數(shù)組tvecs[],來(lái)分別指向tv1、tv2、…、tv5結(jié)構(gòu)變量。如上述代碼所示。?
整個(gè)內(nèi)核定時(shí)器機(jī)制的總體結(jié)構(gòu)如下圖7-8所示:?

7.6.3?內(nèi)核動(dòng)態(tài)定時(shí)器機(jī)制的實(shí)現(xiàn)?
在內(nèi)核動(dòng)態(tài)定時(shí)器機(jī)制的實(shí)現(xiàn)中,有三個(gè)操作時(shí)非常重要的:(1)將一個(gè)定時(shí)器插入到它應(yīng)該所處的定時(shí)器向量中。(2)定時(shí)器的遷移,也即將一個(gè)定時(shí)器從它原來(lái)所處的定時(shí)器向量遷移到另一個(gè)定時(shí)器向量中。(3)掃描并執(zhí)行當(dāng)前已經(jīng)到期的定時(shí)器。?

7.6.3.1?動(dòng)態(tài)定時(shí)器機(jī)制的初始化?
函數(shù)init_timervecs()實(shí)現(xiàn)對(duì)動(dòng)態(tài)定時(shí)器機(jī)制的初始化。該函數(shù)僅被sched_init()初始化例程所調(diào)用。動(dòng)態(tài)定時(shí)器機(jī)制初始化過(guò)程的主要任務(wù)就是將tv1、tv2、…、tv5這5個(gè)結(jié)構(gòu)變量中的定時(shí)器向量指針數(shù)組vec[]初始化為NULL。如下所示(kernel/timer.c):?
void?init_timervecs?(void)?
{?
int?i;?

for?(i?=?0;?i?<?TVN_SIZE;?i++)?{?
INIT_LIST_HEAD(tv5.vec?+?i);?
INIT_LIST_HEAD(tv4.vec?+?i);?
INIT_LIST_HEAD(tv3.vec?+?i);?
INIT_LIST_HEAD(tv2.vec?+?i);?
}?
for?(i?=?0;?i?<?TVR_SIZE;?i++)?
INIT_LIST_HEAD(tv1.vec?+?i);?
}?
上述函數(shù)中的宏TVN_SIZE是指timer_vec結(jié)構(gòu)類型中的定時(shí)器向量指針數(shù)組vec[]的大小,值為64。宏TVR_SIZE是指timer_vec_root結(jié)構(gòu)類型中的定時(shí)器向量數(shù)組vec[]的大小,值為256。?

7.6.3.2?動(dòng)態(tài)定時(shí)器的時(shí)鐘滴答基準(zhǔn)timer_jiffies?
由于動(dòng)態(tài)定時(shí)器是在時(shí)鐘中斷的Bottom?Half中被執(zhí)行的,而從TIMER_BH向量被激活到其timer_bh()函數(shù)真正執(zhí)行這段時(shí)間內(nèi)可能會(huì)有幾次時(shí)鐘中斷發(fā)生。因此內(nèi)核必須記住上一次運(yùn)行定時(shí)器機(jī)制是什么時(shí)候,也即內(nèi)核必須保存上一次運(yùn)行定時(shí)器機(jī)制時(shí)的jiffies值。為此,Linux在kernel/timer.c文件中定義了全局變量timer_jiffies來(lái)表示上一次運(yùn)行定時(shí)器機(jī)制時(shí)的jiffies值。該變量的定義如下所示:?
static?unsigned?long?timer_jiffies;?

7.6.3.3?對(duì)內(nèi)核動(dòng)態(tài)定時(shí)器鏈表的保護(hù)?
由于內(nèi)核動(dòng)態(tài)定時(shí)器鏈表是一種系統(tǒng)全局共享資源,為了實(shí)現(xiàn)對(duì)它的互斥訪問(wèn),Linux定義了專門(mén)的自旋鎖timerlist_lock來(lái)保護(hù)。任何想要訪問(wèn)動(dòng)態(tài)定時(shí)器鏈表的代碼段都首先必須先持有該自旋鎖,并且在訪問(wèn)結(jié)束后釋放該自旋鎖。其定義如下(kernel/timer.c):?
/*?Initialize?both?explicitly?-?let's?try?to?have?them?in?the?same?cache?line?*/?
spinlock_t?timerlist_lock?=?SPIN_LOCK_UNLOCKED;?

7.6.3.4?將一個(gè)定時(shí)器插入到鏈表中?
函數(shù)add_timer()用來(lái)將參數(shù)timer指針?biāo)赶虻亩〞r(shí)器插入到一個(gè)合適的定時(shí)器鏈表中。它首先調(diào)用timer_pending()函數(shù)判斷所指定的定時(shí)器是否已經(jīng)位于在某個(gè)定時(shí)器向量中等待執(zhí)行。如果是,則不進(jìn)行任何操作,只是打印一條內(nèi)核告警信息就返回了;如果不是,則調(diào)用internal_add_timer()函數(shù)完成實(shí)際的插入操作。其源碼如下(kernel/timer.c):?
void?add_timer(struct?timer_list?*timer)?
{?
unsigned?long?flags;?

spin_lock_irqsave(&timerlist_lock,?flags);?
if?(timer_pending(timer))?
goto?bug;?
internal_add_timer(timer);?
spin_unlock_irqrestore(&timerlist_lock,?flags);?
return;?
bug:?
spin_unlock_irqrestore(&timerlist_lock,?flags);?
printk("bug:?kernel?timer?added?twice?at?%p.\n",?
__builtin_return_address(0));?
}?

函數(shù)internal_add_timer()用于將一個(gè)不處于任何定時(shí)器向量中的定時(shí)器插入到它應(yīng)該所處的定時(shí)器向量中去(根據(jù)定時(shí)器的expires值來(lái)決定)。如下所示(kernel/timer.c):?
static?inline?void?internal_add_timer(struct?timer_list?*timer)?
{?
/*?
*?must?be?cli-ed?when?calling?this?
*/?
unsigned?long?expires?=?timer->expires;?
unsigned?long?idx?=?expires?-?timer_jiffies;?
struct?list_head?*?vec;?

if?(idx?<?TVR_SIZE)?{?
int?i?=?expires?&?TVR_MASK;?
vec?=?tv1.vec?+?i;?
}?else?if?(idx?<?1?<<?(TVR_BITS?+?TVN_BITS))?{?
int?i?=?(expires?>>?TVR_BITS)?&?TVN_MASK;?
vec?=?tv2.vec?+?i;?
}?else?if?(idx?<?1?<<?(TVR_BITS?+?2?*?TVN_BITS))?{?
int?i?=?(expires?>>?(TVR_BITS?+?TVN_BITS))?&?TVN_MASK;?
vec?=?tv3.vec?+?i;?
}?else?if?(idx?<?1?<<?(TVR_BITS?+?3?*?TVN_BITS))?{?
int?i?=?(expires?>>?(TVR_BITS?+?2?*?TVN_BITS))?&?TVN_MASK;?
vec?=?tv4.vec?+?i;?
}?else?if?((signed?long)?idx?<?0)?{?
/*?can?happen?if?you?add?a?timer?with?expires?==?jiffies,?
*?or?you?set?a?timer?to?go?off?in?the?past?
*/?
vec?=?tv1.vec?+?tv1.index;?
}?else?if?(idx?<=?0xffffffffUL)?{?
int?i?=?(expires?>>?(TVR_BITS?+?3?*?TVN_BITS))?&?TVN_MASK;?
vec?=?tv5.vec?+?i;?
}?else?{?
/*?Can?only?get?here?on?architectures?with?64-bit?jiffies?*/?
INIT_LIST_HEAD(&timer->list);?
return;?
}?
/*?
*?Timers?are?FIFO!?
*/?
list_add(&timer->list,?vec->prev);?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先,計(jì)算定時(shí)器的expires值與timer_jiffies的插值(注意!這里應(yīng)該使用動(dòng)態(tài)定時(shí)器自己的時(shí)間基準(zhǔn)),這個(gè)差值就表示這個(gè)定時(shí)器相對(duì)于上一次運(yùn)行定時(shí)器機(jī)制的那個(gè)時(shí)刻還需要多長(zhǎng)時(shí)間間隔才到期。局部變量idx保存這個(gè)差值。?
(2)根據(jù)idx的值確定這個(gè)定時(shí)器應(yīng)被插入到哪一個(gè)定時(shí)器向量中。其具體的確定方法我們?cè)?.6.2節(jié)已經(jīng)說(shuō)過(guò)了,這里不再詳述。最后,定時(shí)器向量的頭部指針vec表示這個(gè)定時(shí)器應(yīng)該所處的定時(shí)器向量鏈表頭部。?
(3)最后,調(diào)用list_add()函數(shù)將定時(shí)器插入到vec指針?biāo)赶虻亩〞r(shí)器隊(duì)列的尾部。?

7.6.3.5?修改一個(gè)定時(shí)器的expires值?
當(dāng)一個(gè)定時(shí)器已經(jīng)被插入到內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中后,我們還可以修改該定時(shí)器的expires值。函數(shù)mod_timer()實(shí)現(xiàn)這一點(diǎn)。如下所示(kernel/timer.c):?
int?mod_timer(struct?timer_list?*timer,?unsigned?long?expires)?
{?
int?ret;?
unsigned?long?flags;?

spin_lock_irqsave(&timerlist_lock,?flags);?
timer->expires?=?expires;?
ret?=?detach_timer(timer);?
internal_add_timer(timer);?
spin_unlock_irqrestore(&timerlist_lock,?flags);?
return?ret;?
}?
該函數(shù)首先根據(jù)參數(shù)expires值更新定時(shí)器的expires成員。然后調(diào)用detach_timer()函數(shù)將該定時(shí)器從它原來(lái)所屬的鏈表中刪除。最后調(diào)用internal_add_timer()函數(shù)將該定時(shí)器根據(jù)它新的expires值重新插入到相應(yīng)的鏈表中。?

函數(shù)detach_timer()首先調(diào)用timer_pending()來(lái)判斷指定的定時(shí)器是否已經(jīng)處于某個(gè)鏈表中,如果定時(shí)器原來(lái)就不處于任何鏈表中,則detach_timer()函數(shù)什么也不做,直接返回0值,表示失敗。否則,就調(diào)用list_del()函數(shù)將定時(shí)器從它原來(lái)所處的鏈表中摘除。如下所示(kernel/timer.c):?
static?inline?int?detach_timer?(struct?timer_list?*timer)?
{?
if?(!timer_pending(timer))?
return?0;?
list_del(&timer->list);?
return?1;?
}?

7.6.3.6?刪除一個(gè)定時(shí)器?
函數(shù)del_timer()用來(lái)將一個(gè)定時(shí)器從相應(yīng)的內(nèi)核定時(shí)器隊(duì)列中刪除。該函數(shù)實(shí)際上是對(duì)detach_timer()函數(shù)的高層封裝。如下所示(kernel/timer.c):?
int?del_timer(struct?timer_list?*?timer)?
{?
int?ret;?
unsigned?long?flags;?

spin_lock_irqsave(&timerlist_lock,?flags);?
ret?=?detach_timer(timer);?
timer->list.next?=?timer->list.prev?=?NULL;?
spin_unlock_irqrestore(&timerlist_lock,?flags);?
return?ret;?
}?

7.6.3.7?定時(shí)器遷移操作?
由于一個(gè)定時(shí)器的interval值會(huì)隨著時(shí)間的不斷流逝(即jiffies值的不斷增大)而不斷變小,因此那些原本到期緊迫程度較低的定時(shí)器會(huì)隨著jiffies值的不斷增大而成為既將馬上到期的定時(shí)器。比如定時(shí)器向量tv2.vec[0]中的定時(shí)器在經(jīng)過(guò)256個(gè)時(shí)鐘滴答后會(huì)成為未來(lái)256個(gè)時(shí)鐘滴答內(nèi)會(huì)到期的定時(shí)器。因此,定時(shí)器在內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中的位置也應(yīng)相應(yīng)地隨著改變。改變的規(guī)則是:當(dāng)tv1.index重新變?yōu)?時(shí)(意味著tv1中的256個(gè)定時(shí)器向量都已被內(nèi)核掃描一遍了,從而使tv1中的256個(gè)定時(shí)器向量變?yōu)榭?#xff09;,則用tv2.vec[index]定時(shí)器向量中的定時(shí)器去填充tv1,同時(shí)使tv2.index加1(它以64為模)。當(dāng)tv2.index重新變?yōu)?(意味著tv2中的64個(gè)定時(shí)器向量都已經(jīng)被全部填充到tv1中去了,從而使得tv2變?yōu)榭?#xff09;,則用tv3.vec[index]定時(shí)器向量中的定時(shí)器去填充tv2。如此一直類推下去,直到tv5。?
函數(shù)cascade_timers()完成這種定時(shí)器遷移操作,該函數(shù)只有一個(gè)timer_vec結(jié)構(gòu)類型指針的參數(shù)tv。這個(gè)函數(shù)將把定時(shí)器向量tv->vec[tv->index]中的所有定時(shí)器重新填充到上一層定時(shí)器向量中去。如下所示(kernel/timer.c):?
static?inline?void?cascade_timers(struct?timer_vec?*tv)?
{?
/*?cascade?all?the?timers?from?tv?up?one?level?*/?
struct?list_head?*head,?*curr,?*next;?

head?=?tv->vec?+?tv->index;?
curr?=?head->next;?
/*?
*?We?are?removing?_all_?timers?from?the?list,?so?we?don't?have?to?
*?detach?them?individually,?just?clear?the?list?afterwards.?
*/?
while?(curr?!=?head)?{?
struct?timer_list?*tmp;?

tmp?=?list_entry(curr,?struct?timer_list,?list);?
next?=?curr->next;?
list_del(curr);?//?not?needed?
internal_add_timer(tmp);?
curr?=?next;?
}?
INIT_LIST_HEAD(head);?
tv->index?=?(tv->index?+?1)?&?TVN_MASK;?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先,用指針head指向定時(shí)器頭部向量頭部的list_head結(jié)構(gòu)。指針curr指向定時(shí)器向量中的第一個(gè)定時(shí)器。?
(2)然后,用一個(gè)while{}循環(huán)來(lái)遍歷定時(shí)器向量tv->vec[tv->index]。由于定時(shí)器向量是一個(gè)雙向循環(huán)隊(duì)列,因此循環(huán)的終止條件是curr=head。對(duì)于每一個(gè)被掃描的定時(shí)器,循環(huán)體都先調(diào)用list_del()函數(shù)將當(dāng)前定時(shí)器從鏈表中摘除,然后調(diào)用internal_add_timer()函數(shù)重新確定該定時(shí)器應(yīng)該被放到哪個(gè)定時(shí)器向量中去。?
(3)當(dāng)從while{}循環(huán)退出后,定時(shí)器向量tv->vec[tv->index]中所有的定時(shí)器都已被遷移到其它地方(到它們?cè)摯舻牡胤?#xff1a;-),因此它本身就成為一個(gè)空隊(duì)列。這里我們顯示地調(diào)用INIT_LIST_HEAD()宏來(lái)將定時(shí)器向量的表頭結(jié)構(gòu)初始化為空。?
(4)最后,將tv->index值加1,當(dāng)然它是以64為模。?

7.6.4.8?掃描并執(zhí)行當(dāng)前已經(jīng)到期的定時(shí)器?
函數(shù)run_timer_list()完成這個(gè)功能。如前所述,該函數(shù)是被timer_bh()函數(shù)所調(diào)用的,因此內(nèi)核定時(shí)器是在時(shí)鐘中斷的Bottom?Half中被執(zhí)行的。記住這一點(diǎn)非常重要。全局變量timer_jiffies表示了內(nèi)核上一次執(zhí)行run_timer_list()函數(shù)的時(shí)間,因此jiffies與timer_jiffies的差值就表示了自從上一次處理定時(shí)器以來(lái),期間一共發(fā)生了多少次時(shí)鐘中斷,顯然run_timer_list()函數(shù)必須為期間所發(fā)生的每一次時(shí)鐘中斷補(bǔ)上定時(shí)器服務(wù)。該函數(shù)的源碼如下(kernel/timer.c):?
static?inline?void?run_timer_list(void)?
{?
spin_lock_irq(&timerlist_lock);?
while?((long)(jiffies?-?timer_jiffies)?>=?0)?{?
struct?list_head?*head,?*curr;?
if?(!tv1.index)?{?
int?n?=?1;?
do?{?
cascade_timers(tvecs[n]);?
}?while?(tvecs[n]->index?==?1?&&?++n?<?NOOF_TVECS);?
}?
repeat:?
head?=?tv1.vec?+?tv1.index;?
curr?=?head->next;?
if?(curr?!=?head)?{?
struct?timer_list?*timer;?
void?(*fn)(unsigned?long);?
unsigned?long?data;?

timer?=?list_entry(curr,?struct?timer_list,?list);?
fn?=?timer->function;?
data=?timer->data;?

detach_timer(timer);?
timer->list.next?=?timer->list.prev?=?NULL;?
timer_enter(timer);?
spin_unlock_irq(&timerlist_lock);?
fn(data);?
spin_lock_irq(&timerlist_lock);?
timer_exit();?
goto?repeat;?
}?
++timer_jiffies;?
tv1.index?=?(tv1.index?+?1)?&?TVR_MASK;?
}?
spin_unlock_irq(&timerlist_lock);?
}?
函數(shù)run_timer_list()的執(zhí)行過(guò)程主要就是用一個(gè)大while{}循環(huán)來(lái)為時(shí)鐘中斷執(zhí)行定時(shí)器服務(wù),每一次循環(huán)服務(wù)一次時(shí)鐘中斷。因此一共要執(zhí)行(jiffies-timer_jiffies+1)次循環(huán)。循環(huán)體所執(zhí)行的服務(wù)步驟如下:?
(1)首先,判斷tv1.index是否為0,如果為0則需要從tv2中補(bǔ)充定時(shí)器到tv1中來(lái)。但tv2也可能為空而需要從tv3中補(bǔ)充定時(shí)器,因此用一個(gè)do{}while循環(huán)來(lái)調(diào)用cascade_timer()函數(shù)來(lái)依次視需要從tv2中補(bǔ)充tv1,從tv3中補(bǔ)充tv2、…、從tv5中補(bǔ)充tv4。顯然如果tvi.index=0(2≤i≤5),則對(duì)于tvi執(zhí)行cascade_timers()函數(shù)后,tvi.index肯定為1。反過(guò)來(lái)講,如果對(duì)tvi執(zhí)行過(guò)cascade_timers()函數(shù)后tvi.index不等于1,那么可以肯定在未對(duì)tvi執(zhí)行cascade_timers()函數(shù)之前,tvi.index值肯定不為0,因此這時(shí)tvi不需要從tv(i+1)中補(bǔ)充定時(shí)器,這時(shí)就可以終止do{}while循環(huán)。?
(2)接下來(lái),就要執(zhí)行定時(shí)器向量tv1.vec[tv1.index]中的所有到期定時(shí)器。因此這里用一個(gè)goto?repeat循環(huán)從頭到尾依次掃描整個(gè)定時(shí)器對(duì)列。由于在執(zhí)行定時(shí)器的關(guān)聯(lián)函數(shù)時(shí)并不需要關(guān)CPU中斷,所以在用detach_timer()函數(shù)將當(dāng)前定時(shí)器從對(duì)列中摘除后,就可以調(diào)用spin_unlock_irq()函數(shù)進(jìn)行解鎖和開(kāi)中斷,然后在執(zhí)行完當(dāng)前定時(shí)器的關(guān)聯(lián)函數(shù)后重新用spin_lock_irq()函數(shù)加鎖和關(guān)中斷。?
(3)當(dāng)執(zhí)行完定時(shí)器向量tv1.vec[tv1.index]中的所有到期定時(shí)器后,tv1.vec[tv1.index]應(yīng)該是個(gè)空隊(duì)列。至此這一次定時(shí)器服務(wù)也就宣告結(jié)束。?
(4)最后,將timer_jiffies值加1,將tv1.index值加1,當(dāng)然它的模是256。然后,回到while循環(huán)開(kāi)始下一次定時(shí)器服務(wù)。


?dreamice 回復(fù)于:2008-11-06 17:57:51

7.7?進(jìn)程間隔定時(shí)器itimer?
所謂“間隔定時(shí)器(Interval?Timer,簡(jiǎn)稱itimer)就是指定時(shí)器采用“間隔”值(interval)來(lái)作為計(jì)時(shí)方式,當(dāng)定時(shí)器啟動(dòng)后,間隔值interval將不斷減小。當(dāng)interval值減到0時(shí),我們就說(shuō)該間隔定時(shí)器到期。與上一節(jié)所說(shuō)的內(nèi)核動(dòng)態(tài)定時(shí)器相比,二者最大的區(qū)別在于定時(shí)器的計(jì)時(shí)方式不同。內(nèi)核定時(shí)器是通過(guò)它的到期時(shí)刻expires值來(lái)計(jì)時(shí)的,當(dāng)全局變量jiffies值大于或等于內(nèi)核動(dòng)態(tài)定時(shí)器的expires值時(shí),我們說(shuō)內(nèi)核內(nèi)核定時(shí)器到期。而間隔定時(shí)器則實(shí)際上是通過(guò)一個(gè)不斷減小的計(jì)數(shù)器來(lái)計(jì)時(shí)的。雖然這兩種定時(shí)器并不相同,但卻也是相互聯(lián)系的。假如我們每個(gè)時(shí)鐘節(jié)拍都使間隔定時(shí)器的間隔計(jì)數(shù)器減1,那么在這種情形下間隔定時(shí)器實(shí)際上就是內(nèi)核動(dòng)態(tài)定時(shí)器(下面我們會(huì)看到進(jìn)程的真實(shí)間隔定時(shí)器就是這樣通過(guò)內(nèi)核定時(shí)器來(lái)實(shí)現(xiàn)的)。?
間隔定時(shí)器主要被應(yīng)用在用戶進(jìn)程上。每個(gè)Linux進(jìn)程都有三個(gè)相互關(guān)聯(lián)的間隔定時(shí)器。其各自的間隔計(jì)數(shù)器都定義在進(jìn)程的task_struct結(jié)構(gòu)中,如下所示(include/linux/sched.h):?
struct?task_struct{?
……?
unsigned?long?it_real_value,?it_prof_value,?it_virt_value;?
unsigned?long?it_real_incr,?it_prof_incr,?it_virt_incr;?
struct?timer_list?real_timer;?
……?
}?
(1)真實(shí)間隔定時(shí)器(ITIMER_REAL):這種間隔定時(shí)器在啟動(dòng)后,不管進(jìn)程是否運(yùn)行,每個(gè)時(shí)鐘滴答都將其間隔計(jì)數(shù)器減1。當(dāng)減到0值時(shí),內(nèi)核向進(jìn)程發(fā)送SIGALRM信號(hào)。結(jié)構(gòu)類型task_struct中的成員it_real_incr則表示真實(shí)間隔定時(shí)器的間隔計(jì)數(shù)器的初始值,而成員it_real_value則表示真實(shí)間隔定時(shí)器的間隔計(jì)數(shù)器的當(dāng)前值。由于這種間隔定時(shí)器本質(zhì)上與上一節(jié)的內(nèi)核定時(shí)器時(shí)一樣的,因此Linux實(shí)際上是通過(guò)real_timer這個(gè)內(nèi)嵌在task_struct結(jié)構(gòu)中的內(nèi)核動(dòng)態(tài)定時(shí)器來(lái)實(shí)現(xiàn)真實(shí)間隔定時(shí)器ITIMER_REAL的。?
(2)虛擬間隔定時(shí)器ITIMER_VIRT:也稱為進(jìn)程的用戶態(tài)間隔定時(shí)器。結(jié)構(gòu)類型task_struct中成員it_virt_incr和it_virt_value分別表示虛擬間隔定時(shí)器的間隔計(jì)數(shù)器的初始值和當(dāng)前值,二者均以時(shí)鐘滴答次數(shù)位計(jì)數(shù)單位。當(dāng)虛擬間隔定時(shí)器啟動(dòng)后,只有當(dāng)進(jìn)程在用戶態(tài)下運(yùn)行時(shí),一次時(shí)鐘滴答才能使間隔計(jì)數(shù)器當(dāng)前值it_virt_value減1。當(dāng)減到0值時(shí),內(nèi)核向進(jìn)程發(fā)送SIGVTALRM信號(hào)(虛擬鬧鐘信號(hào)),并將it_virt_value重置為初值it_virt_incr。具體請(qǐng)見(jiàn)7.4.3節(jié)中的do_it_virt()函數(shù)的實(shí)現(xiàn)。?
(3)PROF間隔定時(shí)器ITIMER_PROF:進(jìn)程的task_struct結(jié)構(gòu)中的it_prof_value和it_prof_incr成員分別表示PROF間隔定時(shí)器的間隔計(jì)數(shù)器的當(dāng)前值和初始值(均以時(shí)鐘滴答為單位)。當(dāng)一個(gè)進(jìn)程的PROF間隔定時(shí)器啟動(dòng)后,則只要該進(jìn)程處于運(yùn)行中,而不管是在用戶態(tài)或核心態(tài)下執(zhí)行,每個(gè)時(shí)鐘滴答都使間隔計(jì)數(shù)器it_prof_value值減1。當(dāng)減到0值時(shí),內(nèi)核向進(jìn)程發(fā)送SIGPROF信號(hào),并將it_prof_value重置為初值it_prof_incr。具體請(qǐng)見(jiàn)7.4.3節(jié)的do_it_prof()函數(shù)。?
Linux在include/linux/time.h頭文件中為上述三種進(jìn)程間隔定時(shí)器定義了索引標(biāo)識(shí),如下所示:?
#define?ITIMER_REAL?0?
#define?ITIMER_VIRTUAL?1?
#define?ITIMER_PROF?2?

7.7.1?數(shù)據(jù)結(jié)構(gòu)itimerval?
雖然,在內(nèi)核中間隔定時(shí)器的間隔計(jì)數(shù)器是以時(shí)鐘滴答次數(shù)為單位,但是讓用戶以時(shí)鐘滴答為單位來(lái)指定間隔定時(shí)器的間隔計(jì)數(shù)器的初值顯然是不太方便的,因?yàn)橛脩袅?xí)慣的時(shí)間單位是秒、毫秒或微秒等。所以Linux定義了數(shù)據(jù)結(jié)構(gòu)itimerval來(lái)讓用戶以秒或微秒為單位指定間隔定時(shí)器的時(shí)間間隔值。其定義如下(include/linux/time.h):?
struct?itimerval?{?
struct?timeval?it_interval;?/*?timer?interval?*/?
struct?timeval?it_value;?/*?current?value?*/?
};?
其中,it_interval成員表示間隔計(jì)數(shù)器的初始值,而it_value成員表示間隔計(jì)數(shù)器的當(dāng)前值。這兩個(gè)成員都是timeval結(jié)構(gòu)類型的變量,因此其精度可以達(dá)到微秒級(jí)。?

l?timeval與jiffies之間的相互轉(zhuǎn)換?
由于間隔定時(shí)器的間隔計(jì)數(shù)器的內(nèi)部表示方式與外部表現(xiàn)方式互不相同,因此有必要實(shí)現(xiàn)以微秒為單位的timeval結(jié)構(gòu)和為時(shí)鐘滴答次數(shù)單位的jiffies之間的相互轉(zhuǎn)換。為此,Linux在kernel/itimer.c中實(shí)現(xiàn)了兩個(gè)函數(shù)實(shí)現(xiàn)二者的互相轉(zhuǎn)換——tvtojiffies()函數(shù)和jiffiestotv()函數(shù)。它們的源碼如下:?
static?unsigned?long?tvtojiffies(struct?timeval?*value)?
{?
unsigned?long?sec?=?(unsigned)?value->tv_sec;?
unsigned?long?usec?=?(unsigned)?value->tv_usec;?

if?(sec?>?(ULONG_MAX?/?HZ))?
return?ULONG_MAX;?
usec?+=?1000000?/?HZ?-?1;?
usec?/=?1000000?/?HZ;?
return?HZ*sec+usec;?
}?

static?void?jiffiestotv(unsigned?long?jiffies,?struct?timeval?*value)?
{?
value->tv_usec?=?(jiffies?%?HZ)?*?(1000000?/?HZ);?
value->tv_sec?=?jiffies?/?HZ;?
}?

7.7.2?真實(shí)間隔定時(shí)器ITIMER_REAL的底層運(yùn)行機(jī)制?
間隔定時(shí)器ITIMER_VIRT和ITIMER_PROF的底層運(yùn)行機(jī)制是分別通過(guò)函數(shù)do_it_virt()函數(shù)和do_it_prof()函數(shù)來(lái)實(shí)現(xiàn)的,這里就不再重述(可以參見(jiàn)7.4.3節(jié))。?
由于間隔定時(shí)器ITIMER_REAL本質(zhì)上與內(nèi)核動(dòng)態(tài)定時(shí)器并無(wú)區(qū)別。因此內(nèi)核實(shí)際上是通過(guò)內(nèi)核動(dòng)態(tài)定時(shí)器來(lái)實(shí)現(xiàn)進(jìn)程的ITIMER_REAL間隔定時(shí)器的。為此,task_struct結(jié)構(gòu)中專門(mén)設(shè)立一個(gè)timer_list結(jié)構(gòu)類型的成員變量real_timer。動(dòng)態(tài)定時(shí)器real_timer的函數(shù)指針function總是被task_struct結(jié)構(gòu)的初始化宏INIT_TASK設(shè)置為指向函數(shù)it_real_fn()。如下所示(include/linux/sched.h):?
#define?INIT_TASK(tsk)?\?
……?
real_timer:?{?
function:?it_real_fn?\?
}?\?
……?
}?
而real_timer鏈表元素list和data成員總是被進(jìn)程創(chuàng)建時(shí)分別初始化為空和進(jìn)程task_struct結(jié)構(gòu)的地址,如下所示(kernel/fork.c):?
int?do_fork(……)?
{?
……?
p->it_real_value?=?p->it_virt_value?=?p->it_prof_value?=?0;?
p->it_real_incr?=?p->it_virt_incr?=?p->it_prof_incr?=?0;?
init_timer(&p->real_timer);?
p->real_timer.data?=?(unsigned?long)p;?
……?
}?
當(dāng)用戶通過(guò)setitimer()系統(tǒng)調(diào)用來(lái)設(shè)置進(jìn)程的ITIMER_REAL間隔定時(shí)器時(shí),it_real_incr被設(shè)置成非零值,于是該系統(tǒng)調(diào)用相應(yīng)地設(shè)置好real_timer.expires值,然后進(jìn)程的real_timer定時(shí)器就被加入到內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中,這樣該進(jìn)程的ITIMER_REAL間隔定時(shí)器就被啟動(dòng)了。當(dāng)real_timer定時(shí)器到期時(shí),它的關(guān)聯(lián)函數(shù)it_real_fn()將被執(zhí)行。注意!所有進(jìn)程的real_timer定時(shí)器的function函數(shù)指針都指向it_real_fn()這同一個(gè)函數(shù),因此it_real_fn()函數(shù)必須通過(guò)其參數(shù)來(lái)識(shí)別是哪一個(gè)進(jìn)程,為此它將unsigned?long類型的參數(shù)p解釋為進(jìn)程task_struct結(jié)構(gòu)的地址。該函數(shù)的源碼如下(kernel/itimer.c):?
void?it_real_fn(unsigned?long?__data)?
{?
struct?task_struct?*?p?=?(struct?task_struct?*)?__data;?
unsigned?long?interval;?

send_sig(SIGALRM,?p,?1);?
interval?=?p->it_real_incr;?
if?(interval)?{?
if?(interval?>?(unsigned?long)?LONG_MAX)?
interval?=?LONG_MAX;?
p->real_timer.expires?=?jiffies?+?interval;?
add_timer(&p->real_timer);?
}?
}?
函數(shù)it_real_fn()的執(zhí)行過(guò)程大致如下:?
(1)首先將參數(shù)p通過(guò)強(qiáng)制類型轉(zhuǎn)換解釋為進(jìn)程的task_struct結(jié)構(gòu)類型的指針。?
(2)向進(jìn)程發(fā)送SIGALRM信號(hào)。?
(3)在進(jìn)程的it_real_incr非0的情況下繼續(xù)啟動(dòng)real_timer定時(shí)器。首先,計(jì)算real_timer定時(shí)器的expires值為(jiffies+it_real_incr)。然后,調(diào)用add_timer()函數(shù)將real_timer加入到內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中。?

7.7.3?itimer定時(shí)器的系統(tǒng)調(diào)用?
與itimer定時(shí)器相關(guān)的syscall有兩個(gè):getitimer()和setitimer()。其中,getitimer()用于查詢調(diào)用進(jìn)程的三個(gè)間隔定時(shí)器的信息,而setitimer()則用來(lái)設(shè)置調(diào)用進(jìn)程的三個(gè)間隔定時(shí)器。這兩個(gè)syscall都是現(xiàn)在kernel/itimer.c文件中。?

7.7.3.1?getitimer()系統(tǒng)調(diào)用的實(shí)現(xiàn)?
函數(shù)sys_getitimer()有兩個(gè)參數(shù):(1)which,指定查詢調(diào)用進(jìn)程的哪一個(gè)間隔定時(shí)器,其取值可以是ITIMER_REAL、ITIMER_VIRT和ITIMER_PROF三者之一。(2)value指針,指向用戶空間中的一個(gè)itimerval結(jié)構(gòu),用于接收查詢結(jié)果。該函數(shù)的源碼如下:?
/*?SMP:?Only?we?modify?our?itimer?values.?*/?
asmlinkage?long?sys_getitimer(int?which,?struct?itimerval?*value)?
{?
int?error?=?-EFAULT;?
struct?itimerval?get_buffer;?

if?(value)?{?
error?=?do_getitimer(which,?&get_buffer);?
if?(!error?&&?
copy_to_user(value,?&get_buffer,?sizeof(get_buffer)))?
error?=?-EFAULT;?
}?
return?error;?
}?
顯然,sys_getitimer()函數(shù)主要通過(guò)do_getitimer()函數(shù)來(lái)查詢當(dāng)前進(jìn)程的間隔定時(shí)器信息,并將查詢結(jié)果保存在內(nèi)核空間的結(jié)構(gòu)變量get_buffer中。然后,調(diào)用copy_to_usr()宏將get_buffer中結(jié)果拷貝到用戶空間緩沖區(qū)中。?
函數(shù)do_getitimer()的源碼如下(kernel/itimer.c):?
int?do_getitimer(int?which,?struct?itimerval?*value)?
{?
register?unsigned?long?val,?interval;?

switch?(which)?{?
case?ITIMER_REAL:?
interval?=?current->it_real_incr;?
val?=?0;?
/*?
*?FIXME!?This?needs?to?be?atomic,?in?case?the?kernel?timer?happens!?
*/?
if?(timer_pending(&current->real_timer))?{?
val?=?current->real_timer.expires?-?jiffies;?

/*?look?out?for?negative/zero?itimer..?*/?
if?((long)?val?<=?0)?
val?=?1;?
}?
break;?
case?ITIMER_VIRTUAL:?
val?=?current->it_virt_value;?
interval?=?current->it_virt_incr;?
break;?
case?ITIMER_PROF:?
val?=?current->it_prof_value;?
interval?=?current->it_prof_incr;?
break;?
default:?
return(-EINVAL);?
}?
jiffiestotv(val,?&value->it_value);?
jiffiestotv(interval,?&value->it_interval);?
return?0;?
}?
查詢的過(guò)程如下:?
(1)首先,用局部變量val和interval分別表示待查詢間隔定時(shí)器的間隔計(jì)數(shù)器的當(dāng)前值和初始值。?
(2)如果which=ITIMER_REAL,則查詢當(dāng)前進(jìn)程的ITIMER_REAL間隔定時(shí)器。于是從current->it_real_incr中得到ITIMER_REAL間隔定時(shí)器的間隔計(jì)數(shù)器的初始值,并將其保存到interval局部變量中。而對(duì)于間隔計(jì)數(shù)器的當(dāng)前值,由于ITITMER_REAL間隔定時(shí)器是通過(guò)real_timer這個(gè)內(nèi)核動(dòng)態(tài)定時(shí)器來(lái)實(shí)現(xiàn)的,因此不能通過(guò)current->it_real_value來(lái)獲得ITIMER_REAL間隔定時(shí)器的間隔計(jì)數(shù)器的當(dāng)前值,而必須通過(guò)real_timer來(lái)得到這個(gè)值。為此先用timer_pending()函數(shù)來(lái)判斷current->real_timer是否已被起動(dòng)。如果未啟動(dòng),則說(shuō)明ITIMER_REAL間隔定時(shí)器也未啟動(dòng),因此其間隔計(jì)數(shù)器的當(dāng)前值肯定是0。因此將val變量簡(jiǎn)單地置0就可以了。如果已經(jīng)啟動(dòng),則間隔計(jì)數(shù)器的當(dāng)前值應(yīng)該等于(timer_real.expires-jiffies)。?
(3)如果which=ITIMER_VIRT,則查詢當(dāng)前進(jìn)程的ITIMER_VIRT間隔定時(shí)器。于是簡(jiǎn)單地將計(jì)數(shù)器初值it_virt_incr和當(dāng)前值it_virt_value分別保存到局部變量interval和val中。?
(4)如果which=ITIMER_PROF,則查詢當(dāng)前進(jìn)程的ITIMER_PROF間隔定時(shí)器。于是簡(jiǎn)單地將計(jì)數(shù)器初值it_prof_incr和當(dāng)前值it_prof_value分別保存到局部變量interval和val中。?
(5)最后,通過(guò)轉(zhuǎn)換函數(shù)jiffiestotv()將val和interval轉(zhuǎn)換成timeval格式的時(shí)間值,并保存到value->it_value和value->it_interval中,作為查詢結(jié)果返回。?

7.7.3.2?setitimer()系統(tǒng)調(diào)用的實(shí)現(xiàn)?
函數(shù)sys_setitimer()不僅設(shè)置調(diào)用進(jìn)程的指定間隔定時(shí)器,而且還返回該間隔定時(shí)器的原有信息。它有三個(gè)參數(shù):(1)which,含義與sys_getitimer()中的參數(shù)相同。(2)輸入?yún)?shù)value,指向用戶空間中的一個(gè)itimerval結(jié)構(gòu),含有待設(shè)置的新值。(3)輸出參數(shù)ovalue,指向用戶空間中的一個(gè)itimerval結(jié)構(gòu),用于接收間隔定時(shí)器的原有信息。?
該函數(shù)的源碼如下(kernel/itimer.c):?
/*?SMP:?Again,?only?we?play?with?our?itimers,?and?signals?are?SMP?safe?
*?now?so?that?is?not?an?issue?at?all?anymore.?
*/?
asmlinkage?long?sys_setitimer(int?which,?struct?itimerval?*value,?
struct?itimerval?*ovalue)?
{?
struct?itimerval?set_buffer,?get_buffer;?
int?error;?

if?(value)?{?
if(copy_from_user(&set_buffer,?value,?sizeof(set_buffer)))?
return?-EFAULT;?
}?else?
memset((char?*)?&set_buffer,?0,?sizeof(set_buffer));?

error?=?do_setitimer(which,?&set_buffer,?ovalue???&get_buffer?:?0);?
if?(error?||?!ovalue)?
return?error;?

if?(copy_to_user(ovalue,?&get_buffer,?sizeof(get_buffer)))?
return?-EFAULT;?
return?0;?
}?
對(duì)該函數(shù)的注釋如下:?
(1)在輸入?yún)?shù)指針value非空的情況下,調(diào)用copy_from_user()宏將用戶空間中的待設(shè)置信息拷貝到內(nèi)核空間中的set_buffer結(jié)構(gòu)變量中。如果value指針為空,則簡(jiǎn)單地將set_buffer結(jié)構(gòu)變量全部置0。?
(2)調(diào)用do_setitimer()函數(shù)完成實(shí)際的設(shè)置操作。如果輸出參數(shù)ovalue指針有效,則以內(nèi)核變量get_buffer的地址作為do_setitimer()函數(shù)的第三那個(gè)調(diào)用參數(shù),這樣當(dāng)do_setitimer()函數(shù)返回時(shí),get_buffer結(jié)構(gòu)變量中就將含有當(dāng)前進(jìn)程的指定間隔定時(shí)器的原來(lái)信息。Do_setitimer()函數(shù)返回0值表示成功,非0值表示失敗。?
(3)在do_setitimer()函數(shù)返回非0值的情況下,或者ovalue指針為空的情況下(不需要輸出間隔定時(shí)器的原有信息),函數(shù)就可以直接返回了。?
(4)如果ovalue指針?lè)强?#xff0c;調(diào)用copy_to_user()宏將get_buffer()結(jié)構(gòu)變量中值拷貝到ovalue所指向的用戶空間中去,以便讓用戶得到指定間隔定時(shí)器的原有信息值。?

函數(shù)do_setitimer()的源碼如下(kernel/itimer.c):?
int?do_setitimer(int?which,?struct?itimerval?*value,?struct?itimerval?*ovalue)?
{?
register?unsigned?long?i,?j;?
int?k;?

i?=?tvtojiffies(&value->it_interval);?
j?=?tvtojiffies(&value->it_value);?
if?(ovalue?&&?(k?=?do_getitimer(which,?ovalue))?<?0)?
return?k;?
switch?(which)?{?
case?ITIMER_REAL:?
del_timer_sync(&current->real_timer);?
current->it_real_value?=?j;?
current->it_real_incr?=?i;?
if?(!j)?
break;?
if?(j?>?(unsigned?long)?LONG_MAX)?
j?=?LONG_MAX;?
i?=?j?+?jiffies;?
current->real_timer.expires?=?i;?
add_timer(&current->real_timer);?
break;?
case?ITIMER_VIRTUAL:?
if?(j)?
j++;?
current->it_virt_value?=?j;?
current->it_virt_incr?=?i;?
break;?
case?ITIMER_PROF:?
if?(j)?
j++;?
current->it_prof_value?=?j;?
current->it_prof_incr?=?i;?
break;?
default:?
return?-EINVAL;?
}?
return?0;?
}?
對(duì)該函數(shù)的注釋如下:?
(1)首先調(diào)用tvtojiffies()函數(shù)將timeval格式的初始值和當(dāng)前值轉(zhuǎn)換成以時(shí)鐘滴答為單位的時(shí)間值。并分別保存在局部變量i和j中。?
(2)如果ovalue指針?lè)强?#xff0c;則調(diào)用do_getitimer()函數(shù)查詢指定間隔定時(shí)器的原來(lái)信息。如果do_getitimer()函數(shù)返回負(fù)值,說(shuō)明出錯(cuò)。因此就要直接返回錯(cuò)誤值。否則繼續(xù)向下執(zhí)行開(kāi)始真正地設(shè)置指定的間隔定時(shí)器。?
(3)如果which=ITITMER_REAL,表示設(shè)置ITIMER_REAL間隔定時(shí)器。(a)調(diào)用del_timer_sync()函數(shù)(該函數(shù)在單CPU系統(tǒng)中就是del_timer()函數(shù))將當(dāng)前進(jìn)程的real_timer定時(shí)器從內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中刪除。(b)將it_real_incr和it_real_value分別設(shè)置為局部變量i和j。(c)如果j=0,說(shuō)明不必啟動(dòng)real_timer定時(shí)器,因此執(zhí)行break語(yǔ)句退出switch…case控制結(jié)構(gòu),而直接返回。(d)將real_timer的expires成員設(shè)置成(jiffies+當(dāng)前值j),然后調(diào)用add_timer()函數(shù)將當(dāng)前進(jìn)程的real_timer定時(shí)器加入到內(nèi)核動(dòng)態(tài)定時(shí)器鏈表中,從而啟動(dòng)該定時(shí)器。?
(4)如果which=ITIMER_VIRT,則簡(jiǎn)單地用局部變量i和j的值分別更新it_virt_incr和it_virt_value就可以了。?
(5)如果which=ITIMER_PROF,則簡(jiǎn)單地用局部變量i和j的值分別更新it_prof_incr和it_prof_value就可以了。?
(6)最后,返回0值表示成功。?

7.7.3.3?alarm系統(tǒng)調(diào)用?
系統(tǒng)調(diào)用alarm可以讓調(diào)用進(jìn)程在指定的秒數(shù)間隔后收到一個(gè)SIGALRM信號(hào)。它只有一個(gè)參數(shù)seconds,指定以秒數(shù)計(jì)的定時(shí)間隔。函數(shù)sys_alarm()的源碼如下(kernel/timer.c):?
/*?
*?For?backwards?compatibility??This?can?be?done?in?libc?so?Alpha?
*?and?all?newer?ports?shouldn't?need?it.?
*/?
asmlinkage?unsigned?long?sys_alarm(unsigned?int?seconds)?
{?
struct?itimerval?it_new,?it_old;?
unsigned?int?oldalarm;?

it_new.it_interval.tv_sec?=?it_new.it_interval.tv_usec?=?0;?
it_new.it_value.tv_sec?=?seconds;?
it_new.it_value.tv_usec?=?0;?
do_setitimer(ITIMER_REAL,?&it_new,?&it_old);?
oldalarm?=?it_old.it_value.tv_sec;?
/*?ehhh..?We?can't?return?0?if?we?have?an?alarm?pending..?*/?
/*?And?we'd?better?return?too?much?than?too?little?anyway?*/?
if?(it_old.it_value.tv_usec)?
oldalarm++;?
return?oldalarm;?
}?
這個(gè)系統(tǒng)調(diào)用實(shí)際上就是啟動(dòng)進(jìn)程的ITIMER_REAL間隔定時(shí)器。因此它完全可放到用戶空間的C函數(shù)庫(kù)(比如libc和glibc)中來(lái)實(shí)現(xiàn)。但是為了保此內(nèi)核的向后兼容性,2.4.0版的內(nèi)核仍然將這個(gè)syscall放在內(nèi)核空間中來(lái)實(shí)現(xiàn)。函數(shù)sys_alarm()的實(shí)現(xiàn)過(guò)程如下:?
(1)根據(jù)參數(shù)seconds的值構(gòu)造一個(gè)itimerval結(jié)構(gòu)變量it_new。注意!由于alarm啟動(dòng)的ITIMER_REAL間隔定時(shí)器是一次性而不是循環(huán)重復(fù)的,因此it_new變量中的it_interval成員一定要設(shè)置為0。?
(2)調(diào)用函數(shù)do_setitimer()函數(shù)以新構(gòu)造的定時(shí)器it_new來(lái)啟動(dòng)當(dāng)前進(jìn)程的ITIMER_REAL定時(shí)器,同時(shí)將該間隔定時(shí)器的原定時(shí)間隔保存到局部變量it_old中。?
(3)返回值oldalarm表示以秒數(shù)計(jì)的ITIMER_REAL間隔定時(shí)器的原定時(shí)間隔值。因此先把it_old.it_value.tv_sec賦給oldalarm,并且在it_old.it_value.tv_usec非0的情況下,將oldalarm的值加1(也即不足1秒補(bǔ)足1秒)。


?dreamice 回復(fù)于:2008-11-06 17:58:13

7.8?時(shí)間系統(tǒng)調(diào)用的實(shí)現(xiàn)?
本節(jié)講述與時(shí)間相關(guān)的syscall,這些系統(tǒng)調(diào)用主要用來(lái)供用戶進(jìn)程向內(nèi)核檢索當(dāng)前時(shí)間與日期,因此他們是內(nèi)核的時(shí)間服務(wù)接口。主要的時(shí)間系統(tǒng)調(diào)用共有5個(gè):time、stime和gettimeofday、settimeofday,以及與網(wǎng)絡(luò)時(shí)間協(xié)議NTP相關(guān)的adjtimex系統(tǒng)調(diào)用。這里我們不關(guān)心NTP,因此僅分析前4個(gè)時(shí)間系統(tǒng)調(diào)用。前4個(gè)時(shí)間系統(tǒng)調(diào)用可以分為兩組:(1)time和stime是一組;(2)gettimeofday和settimeofday是一組。?

7.8.1?系統(tǒng)調(diào)用time和stime?
系統(tǒng)調(diào)用time()用于獲取以秒數(shù)表示的系統(tǒng)當(dāng)前時(shí)間(即內(nèi)核全局時(shí)間變量xtime中的tv_sec成員的值)。它只有一個(gè)參數(shù)——整型指針tloc,指向用戶空間中的一個(gè)整數(shù),用來(lái)接收返回的當(dāng)前時(shí)間值。函數(shù)sys_time()的源碼如下(kernel/time.c):?
asmlinkage?long?sys_time(int?*?tloc)?
{?
int?i;?

/*?SMP:?This?is?fairly?trivial.?We?grab?CURRENT_TIME?and?
stuff?it?to?user?space.?No?side?effects?*/?
i?=?CURRENT_TIME;?
if?(tloc)?{?
if?(put_user(i,tloc))?
i?=?-EFAULT;?
}?
return?i;?
}?
注釋如下:?
(1)首先,函數(shù)調(diào)用CURRENT_TIME宏來(lái)得到以秒數(shù)表示的內(nèi)核當(dāng)前時(shí)間值,并將該值保存在局部變量i中。宏CURRENT_TIME定義在include/linux/sched.h頭文件中,它實(shí)際上就是內(nèi)核全局時(shí)間變量xtime中的tv_sec成員。如下所示:?
#define?CURRENT_TIME?(xtime.tv_sec)?
(2)然后,在參數(shù)指針tloc非空的情況下將i的值通過(guò)put_user()宏傳遞到有tloc所指向的用戶空間中去,以作為函數(shù)的輸出結(jié)果。?
(3)最后,將局部變量I的值——也即也秒數(shù)表示的系統(tǒng)當(dāng)前時(shí)間值作為返回值返回。?

系統(tǒng)調(diào)用stime()與系統(tǒng)調(diào)用time()剛好相反,它可以讓用戶設(shè)置系統(tǒng)的當(dāng)前時(shí)間(以秒數(shù)為單位)。它同樣也只有一個(gè)參數(shù)——整型指針tptr,指向用戶空間中待設(shè)置的時(shí)間秒數(shù)值。函數(shù)sys_stime()的源碼如下(kernel/time.c):?
asmlinkage?long?sys_stime(int?*?tptr)?
{?
int?value;?

if?(!capable(CAP_SYS_TIME))?
return?-EPERM;?
if?(get_user(value,?tptr))?
return?-EFAULT;?
write_lock_irq(&xtime_lock);?
xtime.tv_sec?=?value;?
xtime.tv_usec?=?0;?
time_adjust?=?0;?/*?stop?active?adjtime()?*/?
time_status?|=?STA_UNSYNC;?
time_maxerror?=?NTP_PHASE_LIMIT;?
time_esterror?=?NTP_PHASE_LIMIT;?
write_unlock_irq(&xtime_lock);?
return?0;?
}?
注釋如下:?
(1)首先檢查調(diào)用進(jìn)程的權(quán)限,顯然,只有root用戶才能有權(quán)限修改系統(tǒng)時(shí)間。?
(2)調(diào)用get_user()宏將tptr指針?biāo)赶虻挠脩艨臻g中的時(shí)間秒數(shù)值拷貝到內(nèi)核空間中來(lái),并保存到局部變量value中。?
(3)將局部變量value的值更新到全局時(shí)間變量xtime的tv_sec成員中,并將xtime的tv_usec成員清零。?
(4)在相應(yīng)地重置其它狀態(tài)變量后,函數(shù)就可以返回了(返回值0表示成功)。?

7.8.2?系統(tǒng)調(diào)用gettimeofday?
這個(gè)syscall用來(lái)供用戶獲取timeval格式的當(dāng)前時(shí)間信息(精確度為微秒級(jí)),以及系統(tǒng)的當(dāng)前時(shí)區(qū)信息(timezone)。結(jié)構(gòu)類型timeval的指針參數(shù)tv指向接受時(shí)間信息的用戶空間緩沖區(qū),參數(shù)tz是一個(gè)timezone結(jié)構(gòu)類型的指針,指向接收時(shí)區(qū)信息的用戶空間緩沖區(qū)。這兩個(gè)參數(shù)均為輸出參數(shù),返回值0表示成功,返回負(fù)值表示出錯(cuò)。函數(shù)sys_gettimeofday()的源碼如下(kernel/time.c):?
asmlinkage?long?sys_gettimeofday(struct?timeval?*tv,?struct?timezone?*tz)?
{?
if?(tv)?{?
struct?timeval?ktv;?
do_gettimeofday(&ktv);?
if?(copy_to_user(tv,?&ktv,?sizeof(ktv)))?
return?-EFAULT;?
}?
if?(tz)?{?
if?(copy_to_user(tz,?&sys_tz,?sizeof(sys_tz)))?
return?-EFAULT;?
}?
return?0;?
}?
顯然,函數(shù)的實(shí)現(xiàn)主要分成兩個(gè)大的方面:?
(1)如果tv指針有效,則說(shuō)明用戶要以timeval格式來(lái)檢索系統(tǒng)當(dāng)前時(shí)間。為此,先調(diào)用do_gettimeofday()函數(shù)來(lái)檢索系統(tǒng)當(dāng)前時(shí)間并保存到局部變量ktv中。然后再調(diào)用copy_to_user()宏將保存在內(nèi)核空間中的當(dāng)前時(shí)間信息拷貝到由參數(shù)指針tv所指向的用戶空間緩沖區(qū)中。?
(2)如果tz指針有效,則說(shuō)明用戶要檢索當(dāng)前時(shí)區(qū)信息,因此調(diào)用copy_to_user()宏將全局變量sys_tz中的時(shí)區(qū)信息拷貝到參數(shù)指針tz所指向的用戶空間緩沖區(qū)中。?
(3)最后,返回0表示成功。?

函數(shù)do_gettimeofday()的源碼如下(arch/i386/kernel/time.c):?
/*?
*?This?version?of?gettimeofday?has?microsecond?resolution?
*?and?better?than?microsecond?precision?on?fast?x86?machines?with?TSC.?
*/?
void?do_gettimeofday(struct?timeval?*tv)?
{?
unsigned?long?flags;?
unsigned?long?usec,?sec;?

read_lock_irqsave(&xtime_lock,?flags);?
usec?=?do_gettimeoffset();?
{?
unsigned?long?lost?=?jiffies?-?wall_jiffies;?
if?(lost)?
usec?+=?lost?*?(1000000?/?HZ);?
}?
sec?=?xtime.tv_sec;?
usec?+=?xtime.tv_usec;?
read_unlock_irqrestore(&xtime_lock,?flags);?

while?(usec?>=?1000000)?{?
usec?-=?1000000;?
sec++;?
}?

tv->tv_sec?=?sec;?
tv->tv_usec?=?usec;?
}?
該函數(shù)的完成實(shí)際的當(dāng)前時(shí)間檢索工作。由于gettimeofday()系統(tǒng)調(diào)用要求時(shí)間精度要達(dá)到微秒級(jí),因此do_gettimeofday()函數(shù)不能簡(jiǎn)單地返回xtime中的值即可,而必須精確地確定自從時(shí)鐘驅(qū)動(dòng)的Bottom?Half上一次更新xtime的那個(gè)時(shí)刻(由wall_jiffies變量表示,參見(jiàn)7.3節(jié))到do_gettimeofday()函數(shù)的當(dāng)前執(zhí)行時(shí)刻之間的具體時(shí)間間隔長(zhǎng)度,以便精確地修正xtime的值.如下圖7-9所示:?
假定被do_gettimeofday()用來(lái)修正xtime的時(shí)間間隔為fixed_usec,而從wall_jiffies到j(luò)iffies之間的時(shí)間間隔是lost_usec,而從jiffies到do_gettimeofday()函數(shù)的執(zhí)行時(shí)刻的時(shí)間間隔是offset_usec。則下列三個(gè)等式成立:?
fixed_usec=(lost_usec+offset_usec)?
lost_usec=(jiffies-wall_jiffies)*TICK_SIZE=(jiffies-wall_jiffies)*(1000000/HZ)?
由于全局變量last_tsc_low表示上一次時(shí)鐘中斷服務(wù)函數(shù)timer_interrupt()執(zhí)行時(shí)刻的CPU?TSC寄存器的值,因此我們可以用X86?CPU的TSC寄存器來(lái)計(jì)算offset_usec的值。也即:?
offset_usec=delay_at_last_interrupt+(current_tsc_low-last_tsc_low)*fast_gettimeoffset_quotient?
其中,delay_at_last_interrupt是從上一次發(fā)生時(shí)鐘中斷到timer_interrupt()服務(wù)函數(shù)真正執(zhí)行時(shí)刻之間的時(shí)間延遲間隔。每一次timer_interrupt()被執(zhí)行時(shí)都會(huì)計(jì)算這一間隔,并利用TSC的當(dāng)前值更新last_tsc_low變量(可以參見(jiàn)7.4節(jié))。假定current_tsc_low是do_gettimeofday()函數(shù)執(zhí)行時(shí)刻TSC的當(dāng)前值,全局變量fast_gettimeoffset_quotient則表示TSC寄存器每增加1所代表的時(shí)間間隔值,它是由time_init()函數(shù)所計(jì)算的。?
根據(jù)上述原理分析,do_gettimeofday()函數(shù)的執(zhí)行步驟如下:?
(1)調(diào)用函數(shù)do_gettimeoffset()計(jì)算從上一次時(shí)鐘中斷發(fā)生到執(zhí)行do_gettimeofday()函數(shù)的當(dāng)前時(shí)刻之間的時(shí)間間隔offset_usec。?
(2)通過(guò)wall_jiffies和jiffies計(jì)算lost_usec的值。?
(3)然后,令sec=xtime.tv_sec,usec=xtime.tv_usec+lost_usec+offset_usec。顯然,sec表示系統(tǒng)當(dāng)前時(shí)間在秒數(shù)量級(jí)上的值,而usec表示系統(tǒng)當(dāng)前時(shí)間在微秒量級(jí)上的值。?
(4)用一個(gè)while{}循環(huán)來(lái)判斷usec是否已經(jīng)溢出而超過(guò)106us=1秒。如果溢出,則將usec減去106us并相應(yīng)地將sec增加1,直到usec不溢出為止。?
(5)最后,用sec和usec分別更新參數(shù)指針?biāo)赶虻膖imeval結(jié)構(gòu)變量。至此,整個(gè)查詢過(guò)程結(jié)束。?

函數(shù)do_gettimeoffset()根據(jù)CPU是否配置有TSC寄存器這一條件分別有不同的實(shí)現(xiàn)。其定義如下(arch/i386/kernel/time.c):?
#ifndef?CONFIG_X86_TSC?
static?unsigned?long?do_slow_gettimeoffset(void)?
{?
……?
}?
static?unsigned?long?(*do_gettimeoffset)(void)?=?do_slow_gettimeoffset;?
#else?
#define?do_gettimeoffset()?do_fast_gettimeoffset()?
#endif?
顯然,在配置有TSC寄存器的i386平臺(tái)上,do_gettimeoffset()函數(shù)實(shí)際上就是do_fast_gettimeoffset()函數(shù)。它通過(guò)TSC寄存器來(lái)計(jì)算do_fast_gettimeoffset()函數(shù)被執(zhí)行的時(shí)刻到上一次時(shí)鐘中斷發(fā)生時(shí)的時(shí)間間隔值。其源碼如下(arch/i386/kernel/time.c):?
static?inline?unsigned?long?do_fast_gettimeoffset(void)?
{?
register?unsigned?long?eax,?edx;?

/*?Read?the?Time?Stamp?Counter?*/?

rdtsc(eax,edx);?

/*?..?relative?to?previous?jiffy?(32?bits?is?enough)?*/?
eax?-=?last_tsc_low;?/*?tsc_low?delta?*/?

/*?
*?Time?offset?=?(tsc_low?delta)?*?fast_gettimeoffset_quotient?
*?=?(tsc_low?delta)?*?(usecs_per_clock)?
*?=?(tsc_low?delta)?*?(usecs_per_jiffy?/?clocks_per_jiffy)?
*?
*?Using?a?mull?instead?of?a?divl?saves?up?to?31?clock?cycles?
*?in?the?critical?path.?
*/?

__asm__("mull?%2"?
:"=a"?(eax),?"=d"?(edx)?
:"rm"?(fast_gettimeoffset_quotient),?
"0"?(eax));?

/*?our?adjusted?time?offset?in?microseconds?*/?
return?delay_at_last_interrupt?+?edx;?
}?
對(duì)該函數(shù)的注釋如下:?
(1)先調(diào)用rdtsc()函數(shù)讀取當(dāng)前時(shí)刻TSC寄存器的值,并將其高32位保存在edx局部變量中,低32位保存在局部變量eax中。?
(2)讓局部變量eax=Δtsc_low=eax-last_tsc_low;也即計(jì)算當(dāng)前時(shí)刻的TSC值與上一次時(shí)鐘中斷服務(wù)函數(shù)timer_interrupt()執(zhí)行時(shí)的TSC值之間的差值。?
(3)顯然,從上一次timer_interrupt()到當(dāng)前時(shí)刻的時(shí)間間隔就是(Δtsc_low*fast_gettimeoffset_quotient)。因此用一條mul指令來(lái)計(jì)算這個(gè)乘法表達(dá)式的值。?
(4)返回值delay_at_last_interrupt+(Δtsc_low*fast_gettimeoffset_quotient)就是從上一次時(shí)鐘中斷發(fā)生時(shí)到當(dāng)前時(shí)刻之間的時(shí)間偏移間隔值。?

7.8.3?系統(tǒng)調(diào)用settimeofday?
這個(gè)系統(tǒng)調(diào)用與gettimeofday()剛好相反,它供用戶設(shè)置當(dāng)前時(shí)間以及當(dāng)前時(shí)間信息。它也有兩個(gè)參數(shù):(1)參數(shù)指針tv,指向含有待設(shè)置時(shí)間信息的用戶空間緩沖區(qū);(2)參數(shù)指針tz,指向含有待設(shè)置時(shí)區(qū)信息的用戶空間緩沖區(qū)。函數(shù)sys_settimeofday()的源碼如下(kernel/time.c):?
asmlinkage?long?sys_settimeofday(struct?timeval?*tv,?struct?timezone?*tz)?
{?
struct?timeval?new_tv;?
struct?timezone?new_tz;?

if?(tv)?{?
if?(copy_from_user(&new_tv,?tv,?sizeof(*tv)))?
return?-EFAULT;?
}?
if?(tz)?{?
if?(copy_from_user(&new_tz,?tz,?sizeof(*tz)))?
return?-EFAULT;?
}?

return?do_sys_settimeofday(tv???&new_tv?:?NULL,?tz???&new_tz?:?NULL);?
}?
函數(shù)首先調(diào)用copy_from_user()宏將保存在用戶空間中的待設(shè)置時(shí)間信息和時(shí)區(qū)信息拷貝到內(nèi)核空間中來(lái),并保存到局部變量new_tv和new_tz中。然后,調(diào)用do_sys_settimeofday()函數(shù)完成實(shí)際的時(shí)間設(shè)置和時(shí)區(qū)設(shè)置操作。?
函數(shù)do_sys_settimeofday()的源碼如下(kernel/time.c):?
int?do_sys_settimeofday(struct?timeval?*tv,?struct?timezone?*tz)?
{?
static?int?firsttime?=?1;?

if?(!capable(CAP_SYS_TIME))?
return?-EPERM;?

if?(tz)?{?
/*?SMP?safe,?global?irq?locking?makes?it?work.?*/?
sys_tz?=?*tz;?
if?(firsttime)?{?
firsttime?=?0;?
if?(!tv)?
warp_clock();?
}?
}?
if?(tv)?
{?
/*?SMP?safe,?again?the?code?in?arch/foo/time.c?should?
*?globally?block?out?interrupts?when?it?runs.?
*/?
do_settimeofday(tv);?
}?
return?0;?
}?
該函數(shù)的執(zhí)行過(guò)程如下:?
(1)首先,檢查調(diào)用進(jìn)程是否有相應(yīng)的權(quán)限。如果沒(méi)有,則返回錯(cuò)誤值-EPERM。?
(2)如果執(zhí)政tz有效,則用tz所指向的新時(shí)區(qū)信息更新全局變量sys_tz。并且如果是第一次設(shè)置時(shí)區(qū)信息,則在tv指針不為空的情況下調(diào)用wrap_clock()函數(shù)來(lái)調(diào)整xtime中的秒數(shù)值。函數(shù)wrap_clock()的源碼如下(kernel/time.c):?
inline?static?void?warp_clock(void)?
{?
write_lock_irq(&xtime_lock);?
xtime.tv_sec?+=?sys_tz.tz_minuteswest?*?60;?
write_unlock_irq(&xtime_lock);?
}?
(3)如果參數(shù)tv指針有效,則根據(jù)tv所指向的新時(shí)間信息調(diào)用do_settimeofday()函數(shù)來(lái)更新內(nèi)核的當(dāng)前時(shí)間xtime。?
(4)最后,返回0值表示成功。?
函數(shù)do_settimeofday()執(zhí)行剛好與do_gettimeofday()相反的操作。這是因?yàn)槿肿兞縳time所表示的時(shí)間是與wall_jiffies相對(duì)應(yīng)的那一個(gè)時(shí)刻。因此,必須從參數(shù)指針tv所指向的新時(shí)間中減去時(shí)間間隔fixed_usec(其含義見(jiàn)7.8.2節(jié))。函數(shù)源碼如下(arch/i386/kernel/time.c):?
void?do_settimeofday(struct?timeval?*tv)?
{?
write_lock_irq(&xtime_lock);?
/*?
*?This?is?revolting.?We?need?to?set?"xtime"?correctly.?However,?the?
*?value?in?this?location?is?the?value?at?the?most?recent?update?of?
*?wall?time.?Discover?what?correction?gettimeofday()?would?have?
*?made,?and?then?undo?it!?
*/?
tv->tv_usec?-=?do_gettimeoffset();?
tv->tv_usec?-=?(jiffies?-?wall_jiffies)?*?(1000000?/?HZ);?

while?(tv->tv_usec?<?0)?{?
tv->tv_usec?+=?1000000;?
tv->tv_sec--;?
}?

xtime?=?*tv;?
time_adjust?=?0;?/*?stop?active?adjtime()?*/?
time_status?|=?STA_UNSYNC;?
time_maxerror?=?NTP_PHASE_LIMIT;?
time_esterror?=?NTP_PHASE_LIMIT;?
write_unlock_irq(&xtime_lock);?
}?
該函數(shù)的執(zhí)行步驟如下:?
(1)調(diào)用do_gettimeoffset()函數(shù)計(jì)算上一次時(shí)鐘中斷發(fā)生時(shí)刻到當(dāng)前時(shí)刻之間的時(shí)間間隔值。?
(2)通過(guò)wall_jiffies與jiffies計(jì)算二者之間的時(shí)間間隔lost_usec。?
(3)從tv->tv_usec中減去fixed_usec,即:tv->tv_usec-=(lost_usec+offset_usec)。?
(4)用一個(gè)while{}循環(huán)根據(jù)tv->tv_usec是否小于0來(lái)調(diào)整tv結(jié)構(gòu)變量。如果tv->tv_usec小于0,則將tv->tv_usec加上106us,并相應(yīng)地將tv->tv_sec減1。直到tv->tv_usec不小于0為止。?
(5)用修正后的時(shí)間tv來(lái)更新內(nèi)核全局時(shí)間變量xtime。?
(6)最后,重置其它時(shí)間狀態(tài)變量。?

至此,我們已經(jīng)完全分析了整個(gè)Linux內(nèi)核的時(shí)鐘機(jī)制!

總結(jié)

以上是生活随笔為你收集整理的Linux内核的时钟中断的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

日韩在线观看小视频 | 日韩av一区二区在线 | 国产亚洲精品久久久久久大师 | 久久免费中文视频 | 中文字幕乱码亚洲精品一区 | 国产日韩精品一区二区三区 | 99在线视频网站 | 日韩在线 | 九九精品视频在线观看 | 91人人射 | 黄色片毛片 | 欧洲亚洲国产视频 | 成年人黄色免费网站 | 四虎影视成人永久免费观看亚洲欧美 | 婷婷久久丁香 | 日韩午夜三级 | 91在线精品秘密一区二区 | 999成人 | av一区在线 | 精品国产亚洲在线 | 日日干天天爽 | 免费电影一区二区三区 | 色综合久久久久 | 亚洲国产日韩欧美在线 | 天堂网在线视频 | 91资源在线 | 国产69熟 | 亚洲欧美日韩精品一区二区 | 国产一区二区三区免费视频 | 国产美女免费观看 | 久久精品欧美一区二区三区麻豆 | 美女视频黄免费的 | 91在线视频播放 | 久久九九久久 | 92精品国产成人观看免费 | 激情六月婷婷久久 | 五月婷婷毛片 | 免费黄色av电影 | 97人人模人人爽人人少妇 | 国产精品九九热 | 99久热在线精品视频 | 美女在线观看网站 | 91精品麻豆 | 在线播放视频一区 | www.av免费| 日韩在线免费视频观看 | 国产免费亚洲 | av免费在线观看1 | 亚洲免费观看视频 | 91在线看片| 免费日韩 | 国产一区二区精品久久91 | 久久国产综合视频 | 欧美一级日韩三级 | 丁香一区二区 | 久久久久国产成人精品亚洲午夜 | 91丨九色丨蝌蚪丨对白 | 免费在线观看的av网站 | 成人久久久久久久久久 | 日本公妇色中文字幕 | 日韩高清免费无专码区 | 中文字幕专区高清在线观看 | 人人要人人澡人人爽人人dvd | 最近的中文字幕大全免费版 | 亚洲97在线 | 高清av中文字幕 | 免费麻豆视频 | 国产一区观看 | 亚洲精选久久 | 激情伊人五月天 | 成人午夜电影在线观看 | 综合网伊人 | 97在线成人| 国产在线毛片 | 欧美精品久久久 | av成人在线看 | 久久激情视频免费观看 | 啪啪资源 | 久久精品国产亚洲 | 探花视频在线观看免费 | 日韩有码欧美 | 久久福利| 在线播放 一区 | 在线观看日韩精品 | 欧洲视频一区 | 911亚洲精品第一 | 亚洲电影一区二区 | 麻豆91精品| 黄色三级视频片 | 少妇啪啪av入口 | 午夜久久久精品 | 国产成人久久av免费高清密臂 | 色夜影院| 欧美一级片在线播放 | 99热.com| 亚洲精品乱码久久久久久高潮 | 在线黄色国产 | 亚洲精品国产精品国自产观看 | 欧美精品免费一区二区 | 成年人网站免费在线观看 | 中文字幕亚洲综合久久五月天色无吗'' | 久久久免费精品 | 91高清在线看 | 国内成人精品2018免费看 | 亚洲一区二区精品3399 | 色综合天天天天做夜夜夜夜做 | 一区二区欧美日韩 | 日韩av三区 | 天海冀一区二区三区 | 国内视频在线观看 | 久久久久久高清 | 国产精品成人一区二区 | 国产护士av | 玖玖在线视频观看 | 怡春院av | 国产精品igao视频网网址 | 国产精品免费观看国产网曝瓜 | 久草在线视频资源 | 天天草网站 | 中文字幕色站 | 国产在线观看免费av | 高清在线一区二区 | 97精品视频在线 | 色综合久久久久网 | 狠狠操欧美| 精品国产视频一区 | 久草在线视频国产 | 免费91麻豆精品国产自产在线观看 | 成人18视频 | 国产精品18久久久 | 午夜精品一区二区三区视频免费看 | 国产精品 日韩精品 | 菠萝菠萝蜜在线播放 | 国产精品福利在线播放 | 草在线视频 | 国产精品免费不卡 | 亚洲高清网站 | 黄网站免费大全入口 | 久久久久久久免费 | 亚洲视频1区2区 | 亚洲欧美日韩国产精品一区午夜 | 草久视频在线 | 高清av网站 | 在线看片a| 亚洲国产免费网站 | 精品在线你懂的 | 成人网看片 | 久久久久久99精品 | 国产毛片久久久 | 精品国产伦一区二区三区观看说明 | 亚洲精品字幕在线 | 9999精品免费视频 | 精品国产欧美一区二区三区不卡 | 欧美久久99 | 伊人色综合久久天天网 | 69xxxx欧美 | 人人射av | 欧美日韩免费一区二区三区 | 激情综合一区 | 久久精品网址 | 国内久久视频 | 日韩高清二区 | 97精品国产91久久久久久久 | 91视视频在线直接观看在线看网页在线看 | 国产98色在线 | 日韩 | 国产群p| 免费一区在线 | 久久婷婷色综合 | 亚洲精品成人在线 | 国产一区成人 | 开心激情久久 | 国产高清精品在线 | 欧美日韩视频在线播放 | 在线免费观看国产黄色 | 日韩在线观看你懂得 | 在线观看不卡的av | 欧美极品裸体 | 国产免费亚洲 | 国内精品久久久久久久久 | 青青网视频| 成人午夜在线观看 | 在线成人欧美 | 久久ww | 久久福利在线 | 激情欧美一区二区免费视频 | 久久人人爽人人片 | 美女av电影 | 91av原创 | 中文字幕欧美日韩va免费视频 | 黄色三级网站 | 狠狠干2018| 成人在线播放av | 日日夜夜骑 | 99热 精品在线 | 亚洲免费激情 | 婷婷在线色| 国产一区二区手机在线观看 | www.久久99 | 欧美三级在线播放 | 深爱婷婷网| 成年人黄色免费看 | 97视频在线免费观看 | 久艹视频在线免费观看 | 亚洲永久免费av | 欧美 日韩 国产 成人 在线 | 久久久久久久久久网 | 91大神精品视频在线观看 | 一区二区三区观看 | av三级在线播放 | 国产视频在线观看一区 | 亚洲国产成人精品久久 | 国产伦理一区二区三区 | 免费av免费观看 | 国产亚洲成人网 | 在线天堂中文www视软件 | 天天激情综合网 | 欧美日韩久久一区 | 国产黄色大片 | 国内丰满少妇猛烈精品播放 | 亚洲永久精品视频 | 久久国语露脸国产精品电影 | 黄色天堂在线观看 | 久久综合加勒比 | 久久精品这里精品 | 久久精品男人的天堂 | 国产黄色片久久久 | 久草免费资源 | 国产一区在线视频观看 | 国产黄av| www.超碰97.com | 91九色丨porny丨丰满6 | 蜜桃麻豆www久久囤产精品 | 亚洲一级电影在线观看 | 国产日产精品一区二区三区四区 | 亚洲精品国产拍在线 | 在线观看视频一区二区三区 | 色婷婷激情四射 | 91正在播放 | 欧美在线视频免费 | 香蕉视频在线免费 | 国产精品成人a免费观看 | 久久人人爽av | 亚洲一区二区三区四区精品 | 男女拍拍免费视频 | 国产18精品乱码免费看 | 国产亚洲一级高清 | 九九视频免费在线观看 | 免费观看完整版无人区 | 天天操天天艹 | 亚州人成在线播放 | 中文字幕a在线 | 日韩久久精品一区二区 | av大全免费在线观看 | 成人免费在线网 | 日韩欧在线 | 色综合久久中文综合久久牛 | 最近中文字幕完整视频高清1 | 五月婷婷中文网 | 99久久99久久精品国产片果冰 | 999久久久久久久久久久 | 欧美一级小视频 | 久章草在线观看 | 最新99热 | 国产黄色精品在线 | 成人a视频在线观看 | 婷婷丁香狠狠爱 | 激情av综合| 色综久久 | 美国人与动物xxxx | 久久久免费 | 91中文在线观看 | 97精品一区二区三区 | 亚洲精品午夜一区人人爽 | 欧美一二三视频 | 在线观看片 | 欧美人人| 国产小视频精品 | 精品无人国产偷自产在线 | www黄色| 91在线资源 | 免费观看久久 | 夜夜躁狠狠燥 | 在线播放 一区 | 视频精品一区二区三区 | 99在线免费视频 | 国产精品永久免费观看 | 色婷婷成人网 | 日韩三级中文字幕 | 最近中文字幕高清字幕免费mv | 国产成人在线观看 | 久久久久中文 | 欧美性生活小视频 | 一区二区视频播放 | 在线 国产一区 | 久久97精品| 国产成人精品电影久久久 | 久久精品直播 | 狠狠狠色丁香婷婷综合久久五月 | 日韩中文在线字幕 | 国产视频在线观看免费 | 少妇性bbb搡bbb爽爽爽欧美 | 天天综合亚洲 | 婷婷综合影院 | 五月婷婷激情网 | 天天干.com | 成人a级网站 | 国产成人a亚洲精品 | 国产黄a三级三级 | 国产福利在线 | 久久免费在线观看 | 日本视频网 | 天天操人人干 | 一区二区三区视频在线 | 精品国产综合区久久久久久 | 福利片视频区 | 91视频 - 114av | 亚洲精品人人 | 九九热.com| 91福利国产在线观看 | 91av短视频 | 亚洲精品资源 | 美女免费视频网站 | 国产一级片毛片 | 精品国产乱码久久久久久久 | 国产91勾搭技师精品 | 91片黄在线观看动漫 | 国产一区二区在线看 | 久久无码av一区二区三区电影网 | 日韩成人精品一区二区 | 在线观看中文字幕2021 | 国产精品成人aaaaa网站 | 久久久久久久久亚洲精品 | 亚洲天天在线 | 亚洲精品在线一区二区 | 色综合天天视频在线观看 | 久久精品国产v日韩v亚洲 | 久久久毛片 | 在线观看视频黄 | 精品久久久国产 | 亚洲国产三级 | 91av网址 | 久久99亚洲精品久久 | 成人网在线免费视频 | 日本中文一级片 | 中文字幕免费播放 | 日韩精品一区二区三区免费观看视频 | 久久久久高清 | 日本激情动作片免费看 | 人人看人人爱 | 国产精品一区免费在线观看 | 国产成人免费高清 | 91九色蝌蚪在线 | 日韩av电影国产 | 国产视频精品久久 | 久久精品综合一区 | 色综合久久久久 | 91桃色在线免费观看 | 国产高清视频在线免费观看 | 99视频| 国产精品一区二区久久精品 | 久久人人做 | 成人免费视频在线观看 | 日韩电影中文字幕 | av在线中文 | 伊人久久精品久久亚洲一区 | 99c视频高清免费观看 | 一区二区三区在线影院 | 国产精品男女视频 | 日本大片免费观看在线 | 欧美精品xx| 狠狠色狠狠色合久久伊人 | 一区二区三区四区精品 | 中文亚洲欧美日韩 | 夜色成人av| 欧美999| 中文字幕精品一区二区三区电影 | 天天操夜夜叫 | 欧美成人h版电影 | 四虎国产精品免费观看视频优播 | 色久综合 | 久久久免费少妇 | 欧美一二三视频 | 黄色在线成人 | 日韩精品亚洲专区在线观看 | 国产精品不卡视频 | 一色av | 美女视频永久黄网站免费观看国产 | 久久综合五月天 | 欧美成人基地 | 久草在线免费新视频 | 亚洲最大在线视频 | 国产午夜精品一区二区三区在线观看 | 国产专区在线 | 国产精品欧美精品 | 五月婷婷婷婷婷 | 天天色天 | 日韩高清在线观看 | 午夜在线资源 | 欧美男同网站 | 久久男人免费视频 | 日韩欧美一区二区不卡 | www麻豆视频 | 免费看的黄色小视频 | 最新色站 | 亚洲国产成人在线 | 日韩高清精品免费观看 | 日韩久久久久久久久久久久 | 精品国产1区二区 | 久久视频免费观看 | 精品久久久成人 | 色婷婷国产在线 | 91亚色视频在线观看 | 亚洲japanese制服美女 | 天天看天天干 | 又黄又刺激的视频 | 国产精品久久久久久久久久直播 | 亚洲综合色av | 91精品久久久久久久99蜜桃 | 欧美视频日韩视频 | 婷婷天天色| 久久美女免费视频 | 中文字幕 国产视频 | 97在线免费观看视频 | 看v片 | 色七七亚洲影院 | 久久久久亚洲国产精品 | 91av视频网 | 久久国产二区 | 亚洲 精品在线视频 | 视频一区视频二区在线观看 | 欧美性色黄 | 中文字幕国产一区二区 | 青春草免费在线视频 | 丝袜美腿在线播放 | 四虎影视成人永久免费观看亚洲欧美 | 日本在线免费看 | 99精品久久久久久久久久综合 | 97av在线 | 中文字幕久久精品 | 绯色av一区 | 六月丁香六月婷婷 | 日韩综合视频在线观看 | 青青河边草免费观看完整版高清 | 手机看片国产日韩 | 黄p网站在线观看 | 在线观看a视频 | 国产精品自产拍在线观看网站 | 骄小bbw搡bbbb揉bbbb | 中文字幕第一 | 婷婷 中文字幕 | 免费精品| 69国产成人综合久久精品欧美 | 视频国产 | 天堂va欧美va亚洲va老司机 | 欧洲av在线 | 国产精品美女毛片真酒店 | 久久久久蜜桃 | 久久成人麻豆午夜电影 | 成人手机在线视频 | 天天曰视频 | 99精品免费视频 | 久久影视一区二区 | 久久国产精品久久w女人spa | 西西4444www大胆无视频 | 午夜婷婷网 | 日韩综合一区二区 | 日韩欧美视频在线免费观看 | 性色av香蕉一区二区 | 91精品专区 | 日韩国产精品毛片 | 免费一级片在线观看 | 天无日天天操天天干 | 激情动态| 中文视频在线看 | 国产欧美在线一区二区三区 | 三级黄在线 | 久久综合99 | 久草国产在线观看 | 公与妇乱理三级xxx 在线观看视频在线观看 | 色97在线| 一区二区视 | 亚洲v精品| 精品国产伦一区二区三区观看体验 | 天堂av最新网址 | 国产精品第二页 | 国产亚洲视频中文字幕视频 | 亚州国产精品 | 伊人婷婷色 | 97综合网 | 天天干夜夜爱 | 国产精品美女久久久免费 | 国产午夜精品一区二区三区在线观看 | 午夜久久影院 | 狠狠色噜噜狠狠狠狠2022 | 亚洲天堂网视频在线观看 | 亚洲国产精品一区二区久久hs | 91在线精品秘密一区二区 | 91麻豆看国产在线紧急地址 | 一级一级一片免费 | 亚洲最大在线视频 | 香蕉久久国产 | 99热最新网址 | 国产探花 | 99在线精品观看 | 日韩大陆欧美高清视频区 | 91亚洲精| 久久国产品 | 久久免费在线观看 | 国产在线观看91 | 四虎在线观看网址 | 探花视频在线观看免费版 | 在线之家免费在线观看电影 | 日本中文字幕系列 | 欧美一二三区播放 | av不卡在线看 | 欧美专区国产专区 | 亚洲一区在线看 | 欧美日韩不卡一区二区 | 日日爽天天操 | 亚洲一级免费电影 | 欧美精品在线视频观看 | 日韩激情小视频 | 深夜福利视频在线观看 | 色综合久久88色综合天天6 | 免费成人结看片 | 国产中文字幕在线视频 | 国产成人精品一区二区三区网站观看 | 国产精品免费视频久久久 | 一区二区中文字幕在线播放 | 久久久久久黄色 | 久久久久国产精品一区 | 精品无人国产偷自产在线 | 中文字幕一区在线观看视频 | 国产无区一区二区三麻豆 | 日本精品va在线观看 | 国产剧情在线一区 | 日韩偷拍精品 | 国产成人精品一区在线 | 探花视频在线观看免费 | av中文字幕网站 | 狠狠天天 | 久草免费在线视频观看 | 福利网址在线观看 | 综合色综合 | 91黄色免费看 | 日本激情中文字幕 | 狠狠干电影 | 成人97视频一区二区 | 国产精品一区二区久久精品爱微奶 | 久久精品亚洲一区二区三区观看模式 | 日韩av一区二区三区四区 | 国产又粗又硬又爽的视频 | 最新色视频 | 中文字幕a∨在线乱码免费看 | 欧美黑人巨大xxxxx | 成人资源在线 | 狠狠狠色丁香婷婷综合久久五月 | 色综合咪咪久久网 | 日韩在线网址 | 欧美日韩另类在线观看 | 狠狠狠狠狠狠狠狠 | 日本久久综合网 | 免费日韩高清 | av电影在线不卡 | 国产小视频在线观看免费 | 欧美一级性视频 | 欧美日韩中文在线视频 | 一区免费观看 | 一区二区网 | 色久五月| 亚洲欧美成人综合 | 中文字幕亚洲精品日韩 | 国产高清在线免费观看 | 精品国自产在线观看 | 久久婷婷一区二区三区 | www天天操 | 九九色视频 | 国产一级一级国产 | 久久免费视频网站 | 激情电影在线观看 | 久久久久99精品国产片 | 久久久国产精品成人免费 | 国产精品久久久久久超碰 | 91九色国产在线 | 国产精品久久影院 | 伊人久久五月天 | av在线免费在线 | 一区二区视频在线观看免费 | 999国内精品永久免费视频 | 日韩av综合网站 | 中文字幕九九 | 免费在线观看日韩欧美 | 91一区二区三区久久久久国产乱 | 99精品视频在线播放免费 | 美女视频黄色免费 | 97视频亚洲| 日韩啪啪小视频 | 日韩精品网址 | 激情视频91 | 91精品久久久久久 | 在线中文字幕播放 | 91九色网址 | 日韩精品一区二区三区第95 | 91精品婷婷国产综合久久蝌蚪 | 亚洲精品国产成人 | 国产99久久精品一区二区永久免费 | 国产午夜在线观看视频 | 欧美精品在线视频观看 | 成人免费网站在线观看 | 成人在线视频免费看 | 超碰在线网 | 亚洲久在线| 欧美久久综合 | 久草视频在线新免费 | 成人国产精品久久久春色 | 亚洲精品视频在线观看视频 | 五月婷在线观看 | 日韩精品一区二区三区免费观看视频 | 国产成人一区二区三区影院在线 | 中文字幕色网站 | 久久视频精品在线观看 | 亚洲一本视频 | 婷婷六月天丁香 | 狠狠色丁香婷婷综合橹88 | 天天摸天天操天天舔 | 国产一区二区在线视频观看 | 色婷婷激情综合 | 久草男人天堂 | 精品久久久久久国产91 | 欧美日韩久久一区 | 国产一区二区在线免费 | 在线免费视频a | 日韩美一区二区三区 | av看片网 | 精品在线播放视频 | 日韩精品一卡 | 亚洲精品乱码白浆高清久久久久久 | 97精品视频在线播放 | 中文字幕免费成人 | 97**国产露脸精品国产 | 五月综合激情网 | 国内精品99 | 香蕉视频国产在线 | 国产一区免费在线观看 | 欧美亚洲国产一卡 | 91插插影库 | 成人福利av | 麻豆一区二区 | 色网免费观看 | 欧美日韩在线播放 | 中文字幕在线观看你懂的 | 天天射天天干天天插 | 久久久综合 | 色婷婷亚洲 | 国产精品久久久视频 | 免费又黄又爽 | 99久久精品国产系列 | 天天操夜操视频 | 成人资源在线播放 | 美女久久久 | 国产xxxxx在线观看 | 免费观看v片在线观看 | 亚洲黄色影院 | 色婷婷免费视频 | 黄色性av| 高潮久久久久久久久 | 69精品久久久 | 黄色毛片视频免费 | 九九热只有这里有精品 | 三级动态视频在线观看 | 在线免费av观看 | 久久国产精品一二三区 | 久草在线 | 亚洲最大成人网4388xx | 久久在现视频 | h文在线观看免费 | 免费看黄20分钟 | 深爱激情av | 又污又黄的网站 | 激情av资源| 综合激情av | 久久久av电影 | 国产69精品久久久久9999apgf | av高清免费| 日本久久影视 | 伊人色综合久久天天网 | 久久免费观看视频 | 国产色在线观看 | 日日夜夜精品免费观看 | 香蕉视频日本 | 香蕉视频国产在线 | 国产视频一区精品 | 欧美资源 | 日韩爱爱网站 | 国产精品大全 | 狠狠亚洲| 伊人亚洲精品 | 毛片a级片| 国内精品久久久久久久97牛牛 | 色香天天| 日日摸日日添日日躁av | 成人资源在线播放 | 国产精品第十页 | 在线观看av网 | 欧美少妇xxxxxx | 午夜久久久久久久久久影院 | 久久精品电影 | 激情视频一区二区三区 | 国产1区在线观看 | 99免费看片 | 亚洲日韩中文字幕 | 国产精品女同一区二区三区久久夜 | 黄色电影网站在线观看 | 亚洲成人免费 | 国产在线观看av | 亚洲精品视频在线播放 | 午夜精品久久久久久久99 | 日韩在线视频一区二区三区 | 激情大尺度视频 | 夜夜高潮夜夜爽国产伦精品 | av网站免费看 | 91亚洲影院 | 欧美日韩一区二区在线观看 | 婷婷中文字幕综合 | 三级黄色网络 | av官网在线| 综合激情网... | 久久图 | 色欧美成人精品a∨在线观看 | 一区二区激情 | 欧美另类xxxx | 色网站视频| 99久久精品国产一区 | 国产精品一区二区视频 | 国产精品久久久久久久久久免费 | 国产成年免费视频 | 午夜av一区| 激情动态| 亚洲自拍av在线 | av中文国产 | 看黄色91 | 99理论片 | 精品综合久久久 | 久久激五月天综合精品 | 成人羞羞视频在线观看免费 | 欧美a影视 | 久久久免费 | 久久一区二区三区四区 | a视频在线看 | 超碰97中文 | 免费观看黄 | 一区二区三区动漫 | 免费在线国产精品 | 不卡的av片| 国产成人综合图片 | 国内精品一区二区 | 日韩成人精品一区二区 | 亚洲女同videos | 久久婷婷开心 | 天天综合在线观看 | 久久九九久久 | 久久精品视频播放 | av一区二区在线观看中文字幕 | 色狠狠操 | 日本性生活免费看 | 日本性xxx | 日本中文字幕在线播放 | 婷婷狠狠操 | 免费观看成人网 | 国产精品免费观看国产网曝瓜 | 特级a老妇做爰全过程 | 天堂av在线网站 | 四虎国产视频 | 国产午夜不卡 | 天天操天天操一操 | 色综合久久88色综合天天人守婷 | 亚洲精品在线视频 | 亚洲欧美日韩精品久久久 | 337p西西人体大胆瓣开下部 | 亚洲欧美视频在线观看 | av福利在线播放 | 99爱国产精品| 亚洲免费在线播放视频 | 日日夜夜天天操 | 亚洲va天堂va欧美ⅴa在线 | 中文字幕首页 | 欧美日韩中文国产 | 在线激情av电影 | 亚洲精品视频免费看 | 91成人精品 | 久久国产精品精品国产色婷婷 | 午夜av电影院| 国产精品嫩草影院123 | 久久久免费少妇 | 久久精品国产成人 | 成人sm另类专区 | 中午字幕在线观看 | 天天射网站 | 精品亚洲在线 | 免费看的黄色小视频 | 91精品国产麻豆 | 精品国内自产拍在线观看视频 | 国产免费一区二区三区最新 | 国产又粗又猛又色又黄网站 | 日韩午夜一级片 | av免费播放 | 伊人国产在线播放 | 欧美日韩国产一区二区三区在线观看 | 亚洲精品777 | 九九有精品 | 一区二区三区在线观看免费视频 | 亚洲最大在线视频 | 五月天婷婷在线观看视频 | 日韩欧美视频免费看 | 国产精品一区二区三区久久久 | 九九免费视频 | 一色屋精品视频在线观看 | 91最新网址在线观看 | 国产精品刺激对白麻豆99 | 丁香激情五月婷婷 | 国产视频欧美视频 | 夜夜骑天天操 | 五月婷婷综合激情网 | 日日夜av| 久久久免费毛片 | 国产丝袜高跟 | 超碰97人人在线 | 成人精品福利 | 久久久精品国产免费观看同学 | 国内精品久久久久久久久久 | 午夜色性片 | 中文字幕日本在线观看 | 国产色婷婷在线 | 日日婷婷夜日日天干 | 久久综合色天天久久综合图片 | 婷婷5月色 | 一色av| 亚洲丁香日韩 | 亚洲一二区视频 | 99色亚洲| 在线观看视频国产一区 | 激情五月综合 | 日本二区三区在线 | 亚洲精品麻豆 | 成人精品一区二区三区电影免费 | 黄色午夜 | a级国产乱理论片在线观看 特级毛片在线观看 | 欧美日韩精品在线 | 久久情侣偷拍 | 色综合天天 | 国产高清在线免费 | 天天干天天操天天拍 | 精品亚洲免费视频 | 久久免费播放 | aav在线| 啪啪免费试看 | 国内精品久久久久久久久 | 色婷婷亚洲精品 | 欧美a级在线| 瑞典xxxx性hd极品 | 色五月色开心色婷婷色丁香 | 97超碰人人模人人人爽人人爱 | 午夜久久久影院 | 免费视频黄色 | 中文字幕一区二区三区四区视频 | 欧美一级电影片 | 性色av一区二区 | 在线免费观看欧美日韩 | 四虎影院在线观看av | 国产免费亚洲 | 国产精品久久久久久久午夜片 | av免费看网站| 在线视频在线观看 | 园产精品久久久久久久7电影 | 狠狠久久婷婷 | 91精品国产一区二区三区 | 色狠狠久久av五月综合 | 国产特级毛片aaaaaa高清 | wwwwww国产| 久草影视在线 | 国产首页| 国产黄色网 | 人人插人人草 | 97视频在线免费观看 | 国产自产高清不卡 | 夜夜操天天摸 | 久久久久国产精品厨房 | 亚洲国产合集 | 一本一本久久a久久精品牛牛影视 | 香蕉97视频观看在线观看 | 亚洲精品在线免费看 | 在线观看mv的中文字幕网站 | 欧美日韩国产欧美 | 2022中文字幕在线观看 | 日韩三级精品 | 国产免费作爱视频 | 粉嫩av一区二区三区四区 | 91丨九色丨国产丨porny精品 | 亚洲精品国产精品国自产 | 在线你懂 | 亚洲免费成人 | 亚洲欧美成人综合 | 国产免费影院 | 成 人 黄 色 视频免费播放 | 六月色婷 | 性色av香蕉一区二区 | 国产在线观看99 | 久久女教师 | 国产人成免费视频 | 精品一区二区在线看 | 国产亚洲精品bv在线观看 | 国产一级片免费观看 | 欧美一区二区在线免费看 | 探花国产在线 | 天天操天天吃 | 美女视频网站久久 | 麻豆一二 | 亚洲精品午夜一区人人爽 | 亚洲天堂激情 | 成人羞羞免费 | 国产亚洲一区二区三区 | 黄色aa久久 | 美女久久久久久 | 日本在线观看一区 | 中文字幕精品久久 | 亚洲精品视频在线观看网站 | 99欧美视频 | 一级黄色片在线免费观看 | 亚洲一区动漫 | 亚州精品在线视频 | 国产99亚洲 | 欧美9999 | 四虎在线影视 | 国产亚洲va综合人人澡精品 | 在线观看视频国产 | 久久不射电影院 | 看片在线亚洲 | 国产久草在线观看 | 国产精品成人在线观看 | 日韩免费在线视频 | 亚洲欧美国产视频 | 丝袜美腿在线播放 | 国产精品久久久久久久久久久免费 | 天堂av在线网 | 国产精品 999 | 中文字幕观看视频 | 免费午夜视频在线观看 | 国产高清久久 | 亚洲va欧美va人人爽 | 国产日产精品一区二区三区四区的观看方式 | 欧美性色xo影院 | 国产91成人 | 日韩免费看的电影 | 91网在线 | 久草免费新视频 | 日韩www在线 | 精品综合久久久 | 久久免费高清视频 | 久久久久视 | 国产美女视频一区 | 久久精品视频3 | 最新国产精品拍自在线播放 | 国产亚洲在线视频 | 国产一区二区三区在线免费观看 | 日韩在线观看高清 | 欧美激情综合网 | 午夜精品av| 亚洲国产精品99久久久久久久久 | 久久激情视频 久久 | 欧美日韩一区二区三区免费视频 | av在线免费观看黄 | 91手机视频在线 | 婷婷激情欧美 | 99精品国产在热久久下载 | 中文字幕首页 | 国际av在线 | 国产人成在线视频 | 免费在线观看一区二区三区 | 婷婷综合久久 | 一区二区三区动漫 | 国产精品大片 | 成年人在线免费看视频 | 国产精品久久久久久久久久久久冷 | 成人av片在线观看 | 国产在线观看a | 久久高清| 四虎成人精品永久免费av | 成人欧美日韩国产 | 中文字幕日本在线观看 | 丁香午夜 | 91麻豆网站| 99视频在线免费播放 | 日日操天天射 | 在线高清一区 | 91禁看片 | 在线播放 日韩专区 | 国产人成看黄久久久久久久久 | 日本久久影视 |