1. 程式人生 > >Linux時間子系統(六) POSIX timer

Linux時間子系統(六) POSIX timer

reat ret sched 根據 href 完成 ask 發送 重要

一、前言

在用戶空間接口函數文檔中,我們描述了和POSIX timer相關的操作,主要包括創建一個timer、設定timer、獲取timer的狀態、獲取timer overrun的信息、刪除timer。本文將沿著這些用戶空間的接口定義來看看內核態的實現。雖然POSIX timer可以基於各種不同的clock創建,本文主要描述real time clock相關的timer。

本文第二章描述了POSIX timer的基本原理,第三章描述系統調用的具體實現,第四章主要講real time clock的timer callback函數的實現,第五章介紹了timer超期後,內核如何處理信號。

二、基本概念和工作原理

1、如何標識POSIX timer

POSIX.1b interval timer(後面的文章中簡稱POSIX timer)是用來替代傳統的interval timer的,posix timer一個重要的改進是進程可以創建更多(而不是3個)timer,既然可以創建多個timer,那麽就存在標識問題,我們用timer ID來標識一個具體的posix timer。這個timer ID也作為一個handler參數在用戶空間和內核空間之間傳遞。

posix timer是一種資源,它隸屬於某一個進程,。對於kernel,我們會用timer ID來標識一個POSIX timer,而這個ID是由進程自己管理和分配的。在進程控制塊(struct task_struct )中有一個struct signal_struct *signal的成員,用來管理和signal相關的控制數據。timer的處理和信號的發送是有關系的,因此也放到該數據結構中:

……
int posix_timer_id;
……

一個進程在fork的時候,posix_timer_id會被設定為0,因此,對於一個進程而言,其timer ID從0開始分配,隨後會依次加一,達到最大值後會從0開始。由此可見,timer ID不是一個全局唯一標識符,只是能保證在一個進程內,其ID是唯一的。實際timer ID的分配算法可以參考posix_timer_add函數,如下:

static int posix_timer_add(struct k_itimer *timer)
{
struct signal_struct *sig = current->signal;
int first_free_id = sig->posix_timer_id;----------------(1)
struct hlist_head *head;
int ret = -ENOENT;

do {-------------------------------(2)
spin_lock(&hash_lock);
head = &posix_timers_hashtable[hash(sig, sig->posix_timer_id)];----(3)
if (!__posix_timers_find(head, sig, sig->posix_timer_id)) {--------(4)
hlist_add_head_rcu(&timer->t_hash, head);
ret = sig->posix_timer_id;
}
if (++sig->posix_timer_id < 0)--------------------(5)
sig->posix_timer_id = 0;
if ((sig->posix_timer_id == first_free_id) && (ret == -ENOENT))------(6)
ret = -EAGAIN;
spin_unlock(&hash_lock);
} while (ret == -ENOENT);
return ret;
}

(1)sig->posix_timer_id中記錄了上一次分配的ID+1,該值被認為是下一個可以使用的free ID(當然,這個假設不一定成立,但是有很大的機會),也就是本次scan free timer ID的起點位置。

(2)do while是一個循環過程,如果選定的timer ID不是free的,我們還需要++sig->posix_timer_id,以便看看下一個timer ID是否是free的,這個過程不斷的循環執行,直到找到一個free的timer ID,或者出錯退出循環。一旦找到free的timer ID,則將該posix timer插入哈希表。

(3)根據分配的timer ID和該進程的signal descriptor的地址,找到該posix timer的hash鏈表頭

(4)看看該進程中是否已經有了該timer ID的posix timer存在,如果沒有,那麽timer ID分配完成

(5)否則,看看下一個timer ID的情況。如果溢出(超過了INT_MAX),那麽從0開始搜索

(6)如果scan了一圈還是沒有找到free timer ID,那麽就出錯返回。

2、如何組織POSIX timer

