Linux系統進程間隔定時器Itimer

 所謂「間隔定時器(Interval Timer,簡稱itimer)就是指定時器採用「間隔」值(interval)來做爲計時方式,當定時器啓動後,間隔值interval將不斷減少。當 interval值減到0時,咱們就說該間隔定時器到期。與上一節所說的內核動態定時器相比,兩者最大的區別在於定時器的計時方式不一樣。內核定時器是經過 它的到期時刻expires值來計時的,當全局變量jiffies值大於或等於內核動態定時器的expires值時,咱們說內核內核定時器到期。而間隔定 時器則其實是經過一個不斷減少的計數器來計時的。雖然這兩種定時器並不相同,但卻也是相互聯繫的。假如咱們每一個時鐘節拍都使間隔定時器的間隔計數器減 1,那麼在這種情形下間隔定時器實際上就是內核動態定時器(下面咱們會看到進程的真實間隔定時器就是這樣經過內核定時器來實現的)。linux

  間隔定時器主要被應用在用戶進程上。每一個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;
……
}
app

(1)真實間隔定時器(ITIMER_REAL):這種間隔定時器在啓動後,無論進程是否運行,每一個時鐘滴答都將其間隔計數器減1。當減到0值時,內核向 進程發送SIGALRM信號。結構類型task_struct中的成員it_real_incr則表示真實間隔定時器的間隔計數器的初始值,而成員 it_real_value則表示真實間隔定時器的間隔計數器的當前值。因爲這種間隔定時器本質上與上一節的內核定時器時同樣的,所以Linux其實是 經過real_timer這個內嵌在task_struct結構中的內核動態定時器來實現真實間隔定時器ITIMER_REAL的。
2)虛擬間隔定時器ITIMER_VIRT:也稱爲進程的用戶態間隔定時器。結構類型task_struct中成員it_virt_incr和 it_virt_value分別表示虛擬間隔定時器的間隔計數器的初始值和當前值,兩者均以時鐘滴答次數位計數單位。當虛擬間隔定時器啓動後,只有當進程 在用戶態下運行時,一次時鐘滴答才能使間隔計數器當前值it_virt_value減1。當減到0值時,內核向進程發送SIGVTALRM信號(虛擬鬧鐘 信號),並將it_virt_value重置爲初值it_virt_incr。具體請見7.4.3節中的do_it_virt()函數的實現。ide

(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。具體請見7.4.3節的do_it_prof()函數。函數

  Linux在include/linux/time.h頭文件中爲上述三種進程間隔定時器定義了索引標識,以下所示:atom

#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
spa

 7.7.1 數據結構itimerval指針

  雖然,在內核中間隔定時器的間隔計數器是以時鐘滴答次數爲單位,可是讓用戶以時鐘滴答 爲單位來指定間隔定時器的間隔計數器的初值顯然是不太方便的,由於用戶習慣的時間單位是秒、毫秒或微秒等。因此Linux定義了數據結構 itimerval來讓用戶以秒或微秒爲單位指定間隔定時器的時間間隔值。其定義以下(include/linux/time.h):code

struct itimerval {
struct timeval it_interval; /* timer interval */
struct timeval it_value; /* current value */
};
索引

其中,it_interval成員表示間隔計數器的初始值,而it_value成員表示間隔計數器的當前值。這兩個成員都是timeval結構類型的變量,所以其精度能夠達到微秒級。

  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.7.2 真實間隔定時器ITIMER_REAL的底層運行機制

  間隔定時器ITIMER_VIRT和ITIMER_PROF的底層運行機制是分別經過函數do_it_virt()函數和do_it_prof()函數來實現的,這裏就再也不重述(能夠參見7.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.7.3 itimer定時器的系統調用

   與itimer定時器相關的syscall有兩個:getitimer()和setitimer()。其中,getitimer()用於查詢調用進程的 三個間隔定時器的信息,而setitimer()則用來設置調用進程的三個間隔定時器。這兩個syscall都是如今kernel/itimer.c文件 中。

  7.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(¤t->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.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;
= tvtojiffies(&value->it_interval);
= tvtojiffies(&value->it_value);
if (ovalue && (= do_getitimer(which, ovalue)) < 0)
return k;
switch (which) {
case ITIMER_REAL:
del_timer_sync(¤t->real_timer);
current->it_real_value = j;
current->it_real_incr = i;
if (!j)
break;
if (> (unsigned long) LONG_MAX)
= LONG_MAX;
= j + jiffies;
current->real_timer.expires = i;
add_timer(¤t->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.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秒)。

相關文章
相關標籤/搜索