免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
linux內核分析筆記----定時器和時間管理
在這一次里,主要講講和時間相關的東西,這個我們都比較熟悉,我就直接如主題。
首先要明白兩個概念:系統(tǒng)定時器和動態(tài)定時器。周期性產生的事件都是有系統(tǒng)定時器驅動的,這里的系統(tǒng)定時器是一種可編程硬件芯片,它能以固定頻率產生中斷。該中斷就是定時器中斷,它所對應的中斷處理程序負責更新系統(tǒng)時間,也負責執(zhí)行需要周期行運行的任務。系統(tǒng)定時器和時鐘中斷處理程序是Linux系統(tǒng)內核管理機制中的中樞。動態(tài)定時器是用來推遲執(zhí)行程序的工具。內核可以動態(tài)創(chuàng)建或銷毀動態(tài)定時器。
內核必須在硬件的幫助下才能計算和管理時間。硬件為內核提供了一個系統(tǒng)定時器用以計算流逝的時間,該時鐘在內核中可看成是一個電子時間資源。系統(tǒng)定時器以某種頻率自行觸發(fā)時鐘中斷,該頻率可以通過編程預定稱為節(jié)拍率(tick rate).當時鐘中斷發(fā)生時,內核就通過一種特殊的中斷處理程序對其進行處理。系統(tǒng)定時器頻率(節(jié)拍率)是通過靜態(tài)預處理定義的,也就是HZ.在系統(tǒng)啟動時按照HZ值對硬件進行設置。體系結構不一樣,HZ的值也不同,定義在asm/param.h中。剛提到的節(jié)拍率就是這個意思。周期是1/HZ秒。最后要說明的是這個HZ值在編寫內核代碼時,不是固定不變的,而是可調的。當然,對于操作系統(tǒng)而言,也并不是一定要這個固定的時鐘中斷。實際上,內核可以使用動態(tài)編程定時器操作掛起事件。這里就不多說了。
在linux內核里,有一個叫jiffies的變量(定義在linux/jiffies)記錄了自系統(tǒng)啟動以來產生的節(jié)拍的總數(shù)。啟動時,內核將該變量初始化為0,此后每次時鐘中斷處理程序都會增加該變量的值。因為一秒內時鐘中斷的次數(shù)等于HZ,所以jiffies一秒內增加的值也就為HZ.系統(tǒng)運行時間以秒為單位計算,就等于jiffes/HZ.它作為在計算機表示的變量,就總存在大小,當這個變量增加到超出它的表示上限時,就要回繞到0.這個回繞看起來很簡單,但實際上還是給我們編程造成了很大的麻煩,比如邊界條件判斷時。幸好,內核提供了四個宏來幫助比較節(jié)拍計數(shù),這些宏定義在linux/jiffies.h可以很好的處理節(jié)拍回繞的情況:
    說明:unknown參數(shù)通常是jiffies,known參數(shù)是需要對比的值。
