LINUX進程間隔定時器itimer

來源:互聯網
上載者:User
轉自:http://hi.baidu.com/wzbob/blog/item/dec68f8255391690f703a66f.html

系統時鐘硬體與LINUX時間表示 之 進程間隔定時器itimer

7 、進程間隔定時器itimer
所謂“間隔定時器(Interval
Timer,簡稱itimer)就是指定時器採用“間隔”值(interval)來作為計時方式,當定時器啟動後,間隔值interval將不斷減小。當
interval值減到0時,我們就說該間隔定時器到期。與上一節所說的核心動態定時器相比,二者最大的區別在於定時器的計時方式不同。核心定時器是通過
它的到期時刻expires值來計時的,當全域變數jiffies值大於或等於核心動態定時器的expires值時,我們說核心核心定時器到期。而間隔定
時器則實際上是通過一個不斷減小的計數器來計時的。雖然這兩種定時器並不相同,但卻也是相互聯絡的。假如我們每個時鐘節拍都使間隔定時器的間隔計數器減
1,那麼在這種情形下間隔定時器實際上就是核心動態定時器(下面我們會看到進程的真實間隔定時器就是這樣通過核心定時器來實現的)。
間隔定時器主要被應用在使用者進程上。每個Linux進程都有三個相互關聯的間隔定時器。其各自的間隔計數器都定義在進程的task_struct結構中,如下所示(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)真實間隔定時器(ITIMER_REAL):這種間隔定時器在啟動後,不管進程是否運行,每個時鐘滴答都將其間隔計數器減1。當減到0值時,核心向
進程發送SIGALRM訊號。結構類型task_struct中的成員it_real_incr則表示真實間隔定時器的間隔計數器的初始值,而成員
it_real_value則表示真實間隔定時器的間隔計數器的當前值。由於這種間隔定時器本質上與上一節的核心定時器時一樣的,因此Linux實際上是
通過real_timer這個內嵌在task_struct結構中的核心動態定時器來實現真實間隔定時器ITIMER_REAL的。
(2)虛擬間隔定時器ITIMER_VIRTUAL:也稱為進程的使用者態間隔定時器。結構類型task_struct中成員it_virt_incr和
it_virt_value分別表示虛擬間隔定時器的間隔計數器的初始值和當前值,二者均以時鐘滴答次數位計數單位。當虛擬間隔定時器啟動後,只有當進程
在使用者態下運行時,一次時鐘滴答才能使間隔計數器當前值it_virt_value減1。當減到0值時,核心向進程發送SIGVTALRM訊號(虛擬鬧鐘
訊號),並將it_virt_value重設為初值it_virt_incr。具體請見4.3節中的do_it_virt()函數的實現。
(3)PROF間隔定時器ITIMER_PROF:進程的task_struct結構中的it_prof_value和it_prof_incr成員分別
表示PROF間隔定時器的間隔計數器的當前值和初始值(均以時鐘滴答為單位)。當一個進程的PROF間隔定時器啟動後,則只要該進程處於運行中,而不管是
在使用者態或核心態下執行,每個時鐘滴答都使間隔計數器it_prof_value值減1。當減到0值時,核心向進程發送SIGPROF訊號,並將
it_prof_value重設為初值it_prof_incr。具體請見4.3節的do_it_prof()函數。
Linux在include/linux/time.h標頭檔中為上述三種進程間隔定時器定義了索引標識,如下所示:
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2

7.1 資料結構itimerval
雖然,在核心中間隔定時器的間隔計數器是以時鐘滴答次數為單位,但是讓使用者以時鐘滴答為單位來指定間隔定時器的間隔計數器的初值顯然是不太方便的,因為用
戶習慣的時間單位是秒、毫秒或微秒等。所以Linux定義了資料結構itimerval來讓使用者以秒或微秒為單位指定間隔定時器的時間間隔值。其定義如下
(include/linux/time.h):
struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
其中,it_interval成員表示間隔計數器的初始值,而it_value成員表示間隔計數器的當前值。這兩個成員都是timeval結構類型的變數,因此其精度可以達到微秒級。

