Linux時間子系統之(六):POSIX timer


專題文檔匯總目錄

Notes:首先講解了POSIX timer的標識(唯一識別)、POSIX Timer的組織(管理POSIX Timer)、內核中如何抽象POSIX Timer;然后分析了POSIX timer相關系統調用(timer_create/timer_gettime/timer_getoverrun/timer_settime/timer_delete);再后重點分析了real time timer的callback函數。POSIX timer超時有三種方式(SIGEV_SIGNAL、SIGEV_NONE和SIGEV_THREAD),重點分析了信號觸發方式。

原文地址: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

Notes:獲取POSIX timer的ID,保證進程內唯一的一個32為整數。

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

Notes:兩種組織方式全局hash表posix_timers_hashtable和進程管理的task->signal->posix_timers。

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 task_struct {

    struct signal_struct {

        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)這個成員用來標識進程。Notes:分為進程和線程兩種類型。

(7)it這個成員是一個union類型的,用於描述和timer interval相關的信息,不同類型的timer選擇使用不同的成員數據。alarm是和alarm timer相關的成員,具體可以參考alarm timer的文檔。(mmtimer不知道用在什么場合,可能和Multimedia Timer相關Notes:driver/char/mmtimer.c)。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)

Notes:如果(3)中從用戶空間獲取sigevent和it_pid成功,或者(4)中使用缺省值,(5)就將這些值賦給k_itimer,其中si_code為SI_TIMER表示為內核Timer超時發送的信號

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

Notes:由timer_id通過lock_timer得到內核定時器k_itimer,由k_itimer的clock類型通過clockid_to_klock得到k_clock,k_clock的成員函數和系統調用對應,此處為timer_get。

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函數

Notes:POSIX三種Timer類型對應的底層技術:hrtimer、cpu_timer_list、alarm。

對於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、信號的產生

Notes:POSIX hrtimer的超時函數是posix_timer_fn,posix_timer_fn-->posix_timer_event-->send_sigqueue,send_sigqueue進行具體的操作。

在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
}

原創文章,轉發請注明出處。蝸窩科技

http://www.wowotech.net/timer_subsystem/posix-timer.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM