所謂“間隔定時器(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_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()函數的實現。
(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頭文件中為上述三種進程間隔定時器定義了索引標識,如下所示:
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
7.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結構類型的變量,因此其精度可以達到微秒級。
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;
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(¤t->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(¤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秒)。