l timeval與jiffies之間的相互轉換
由於間隔定時器的間隔計數器的內部表示方式與外部表格現方式互不相同,因此有必要實現以微秒為單位的timeval結構和為時鐘滴答次數單位的
jiffies之間的相互轉換。為此,Linux在kernel/itimer.c中實現了兩個函數實現二者的互相轉換——tvtojiffies()函
數和jiffiestotv()函數。它們的源碼如下:
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.2 真實間隔定時器ITIMER_REAL的底層運行機制
間隔定時器ITIMER_VIRT和ITIMER_PROF的底層運行機制是分別通過函數do_it_virt()函數和do_it_prof()函數來實現的,這裡就不再重述(可以參見4.3節)。
由於間隔定時器ITIMER_REAL本質上與核心動態定時器並無區別。因此核心實際上是通過核心動態定時器來實現進程的ITIMER_REAL間隔定時
器的。為此,task_struct結構中專門設立一個timer_list結構類型的成員變數real_timer。動態定時器real_timer的
函數指標function總是被task_struct結構的初始化宏INIT_TASK設定為指向函數it_real_fn()。如下所示
(include/linux/sched.h):
#define INIT_TASK(tsk) /
……
real_timer: {
function: it_real_fn /
} /
……
}
而real_timer鏈表元素list和data成員總是被進程建立時分別初始化為空白和進程task_struct結構的地址,如下所示(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;
……
}
當使用者通過setitimer()系統調用來設定進程的ITIMER_REAL間隔定時器時,it_real_incr被設定成非零值,於是該系統調用相
應地設定好real_timer.expires值,然後進程的real_timer定時器就被加入到核心動態定時器鏈表中,這樣該進程的
ITIMER_REAL間隔定時器就被啟動了。當real_timer定時器到期時,它的關聯函數it_real_fn()將被執行。注意!所有進程的
real_timer定時器的function函數指標都指向it_real_fn()這同一個函數,因此it_real_fn()函數必須通過其參數來
識別是哪一個進程,為此它將unsigned
long類型的參數p解釋為進程task_struct結構的地址。該函數的源碼如下(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);
}
}
函數it_real_fn()的執行過程大致如下:
(1)首先將參數p通過強制類型轉換解釋為進程的task_struct結構類型的指標。
(2)向進程發送SIGALRM訊號。
(3)在進程的it_real_incr非0的情況下繼續啟動real_timer定時器。首先,計算real_timer定時器的expires值為
(jiffies+it_real_incr)。然後,調用add_timer()函數將real_timer加入到核心動態定時器鏈表中。

7.3 itimer定時器的系統調用
與itimer定時器相關的syscall有兩個:getitimer()和setitimer()。其中,getitimer()用於查詢調用進程的三
個間隔定時器的資訊,而setitimer()則用來設定調用進程的三個間隔定時器。這兩個syscall都是現在kernel/itimer.c檔案
中。