static DEFINE_HASHTABLE(posix_timers_hashtable, 9);
static DEFINE_SPINLOCK(hash_lock);

隨著系統啟動和運行,各個進程會不斷的創建屬於自己的POSIX timer,這些timer被放到了一個全局的hash表中,也就是posix_timers_hashtable。該table共計有512個入口,每個入口都是一個POSIX timer鏈表頭的指針。每一個系統中的POSIX timer都會根據其hash key放入到其中一個入口中(掛入鏈表)。具體hash key的計算方法是:

static int hash(struct signal_struct *sig, unsigned int nr)
{
return hash_32(hash32_ptr(sig) ^ nr, HASH_BITS(posix_timers_hashtable));
}

計算key考慮的factor包括timer ID值和進程signal descriptor的地址。

hash_lock是包含全局POSIX timer的鎖,每次訪問該資源的時候需要使用該鎖進行保護。

除了作為一個全局資源來管理的hash table,每個進程也會管理自己分配和釋放的timer資源,當然,這也是通過鏈表進行管理的,鏈表頭在該進程signal descriptor的posix_timers成員中:

……
struct list_head posix_timers;
……

一旦進程創建了一個timer,那麽就會掛入posix_timers的鏈表中。

3、如何抽象POSIX timer

在內核中用struct k_itimer 來描述一個POSIX timer:

struct k_itimer {
struct list_head list; --------------------------(1)
struct hlist_node t_hash;
spinlock_t it_lock; -----保護本數據結構的spin lock
clockid_t it_clock;----------------------------(2)
timer_t it_id;
int it_overrun; -----------------------------(3)
int it_overrun_last;
int it_requeue_pending; -------------------------(4)
#define REQUEUE_PENDING 1
int it_sigev_notify; ----------------------------(5)
struct signal_struct *it_signal; ----該timer對應的signal descriptor
union { ---------------------------------(6)
struct pid *it_pid; /* pid of process to send signal to */
struct task_struct *it_process; /* for clock_nanosleep */
};
struct sigqueue *sigq; ---超期後,該sigquue成員會掛入signal pending隊列
union { ---------------------------------(7)
struct {
struct hrtimer timer;
ktime_t interval;
} real;
struct cpu_timer_list cpu;
struct {
unsigned int clock;
unsigned int node;
unsigned long incr;
unsigned long expires;
} mmtimer;
struct {
struct alarm alarmtimer;
ktime_t interval;
} alarm;
struct rcu_head rcu;
} it;
};

(1)這兩個成員都是和POSIX timer的組織有關。t_hash是鏈接入全局hash table的節點,而list成員是和進程管理自己創建和釋放timer的鏈表相關。

(2)這兩個成員描述了POSIX timer的基本信息的。任何一個timer都是基於clock而構建的,it_clock說明該timer是以系統中哪一個clock為標準來計算超時時間。it_id描述了該timer的ID,在一個進程中唯一標識該timer。

(3)理解這兩個成員首先對timer overrun的概念要理解。對overrun的解釋我們可以用信號異步通知的例子來描述(創建進程執行callback函數也是一樣的)。假設我們當一個POSIX timer超期後,會發送信號給進程,但是也有可能該信號當前被mask而導致signal handler不會調度執行(當然也有其他的場景導致overrun,這裏就不描述了)。這樣,我們當然想知道這種timer的overrun的次數。假設一個timer設定超期時間是1秒,那當timer超期後,會產生一個pending的signal,但是由於種種原因,在3秒後,信號被進程捕獲到,調用signal handler,這時候overrun的次數就是2次。用戶空間可以通過timer_getoverrun來獲取這個overrun的次數。

根據POSIX標準,當信號被遞交給進程後,timer_getoverrun才會返回該timer ID的overrun count,因此在kernel中需要兩個成員,只有信號還沒有遞交給進程,it_overrun就會不斷的累積,一旦完成遞交,it_overrun會保存在it_overrun_last成員中,而自己會被清除,準備進行下一次overrun count的計數。因此,實際上timer_getoverrun函數實際上是獲取it_overrun_last的數據,代碼如下:

SYSCALL_DEFINE1(timer_getoverrun, timer_t, timer_id)
{
……

overrun = timr->it_overrun_last;
……

return overrun;
}

(4)it_requeue_pending標識了該timer對應信號掛入signal pending的狀態。該flag的LSB bit標識該signal已經掛入signal pending隊列,其他的bit作為信號的私有數據。下面的代碼會更詳細的描述。

(5)it_sigev_notify成員說明了timer超期後如何異步通知該進程(線程)。定義如下:

#define SIGEV_SIGNAL 0 -----使用向進程發送信號的方式來通知
#define SIGEV_NONE 1 ------沒有異步通知事件,用戶空間的程序用輪詢的方法
#define SIGEV_THREAD 2 ----異步通知的方式是創建一個新線程來執行callback函數
#define SIGEV_THREAD_ID 4 -----使用向指定線程發送信號的方式來通知

(6)這個成員用來標識進程。

(7)it這個成員是一個union類型的,用於描述和timer interval相關的信息,不同類型的timer選擇使用不同的成員數據。alarm是和alarm timer相關的成員,具體可以參考alarm timer的文檔。(mmtimer不知道用在什麽場合,可能和Multimedia Timer相關)。real用於real time clock的場景。real time clock的timer是構建在高精度timer上的(timer成員),而interval則描述該timer的mode,如果是one shot類型的,interval等於0,否則interval描述周期性觸發timer的時間間隔。更詳細的內容會在本文後面的小節中描述。

三、和POSIX timer相關的系統調用

1、創建timer的系統調用。具體代碼如下:

SYSCALL_DEFINE3(timer_create, const clockid_t, which_clock,
struct sigevent __user *, timer_event_spec,
timer_t __user *, created_timer_id)
{
struct k_clock *kc = clockid_to_kclock(which_clock);--根據clock ID獲取內核中的struct k_clock
struct k_itimer *new_timer;
int error, new_timer_id;
sigevent_t event;
int it_id_set = IT_ID_NOT_SET;

new_timer = alloc_posix_timer();-----分配一個POSIX timer,所有成員被初始化為0

spin_lock_init(&new_timer->it_lock);
new_timer_id = posix_timer_add(new_timer);-----------(1)

it_id_set = IT_ID_SET;
new_timer->it_id = (timer_t) new_timer_id;
new_timer->it_clock = which_clock;
new_timer->it_overrun = -1; -------------------(2)

if (timer_event_spec) {
if (copy_from_user(&event, timer_event_spec, sizeof (event))) {-----拷貝用戶空間的參數
error = -EFAULT;
goto out;
}
rcu_read_lock();
new_timer->it_pid = get_pid(good_sigevent(&event));--------(3)
rcu_read_unlock();
} else {
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGALRM;
event.sigev_value.sival_int = new_timer->it_id;
new_timer->it_pid = get_pid(task_tgid(current));----------(4)
}

new_timer->it_sigev_notify = event.sigev_notify;
new_timer->sigq->info.si_signo = event.sigev_signo; --信號ID
new_timer->sigq->info.si_value = event.sigev_value;
new_timer->sigq->info.si_tid = new_timer->it_id; ---信號發送的目的地線程ID
new_timer->sigq->info.si_code = SI_TIMER; -------------(5)

if (copy_to_user(created_timer_id,
&new_timer_id, sizeof (new_timer_id))) {-------------(6)
error = -EFAULT;
goto out;
}

error = kc->timer_create(new_timer);------調用具體clock的create timer函數

spin_lock_irq(¤t->sighand->siglock);
new_timer->it_signal = current->signal;
list_add(&new_timer->list, ¤t->signal->posix_timers);-------(7)
spin_unlock_irq(¤t->sighand->siglock);

return 0;
}

(1)將該timer加入到全局的哈希表中。當然,在加入之前,要分配一個timer ID,內核要確保該timer ID是在本進程內能唯一標識該timer。

(2)初始化該posix timer,設定timer ID,clock ID以及overrun的值。it_id_set這個變量主要用於出錯處理,如果其值等於IT_ID_SET,說明已經完成插入全局的哈希表的操作,那麽其後的出錯處理要有從全局的哈希表中摘除該timer的操作(註意:上面的代碼省略了出錯處理,有興趣的讀者可以自行閱讀)。

(3)good_sigevent這個函數主要是用來進行參數檢查。用戶空間的程序可以通過sigevent_t的數據結構來控制timer超期之後的行為。例如可以向某一個指定的線程(不是進程)發送信號(sigev_notify設定SIGEV_THREAD_ID並且設定SIGEV_SIGNAL),當然這時候要傳遞thread ID的信息。內核會根據這個thread ID來尋找對應的struct task_struct,如果找不到,那麽說明用戶空間傳遞的參數有問題。如果該thread ID對應的struct task_struct的確存在,那麽還需要該thread ID對應的thread和當前thread屬於同一個進程。此外,一旦程序打算用signal通知的方式來進行timer超期通知,那麽傳入的sigev_signo參數必須是一個有效的signal ID。如果這些檢查通過,那麽good_sigevent返回適當的pid信息。這裏有兩種場景,一種是指定thread ID,另外一種是發送給當前進程(實際上是返回當前的線程組leader)

(4)如果用戶空間的程序沒有指定sigevent_t的參數,那麽內核的缺省行為是發送SIGALRM給調用線程所屬的線程組leader。

(5)初始化信號發送相關的數據結構。SI_TIMER用來標識該信號是由於posix timer而產生的。

(6)將分配的timer ID 拷貝回用戶空間

(7)建立posix timer和當前進程signal descriptor的關系(所有線程共享一個signal descriptor)

2、獲取一個posix timer剩余時間的系統調用,代碼如下:

SYSCALL_DEFINE2(timer_gettime, timer_t, timer_id,
struct itimerspec __user *, setting)
{
struct itimerspec cur_setting;
struct k_itimer *timr;
struct k_clock *kc;
unsigned long flags;
int ret = 0;

timr = lock_timer(timer_id, &flags);--------根據timer ID找到對應的posix timer

kc = clockid_to_kclock(timr->it_clock);------根據clock ID獲取內核中的struct k_clock


if (WARN_ON_ONCE(!kc || !kc->timer_get))
ret = -EINVAL;
else
kc->timer_get(timr, &cur_setting); ------調用具體clock的get timer函數

unlock_timer(timr, flags);

if (!ret && copy_to_user(setting, &cur_setting, sizeof (cur_setting))) --將結果copy到用戶空間
return -EFAULT;

return ret;
}

3、timer_getoverrun、timer_settime和timer_delete

這三個系統調用都非常簡單,這裏就不細述了,有興趣的讀者可以自行閱讀。

四、real time clock的timer callback函數

對於real time base的那些clock(CLOCK_REALTIME、CLOCK_MONOTONIC等),其timer相關的函數都是構建在一個高精度timer的基礎上,這個高精度timer就是posix timer中的it.real.timer成員。

1、common_timer_create,代碼如下:

static int common_timer_create(struct k_itimer *new_timer)
{
hrtimer_init(&new_timer->it.real.timer, new_timer->it_clock, 0);
return 0;
}

代碼很簡單,就是初始化了一個高精度timer而已。具體高精度timer的內容可以參考本站其他文檔。

2、common_timer_set,代碼如下:

common_timer_set(struct k_itimer *timr, int flags,
struct itimerspec *new_setting, struct itimerspec *old_setting)
{
struct hrtimer *timer = &timr->it.real.timer;---獲取該posix timer對應的高精度timer
enum hrtimer_mode mode;

if (old_setting)
common_timer_get(timr, old_setting); ----獲取舊的timer設定,參考下節描述

timr->it.real.interval.tv64 = 0; -------初始化interval設定
if (hrtimer_try_to_cancel(timer) < 0)----馬上就要進行新的設定了,當然要停掉該高精度timer
return TIMER_RETRY;

timr->it_requeue_pending = (timr->it_requeue_pending + 2) &
~REQUEUE_PENDING;
timr->it_overrun_last = 0; ----------------------------(1)

if (!new_setting->it_value.tv_sec && !new_setting->it_value.tv_nsec)
return 0; ----如果新設定的時間值等於0的話,那麽該函數僅僅是停掉timer並獲取old value。

mode = flags & TIMER_ABSTIME ? HRTIMER_MODE_ABS : HRTIMER_MODE_REL; --(2)
hrtimer_init(&timr->it.real.timer, timr->it_clock, mode);
timr->it.real.timer.function = posix_timer_fn; -----高精度timer的mode,callback函數設定

hrtimer_set_expires(timer, timespec_to_ktime(new_setting->it_value)); --超期時間設定

timr->it.real.interval = timespec_to_ktime(new_setting->it_interval); ----------(3)

if (((timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE)) { --------(4)
if (mode == HRTIMER_MODE_REL) {
hrtimer_add_expires(timer, timer->base->get_time());
}
return 0;
}

hrtimer_start_expires(timer, mode); ----啟動高精度timer
return 0;
}

(1)it_overrun_last實際上是和timer_getoverrun的調用有關。在一個timer觸發後到異步通知完成之間可能會產生overrun,但是,一旦重新調用timer_settime之後,上次的overrun count要被清除。it_requeue_pending狀態flag中的信號私有數據加一(這個私有數據是[31:1],因此代碼中加2),並且清除pending flag。

(2)這裏的代碼都是對該posix timer對應的高精度timer進行各種設定。該timer的callback函數會在下一章分析

(3)設置interval的值,通過該值可以設定周期性timer,用戶空間傳入的參數是timespec,需轉換成ktime的時間格式

(4)對於輪詢類型的posix timer,我們並不會真正啟動該timer(插入到高精度timer的紅黑樹中),而是僅僅為那些設定相對事件的timer配置正確的超期時間值。

3、common_timer_get,代碼如下:

static void common_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting)
{
ktime_t now, remaining, iv;
struct hrtimer *timer = &timr->it.real.timer;

memset(cur_setting, 0, sizeof(struct itimerspec));

iv = timr->it.real.interval; ---獲取該posix timer對應的timer period值

if (iv.tv64)---------------------------------(1)
cur_setting->it_interval = ktime_to_timespec(iv);---interval timer需返回timer period
else if (!hrtimer_active(timer) &&
(timr->it_sigev_notify & ~SIGEV_THREAD_ID) != SIGEV_NONE)-------(2)
return;

now = timer->base->get_time(); -----------------------(3)


if (iv.tv64 && (timr->it_requeue_pending & REQUEUE_PENDING ||
(timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE))
timr->it_overrun += (unsigned int) hrtimer_forward(timer, now, iv); --------(4)

remaining = ktime_sub(hrtimer_get_expires(timer), now); ---計算剩余時間
if (remaining.tv64 <= 0) { --已經超期
if ((timr->it_sigev_notify & ~SIGEV_THREAD_ID) != SIGEV_NONE)
cur_setting->it_value.tv_nsec = 1; --------------------(5)
} else
cur_setting->it_value = ktime_to_timespec(remaining); ---返回剩余時間信息
}

(1)posix timer的時間設定用struct itimerspec表示:

struct itimerspec {
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
};

如果it_interval等於0的話,那麽說明該posix timer是一個one shot類型的timer。如果非零的話,則說明該timer是一個periodic timer(或者稱之為interval timer),it_interval定義了周期性觸發的時間值。這個timer period值對應內核struct k_itimer中的it.real.interval成員。

(2)如果是one shot類型的timer,it_interval返回0值就OK了,我們只需要設定it_value值。對於通過信號進行異步通知的posix timer,如果對應的高精度timer已經不是active狀態了,那麽it_value值也是0,表示該timer已經觸發了。

(3)獲取當前時間點的值。不論timer當初是如何設定的:相對或者絕對,it_value總是返回相對於當前時間點的值,因此這裏需要獲取當前時間點的值。

(4)對於一個周期性觸發的timer,並且設定SIGEV_NONE,實際上,該timer是不會觸發的,都是用戶程序自己調用timer_gettime來輪詢情況,因此在get time函數中處理超期後,再次設定高精度timer的任務,同時計算overrun次數。

如果periodic timer設定信號異步通知的方式,那麽在信號pending到信號投遞到進程這段時間內,雖然由於各種情況可能導致這段時間很長,按理periodic timer應該多次觸發,但是實際上,信號只有在投遞到進程後才會再次restart高精度timer,因此在信號pending期間,如果用戶調用了timer_gettime,也需要自己處理timer的超期以及overrun。

(5)TODO。

4、common_timer_del。比較簡單,不再贅述。

五、和posix timer相關的信號處理

1、發送什麽信號?發向哪一個進程或者線程?

用戶空間的程序可以通過timer_create函數來創建timer,在創建timer的時候就設定了異步通知的方式(SIGEV_SIGNAL、SIGEV_NONE和SIGEV_THREAD),SIGEV_NONE方式比較簡單,沒有異步通知,用戶空間的程序自己需要調用timer_gettime來輪詢是否超期。SIGEV_THREAD則是創建一個線程來執行callback函數。我們這一章的場景主要描述的就是設定為SIGEV_SIGNAL方式,也就是timer超期後,發送信號來異步通知。缺省是發送給創建timer的進程,當然,也可以設定SIGEV_THREAD_ID的標識,發給一個該進程內的特定的線程。

一個指定進程的timer超期後,產生的信號會掛入該進程(線程)pending隊列,需要註意的是:在任意的時刻,特定timer的信號只會掛入一次,也就是說,該信號產生到該信號被投遞到進程之間,如果timer又一次超期觸發了,這時候,signal pending隊列不會再次掛入信號(即便該signal是一個real-time signal),只會增加overrun的次數。

2、信號的產生

在set timer函數中,內核會設定高精度timer的超期回調函數為posix_timer_fn,代碼如下:

static enum hrtimer_restart posix_timer_fn(struct hrtimer *timer)
{
struct k_itimer *timr;
unsigned long flags;
int si_private = 0;
enum hrtimer_restart ret = HRTIMER_NORESTART; -----------(1)

timr = container_of(timer, struct k_itimer, it.real.timer);-----------(2)
spin_lock_irqsave(&timr->it_lock, flags);

if (timr->it.real.interval.tv64 != 0)
si_private = ++timr->it_requeue_pending; ---------------(3)

if (posix_timer_event(timr, si_private)) { -----------------(4)
如果該signal的handler設定是ignor,那麽需要對interval類型的timer做特別處理
}
}

unlock_timer(timr, flags);
return ret;
}

