歡迎光臨
每天分享高質量文章

Linux時間子系統之:POSIX timer

一、前言

在使用者空間介面函式檔案中,我們描述了和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
}

已同步到看一看
贊(0)

分享創造快樂