7.3.1 getitimer()系統調用的實現
函數sys_getitimer()有兩個參數:(1)which,指定查詢調用進程的哪一個間隔定時器,其取值可以是ITIMER_REAL、
ITIMER_VIRT和ITIMER_PROF三者之一。(2)value指標,指向使用者空間中的一個itimerval結構,用於接收查詢結果。該函
數的源碼如下:
/* 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()函數主要通過do_getitimer()函數來查詢當前進程的間隔定時器資訊,並將查詢結果儲存在核心空間的結構
變數get_buffer中。然後,調用copy_to_usr()宏將get_buffer中結果拷貝到使用者空間緩衝區中。
函數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;
}
查詢的過程如下:
(1)首先,用局部變數val和interval分別表示待查詢間隔定時器的間隔計數器的當前值和初始值。
(2)如果which=ITIMER_REAL,則查詢當前進程的ITIMER_REAL間隔定時器。於是從
current->it_real_incr中得到ITIMER_REAL間隔定時器的間隔計數器的初始值,並將其儲存到interval局部變數
中。而對於間隔計數器的當前值,由於ITITMER_REAL間隔定時器是通過real_timer這個核心動態定時器來實現的,因此不能通過
current->it_real_value來獲得ITIMER_REAL間隔定時器的間隔計數器的當前值,而必須通過real_timer來得
到這個值。為此先用timer_pending()函數來判斷current->real_timer是否已被起動。如果未啟動,則說明
ITIMER_REAL間隔定時器也未啟動,因此其間隔計數器的當前值肯定是0。因此將val變數簡單地置0就可以了。如果已經啟動,則間隔計數器的當前
值應該等於(timer_real.expires-jiffies)。
(3)如果which=ITIMER_VIRT,則查詢當前進程的ITIMER_VIRT間隔定時器。於是簡單地將計數器初值it_virt_incr和當前值it_virt_value分別儲存到局部變數interval和val中。
(4)如果which=ITIMER_PROF,則查詢當前進程的ITIMER_PROF間隔定時器。於是簡單地將計數器初值it_prof_incr和當前值it_prof_value分別儲存到局部變數interval和val中。
(5)最後,通過轉換函式jiffiestotv()將val和interval轉換成timeval格式的時間值,並儲存到value->it_value和value->it_interval中,作為查詢結果返回。

7.3.2 setitimer()系統調用的實現
函數sys_setitimer()不僅設定調用進程的指定間隔定時器,而且還返回該間隔定時器的原有資訊。它有三個參數:(1)which,含義與
sys_getitimer()中的參數相同。(2)輸入參數value,指向使用者空間中的一個itimerval結構,含有待設定的新值。(3)輸出參
數ovalue,指向使用者空間中的一個itimerval結構,用於接收間隔定時器的原有資訊。
該函數的源碼如下(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;
}
對該函數的注釋如下:
(1)在輸入參數指標value非空的情況下,調用copy_from_user()宏將使用者空間中的待設定資訊拷貝到核心空間中的set_buffer結構變數中。如果value指標為空白,則簡單地將set_buffer結構變數全部置0。
(2)調用do_setitimer()函數完成實際的設定作業。如果輸出參數ovalue指標有效,則以核心變數get_buffer的地址作為
do_setitimer()函數的第三那個調用參數,這樣當do_setitimer()函數返回時,get_buffer結構變數中就將含有當前進程
的指定間隔定時器的原來資訊。Do_setitimer()函數返回0值表示成功,非0值表示失敗。
(3)在do_setitimer()函數返回非0值的情況下,或者ovalue指標為空白的情況下(不需要輸出間隔定時器的原有資訊),函數就可以直接返回了。
(4)如果ovalue指標非空,調用copy_to_user()宏將get_buffer()結構變數中值拷貝到ovalue所指向的使用者空間中去,以便讓使用者得到指定間隔定時器的原有資訊值。

函數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;
}
對該函數的注釋如下:
(1)首先調用tvtojiffies()函數將timeval格式的初始值和當前值轉換成以時鐘滴答為單位的時間值。並分別儲存在局部變數i和j中。
(2)如果ovalue指標非空,則調用do_getitimer()函數查詢指定間隔定時器的原來資訊。如果do_getitimer()函數返回負值,說明出錯。因此就要直接返回錯誤值。否則繼續向下執行開始真正地設定指定的間隔定時器。
(3)如果which=ITITMER_REAL,表示設定ITIMER_REAL間隔定時器。(a)調用del_timer_sync()函數(該函數
在單CPU系統中就是del_timer()函數)將當前進程的real_timer定時器從核心動態定時器鏈表中刪除。(b)將
it_real_incr和it_real_value分別設定為局部變數i和j。(c)如果j=0,說明不必啟動real_timer定時器,因此執行
break語句退出switch…case控制結構,而直接返回。(d)將real_timer的expires成員設定成(jiffies+當前值
j),然後調用add_timer()函數將當前進程的real_timer定時器加入到核心動態定時器鏈表中,從而啟動該定時器。
(4)如果which=ITIMER_VIRT,則簡單地用局部變數i和j的值分別更新it_virt_incr和it_virt_value就可以了。
(5)如果which=ITIMER_PROF,則簡單地用局部變數i和j的值分別更新it_prof_incr和it_prof_value就可以了。
(6)最後,返回0值表示成功。

7.3.3 alarm系統調用
系統調用alarm可以讓調用進程在指定的秒數間隔後收到一個SIGALRM訊號。它只有一個參數seconds,指定以秒數計的定時間隔。函數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;
}
這個系統調用實際上就是啟動進程的ITIMER_REAL間隔定時器。因此它完全可放到使用者空間的C函數庫(比如libc和glibc)中來實現。但是為
了保此核心的向後相容性,2.4.0版的核心仍然將這個syscall放在核心空間中來實現。函數sys_alarm()的實現過程如下:
(1)根據參數seconds的值構造一個itimerval結構變數it_new。注意!由於alarm啟動的ITIMER_REAL間隔定時器是一次性而不是迴圈重複的,因此it_new變數中的it_interval成員一定要設定為0。
(2)調用函數do_setitimer()函數以新構造的定時器it_new來啟動當前進程的ITIMER_REAL定時器,同時將該間隔定時器的原定時間隔儲存到局部變數it_old中。
(3)傳回值oldalarm表示以秒數計的ITIMER_REAL間隔定時器的原定時間隔值。因此先把it_old.it_value.tv_sec賦
給oldalarm,並且在it_old.it_value.tv_usec非0的情況下,將oldalarm的值加1(也即不足1秒補足1秒)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.