如果改變內核中的HZ的值則會給用戶空間中某些程序造成異常結果,這是因為內核是以節(jié)拍數(shù)/秒的形式給用戶空間導出這個值的,在這個接口穩(wěn)定了很長一段時間后,應用程序便逐漸依賴于這個特定的HZ的值了。所以如果在內核中更改了HZ的定義值,就打破了用戶空間的常量關系----用戶空間并不知道這個新的HZ的值。為了解決這個問題,內核必須更改所有導出的jiffies的值。內核定義了USER_HZ來代表用戶空間看到的HZ值。內核可以使用宏jiffies_to_clock_t()將一個由HZ表示的節(jié)拍計數(shù)轉換成一個由USER_HZ表示的節(jié)拍數(shù)。改宏的用法取決于USER_HZ是否為HZ的整數(shù)倍或相反。當是整數(shù)倍時,宏的形式相當簡單:
1
#define jiffies_to_clock_t(x) ((x)/(HZ/USER_HZ));
如果不是整數(shù)倍關系,那么該宏就得用更為復雜的算法了。同樣的,如果是64位系統(tǒng),內核使用函數(shù)jiffies_64_to_clock()將64位的jiffies值的單位從HZ轉換為USER_HZ.
體系結構提供了兩種設備進行計時:系統(tǒng)定時器和實時時鐘。系統(tǒng)定時器提供一種周期性觸發(fā)中斷機制。實時時鐘(RTC)是用來持久存儲系統(tǒng)時間的設備,即便系統(tǒng)關閉后,它也可以靠主板上的微型電池提供的電力保護系統(tǒng)的計時。當系統(tǒng)啟動時,內核通過讀取RTC來初始化墻上時間,該時間存放在xtime變量中,實時時鐘最主要的作用是在啟動時初始化xtime變量。
有了上面的概念基礎,下面就分析時鐘中斷處理程序。它分為兩個部分:體系結構相關部分和體系結構無關部分。相關的部分作為系統(tǒng)定時器的中斷處理程序而注冊到內核中,以便在產生時鐘中斷時,它能夠相應地運行。執(zhí)行的工作如下:
1.獲得xtime_lock鎖,以便對訪問jiffies_64和墻上時間xtime進行保護。
2.需要時應答或重新設置系統(tǒng)時鐘。
3.周期性地使用墻上時間更新實時時鐘。
4.調用體系結構無關的時間例程:do_timer().
中斷服務程序主要通過調用與體系結構無關的例程do_timer()執(zhí)行下面的工作:
1.給jiffies_64變量加1.
2.更新資源消耗的統(tǒng)計值,比如當前進程所消耗的系統(tǒng)時間和用戶時間。
3.執(zhí)行已經到期的動態(tài)定時器.
4.執(zhí)行scheduler_tick()函數(shù).
5.更新墻上時間,該時間存放在xtime變量中.
6.計算平均負載值.
do_timer看起來還是很簡單的,應為它的主要工作就是完成上面的框架,具體的讓其它函數(shù)做就好了:
1
2
3
4
5
6
void do_timer(struct pt_regs *regs)
{
jiffies_64++;
update_process_times(user_mode(regs));
update_times();
}
上述user_mode()宏查詢處理器寄存器regs的狀態(tài),如果時鐘中斷發(fā)生在用戶空間,它返回1;如果發(fā)生在內核模式,則返回0.update_process_times()函數(shù)根據(jù)時鐘中斷產生的位置,對用戶或對系統(tǒng)進行相應的時間更新:
1
2
3
4
5
6
7
8
9
void update_process_times(int user_tick)
{
struct task_struct *p=current;
int cpu=smp_processor_id();
int system=user_tick^1;
updata_one_process(p,user_tick,system,cpu);
run_local_timers();
scheduler_tick(user_tick,system);
}
update_one_process()函數(shù)的作用是更新進程時間。它的實現(xiàn)是相當細致的。但注意,因為使用了XOR操作,所以user_tick和system兩個變量只要其中有一個為1,則另外一個就必須為0,updates_one_process()函數(shù)可以通過判斷分支,將user_tick和system加到進程相應的計數(shù)上:
1
2
p->utime = user;
p->stime = system;
上述操作將適當?shù)挠嫈?shù)值增加1,而另外一個值保持不變。也許你已經發(fā)現(xiàn)了,這樣做意味著內核對進程時間計數(shù)時,是根據(jù)中斷發(fā)生時處理器所處的模式進行分類統(tǒng)計的,它把上一個tick全部算給進程。但是事實上進程在上一個節(jié)拍器間可能多次進入和退出內核模式,而在在上一個節(jié)拍期間,該進程也不一定是唯一一個運行進程,但是這沒辦法。接下來的run_lock_times() 函數(shù)標記了一個軟中斷去處理所有到期的定時器。最后,scheduler_tick()函數(shù)負責減少當前運行進程的時間片計數(shù)值并且在需要時設置need_resched標志,在SMP機器中中,該函數(shù)還要負責平衡每個處理器上的運行隊列。當update_process_times()函數(shù)返回時,do_timer()函數(shù)接著會調用update_times()更新墻上時間。
1
2
3
4
5
6
7
8
9
10
void update_times(void)
{
unsigned long ticks;
if(ticks){
wall_jiffies += ticks;
update_wall_time(ticks);
}
last_time_offset = 0;
calc_load(ticks);
}
這里的ticks記錄最近一次更新后新產生的節(jié)拍數(shù)。通常情況下ticks顯然應該等于1.但是時鐘中斷也有可能丟失,因而節(jié)拍也會丟失。在中斷長時間被禁止的情況下,就會出現(xiàn)這種現(xiàn)象(這種情況并不常見,往往是個BUG).wall_jiffies值隨后被加上ticks----所以此刻wall_jiffies值就等于更新的墻上時間的更新值jiffies----接著調用update_wall_time()函數(shù)更新xtime,最后由calc_load()執(zhí)行。do_timer()函數(shù)執(zhí)行完畢后返回與體系結構相關的中斷處理程序,繼續(xù)執(zhí)行后面的工作,釋放xtime_lock鎖,然后退出。以上的工作每1/HZ都要發(fā)生一次。
剛前邊說的墻上時間就是我們常說的實際時間,指變量xtime,由結構體timespec定義(kernel/timer.c),如下:
1
2
3
4
struct  timespec{
time_t tv_sec;  //秒,存放自1970年7月1日(UTC)以來經過的時間,1970年7月1日稱為紀元
long tv_nsec;   //納秒,記錄自上一秒開始經過的納秒數(shù)
}
讀寫這個xtime變量需要xtime_lock鎖,該鎖是一個順序鎖(seqlock).關于內核讀寫就不說了,注意適當加解鎖就好。回到用戶空間,從用戶空間取得墻上時間的主要接口是gettimeofday(),在內核中對應系統(tǒng)調用為sys_gettimeofday():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
{
if (likely(tv != NULL)) {
struct timeval ktv;
do_gettimeofday(&ktv);
if (copy_to_user(tv, &ktv, sizeof(ktv)))
return -EFAULT;
}
if (unlikely(tz != NULL)) {
if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
return -EFAULT;
}
return 0;
}
分析上面的函數(shù)發(fā)現(xiàn),問題就集中在tv上。當tv非空,就調用do_gettimeofday(),它主要完成循環(huán)讀取xtime的操作。如果tz參數(shù)為空,該函數(shù)將把系統(tǒng)時區(qū)(存放在sys_tz中)返回用戶。如果給用戶空間拷貝墻上時間或時區(qū)發(fā)生錯誤,該函數(shù)返回-EFAULT;如果成功,則返回0.另外,內核提供的time系統(tǒng)調用,幾乎被gettimeofday()完全取代。C庫函數(shù)提供的一些墻上時間相關的庫調用如ftime和ctime。系統(tǒng)的settimeofday()是用來設置當前時間,它需要具有CAP_SYS_TIME權限。除了更新xtime時間以外,內核不會像用戶空間程序那樣頻繁使用xtime。但也需要注意在文件系統(tǒng)的實現(xiàn)代碼中存放訪問時間戳時需要使用xtime。
上面說完了有關硬時鐘,下面開始新的話題,是關于定時器的(也稱動態(tài)定時器或內核定時器)。定時器并不周期執(zhí)行,它在超時后就自行銷毀。定義器由定義在linux/timer.h中的time_list表示,如下:
1
2
3
4
5
6
7
8
9
struct timer_list {
struct list_head entry;
unsigned long expires;
spinlock_t lock;
unsigned long magic;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
};
內核提供了一組與定時器相關的用來簡化管理定時器的操作。所有這些接口都聲明在文件linux/timer.h中,大多數(shù)接口在文件kernel/timer.c中獲得實現(xiàn)。有了這些接口,我們要做的事情就很簡單了:
1.創(chuàng)建定時器:struct timer_list my_timer;
2.初始化定時器:init_timer(&my_timer);
3.根據(jù)需要,設置定時器了:
my_timer.expires = jiffies + delay;
my_timer.data = 0;
my_timer.function = my_function;
4.激活定時器:add_timer(&my_timer);
經過上面的幾步,定時器就可以開始工作了。然而,一般來說,定時器都在超時后馬上就會執(zhí)行,但是也有可能被推遲到下一時鐘節(jié)拍時才能運行,所以不能使用它來實現(xiàn)硬實時。如果修改定時器,使用mod_timer(&my_timer,jiffies+new_delay)來修改已經激活的定時器時間。它也可以操作那些已經初始化,但還沒有被激活的定時器,如果定時器未被激活,mod_timer會激活它。如果第啊喲個定時器時未被激活,該函數(shù)返回0;否則返回1。但不論哪種情況,一旦從mod_timer函數(shù)返回,定時器都將被激活而且設置了新的定時值。當然你也可以在超市前刪除定時器用:del_timer(&my_timer);另外需要注意的是在多處理器上定時器中斷可能已經在其它機器上運行了,這是就需要等待可能在其它處理器上運行的定時器處理程序都退出后再刪除該定時器。這是就要使用del_timer_sync()函數(shù)執(zhí)行刪除工作。這個函數(shù)參數(shù)和上面一個一樣,只是不能在中斷上下文中使用而已。定時器是獨立與當前代碼的,這意味著可能存在競爭條件,這個就要特別小心,從這個意義上講后者刪除比前者更加安全。
內核在時鐘中斷發(fā)生后執(zhí)行定時器,定時器作為軟件中斷在下半部上下文中執(zhí)行。具體來說就是時鐘中斷處理程序會執(zhí)行update_process_timers()函數(shù),該函數(shù)隨即調用run_local_timers()函數(shù):
1
2
3
4
void run_local_timers(void)
{
raise_softirq(TIMER_SOFTIRQ);
}
這個函數(shù)處理軟中斷TIEMR_SOFTIRQ,從而在當前處理器上運行所有的超時定時器。所有定時器都以鏈表的形式組織起來,但如果單純的鏈表結構顯然影響性能,因為每次都要順序的的查找調整,這個時候,內核定時器按它們的超時時間將他們分為5組,當定時器超時時間接近時,定時器將隨組一起下移。采用這種方法可以減少搜素超時定時器所帶來的負擔。
下一話題,內核代碼(尤其是驅動程序)除了使用定時器或下半部機制以外還提供了許多延遲的方法來處理各種延遲請求。下面就來總結一下:
1.忙等待(也叫忙循環(huán)):通常是最不理想的方法,因為處理器被白白占用旋轉而無法做別的事情。該方法僅僅在想要延遲的時間是節(jié)拍的整數(shù)倍或者精確率要求不高時才可以使用。實現(xiàn)起來還是挺簡單的,就是在循環(huán)中不斷旋轉直到希望的時鐘節(jié)拍數(shù)耗盡。比如:
1
2
3
unsigned long delay = jiffies+10;   //10個節(jié)拍
while(time_before(jiffies,delay))
cond_resched();
缺點很明顯,更好的方法是在代碼等待時,允許內核重新調度執(zhí)行其他任務,如下:
1
2
3
unsigned long delay = jiffies+10;   //10個節(jié)拍
while(time_before(jiffies,delay))
cond_resched();
cond_resched()函數(shù)將調度一個新程序投入運行,但它只有在設置完need_resched標志后才能生效。換句話說,就是系統(tǒng)中存在更重要的任務需要運行。再由于該方法需要調用調度程序,所以它不能在中斷上下文中使用----只能在進程上下文中使用。事實上,所有延遲方法在進程上下文中使用,因為中斷處理程序都應該盡可能快的執(zhí)行。另外,延遲執(zhí)行不管在哪種情況下都不應該在持有鎖時或者禁止中斷時發(fā)生。
至于說那些需要很短暫的延遲(比時鐘節(jié)拍還短)而且還要求延遲的時間很精確,這種情況多發(fā)生在和硬件同步時,也就是說需要短暫等待某個動作的完成----等待時間往往小于1ms,所以不可能使用像前面例子中那種基于jiffies的延遲方法。這時,就可以使用在linux/delay.h中定義的兩個函數(shù),它們不使用,這兩個函數(shù)可以處理微秒和毫秒級別的延遲的時間,如下所示:
1
2
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
前者是依靠執(zhí)行次數(shù)循環(huán)來達到延遲效果的,而mdelay()函數(shù)又是通過udelay()函數(shù)實現(xiàn)的。因為內核知道處理器在一秒內能執(zhí)行多少次循環(huán),所以udelay()函數(shù)僅僅需要根據(jù)指定的延遲時間在1秒中占的比例,就能決定需要進行多少次循環(huán)就能達到需要的推遲時間。udelay()函數(shù)僅能在要求的延遲時間很短的情況下執(zhí)行,而在高速機器中時間很長的延遲會造成溢出,經驗表明,不要試圖在延遲超過1ms的情況下使用這個函數(shù)。這兩個函數(shù)其實和忙等待一樣,如果不是非常必要,還是不要用了算了。
前邊說的有點害怕,那咋辦呢?其實更理想的延遲執(zhí)行方法是使用schedule_timeout()函數(shù),該方法會讓需要延遲執(zhí)行的任務睡眠到指定的延遲時間耗盡后再重新運行。但該方法也不能保證睡眠時間正好等于指定的延遲時間----只能盡量是睡眠時間接近指定的延遲時間。當指定的時間到期后,內核喚醒被延遲的任務并將其重新放回運行隊列,如下:
1
2
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(s*HZ);
唯一的參數(shù)是延遲的相對時間,單位是jiffies,上例中將相應的任務推入可中斷睡眠隊列,睡眠s秒。在調用函數(shù)schedule_timeout之前,不要要將任務設置成可中斷或不和中斷的一種,否則任務不會休眠。這個函數(shù)需要調用調度程序,所以調用它的代碼必須保證能夠睡眠,簡而言之,調用代碼必須處于進程上下文中,并且不能持有鎖。有關這個函數(shù)的實現(xiàn)細節(jié),可以看下源碼,還是相當簡單的。接下來就是當定時器超時,process_timeout()函數(shù)被調用:
1
2
3
4
void process_timeout(unsigned long data)
{
wake_up_process((task_t *)data);
}
該函數(shù)將任務置為TASK_RUNNING狀態(tài),然后哦將其放入運行隊列。當任務重新被調度時,將返回代碼進入睡眠前的位置繼續(xù)執(zhí)行(正好在調用schedule()后)。如果任務提前被喚醒(比如收到信號),那么定時器被銷毀,process_timeout()函數(shù)返回剩余的時間。
最后,在進程調度那一節(jié)我們說過,進程上下文的代碼為了等待特定時間發(fā)生,可以將自己放入等待隊列。但是,等待隊列上的某個任務可能既在等待一個特定事件到來,又在等待一個特定時間到期----就看誰來得更快。這種情況下,代碼可以簡單的使用scedule_timeout()函數(shù)代替schedule()函數(shù),這樣一來,當希望指定時間到期后,任務都會被喚醒,當然,代碼需要檢查被喚醒的原因----有可能是被事件喚醒,也有可能是因為延遲的時間到期,還可能是因為接收到了信號----然后執(zhí)行相應的操作。
本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux中斷處理之時鐘中斷
linux內核定時器
定時器和時間管理
內核定時器
timer定時器、jiffies、HZ、Tick理解
18. 時鐘管理
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服