(1)高精度timer的超期callback函數的返回值標識了是否需要再次將該timer掛入隊列,以便可以再次觸發timer。對於one shot類型的,需要返回HRTIMER_NORESTART,對於periodic timer,需要返回HRTIMER_RESTART。缺省設定不再次start該timer。

(2)POSIX timer對應的高精度timer是嵌入到k_itimer數據結構中的,通過container_of可以獲取該高精度timer對應的那個k_itimer數據。

(3)對於one shot類型的timer,不存在signal requeue的問題。對於周期性timer,有可能會有overrun的問題,這時候,需要傳遞一個signal的私有數據,以便在queue signal的時候進行標識。++timr->it_requeue_pending用來標記該timer處於pending狀態(加一就是將LSB設定為1)

(4)具體將信號掛入進程(線程)signal pending隊列的操作在posix_timer_event函數中,該函數會調用send_sigqueue函數進行具體操作。如下:

int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
{……

ret = 0;
if (unlikely(!list_empty(&q->list))) {--------是否已經掛入signal pending隊列?
q->info.si_overrun++;------------如果是,那麽增加overrun counter就OK了
return ret;
}
q->info.si_overrun = 0; ------首次掛入signal pending隊列,初始化overrun counter等於0
pending = group ? &t->signal->shared_pending : &t->pending;-掛入進程的還是線程的pending隊列
list_add_tail(&q->list, &pending->list);----掛入pending隊列
sigaddset(&pending->signal, sig);------設定具體哪一個signal pending
complete_signal(sig, t, group);-------設定TIF_SIGPENDING標記
……
}

如果信號已經正確的產生了,掛入進程或者線程的signal pending隊列(也有可能是僅僅增加overrun的計數),或者處理過程中發生了錯誤,posix_timer_event返回False,這時候整個處理就結束了。如果返回TRUE,說明該signal被進程ignor了。這時候需要一些特殊的處理。

相信大家已經註意到了,default的情況下,該高精度timer的callback返回HRTIMER_NORESTART,即便是periodic timer也是如此,難道periodic timer不需要restart高精度timer嗎?當然需要,只不過不是在這裏,在投遞信號的時候會處理的,具體可以參考dequeue_signal的處理。然而,如果一個periodic timer的信號處理是ignor類型的,那麽信號是不會掛入pending隊列的,這時候不會有信號的投遞,不會調用dequeue_signal,這時候則需要在這個callback函數中處理的。這時候會設定下一個超期時間,並返回HRTIMER_RESTART,讓高精度timer有機會重新掛入高精度timer的紅黑樹中。

3、信號投遞到進程

timer超期後會產生一個信號(配置了SIGEV_SIGNAL),這個信號雖然產生了,但是具體在什麽時間點被投遞到進程並執行signal處理函數呢?在ARM中斷處理過程文檔中,我們給出了一個場景(另外一個場景是系統調用返回用戶空間,這裏略過不表,思路是類似的),在返回用戶空間之前,中斷處理代碼會檢查struct thread_info中的flag標記,看看是否有_TIF_WORK_MASK的設定:

#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)

如果任何一個bit有設定,那麽就會調用do_work_pending來處理,如果設定了_TIF_SIGPENDING,那麽就調用do_signal來處理信號,屬於當前進程的pending signal會被一一處理,首先調用dequeue_signal,從隊列中取出信號,然後調用signal handler執行。相關的dequeue_signal代碼如下:

int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
{……
if ((info->si_code & __SI_MASK) == __SI_TIMER && info->si_sys_private) {
spin_unlock(&tsk->sighand->siglock);
do_schedule_next_timer(info);
spin_lock(&tsk->sighand->siglock);
}
return signr;
}

如果你想通過發生信號的方式進行異步通知,那麽必須要設定si_code為SI_TIMER。對於real time的clock,do_schedule_next_timer函數會調用schedule_next_timer來處理periodic timer的restart:

static void schedule_next_timer(struct k_itimer *timr)
{
struct hrtimer *timer = &timr->it.real.timer;

if (timr->it.real.interval.tv64 == 0)---one shot類型的,直接退出
return;

timr->it_overrun += (unsigned int) hrtimer_forward(timer,---設定下次超期時間並計算overrun次數
timer->base->get_time(),
timr->it.real.interval);

timr->it_overrun_last = timr->it_overrun;---保存該timer的overrun次數
timr->it_overrun = -1;----為下次初始化overrun
++timr->it_requeue_pending;------清除pending標記並增加信號私有數據域
hrtimer_restart(timer);----restart該timer
}

Linux時間子系統(六) POSIX timer