【轉】Linux 時鍾處理機制-4 -开发者知识库

【轉】Linux 時鍾處理機制-4 -开发者知识库,第1张

   3.3 添加或刪除軟件時鍾

    在了解了軟件時鍾的數據組織關系之后,現在來看一下如何添加以及刪除一個軟件時鍾。

    3.3.1 添加軟件時鍾

    在 Linux 內核中要添加一個軟件時鍾,首先必須分配 struct timer_list 類型的變量,然后調用函數 add_timer() 將該軟件時鍾添加到相應調用 add_timer 函數的 CPU 的 base 中。 Add_timer 是對函數 __mod_timer() 的一層包裝。函數 __mod_timer() 的代碼如清單3-2:


    清單3-2 __mod_timer 函數

  

 
int __mod_timer(struct timer_list *timer, unsigned long expires)
{
    struct tvec_base *base, *new_base;
    unsigned long flags;
    int ret = 0;
    ……
    base = lock_timer_base(timer, &flags);
    if (timer_pending(timer)) {
        detach_timer(timer, 0);
        ret = 1;
    }
    new_base = __get_cpu_var(tvec_bases);

    if (base != new_base) {
        if (likely(base->running_timer != timer)) {
            /* See the comment in lock_timer_base() */
            timer_set_base(timer, NULL);
            spin_unlock(&base->lock);
            base = new_base;
            spin_lock(&base->lock);
            timer_set_base(timer, base);
        }
    }
    timer->expires = expires;
    internal_add_timer(base, timer);
    spin_unlock_irqrestore(&base->lock, flags);
    return ret;
}

    代碼解釋:

     注:卸載軟件時鍾的意思是指將軟件時鍾從軟件時鍾所在 base 中刪除,以后所說的卸載軟件時鍾也都是這個意思


    取得軟件時鍾所在 base 上的同步鎖( struct tvec_base 變量中的自旋鎖),並返回該軟件時鍾的 base ,保存在 base 變量中
    如果該軟件時鍾處在 pending 狀態(在 base 中,准備執行),則卸載該軟件時鍾
    取得本 CPU 上的 base 指針(類型為 struct tvec_base* ),保存在 new_base 中
    如果 base 和 new_base 不一樣,也就是說軟件時鍾發生了遷移(從一個 CPU 中移到了另一個 CPU 上),那么如果該軟件時鍾的處理函數當前沒有在遷移之前的那個 CPU 上運行,則先將軟件時鍾的 base 設置為 NULL ,然后再將該軟件時鍾的 base 設置為 new_base 。否則,跳到5。
    設置軟件時鍾的到期時間
    調用 internal_add_timer 函數將軟件時鍾添加到軟件時鍾的 base 中(本 CPU 的 base )
    釋放鎖
    這里有必要詳細說明一下軟件時鍾如何被添加到軟件時鍾的 base 中的(添加到本 CPU base 的 tv1~tv5 里面),因為這是軟件時鍾處理的基礎。來看函數 internal_add_timer 函數的實現,如清單3-3


    清單3-3 internal_add_timer 函數

   

 
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
    unsigned long expires = timer->expires;
    unsigned long idx = expires - base->timer_jiffies;
    struct list_head *vec;
    if (idx < TVR_SIZE) {
        int i = expires & TVR_MASK;
        vec = base->tv1.vec   i;
    } else if (idx < 1 << (TVR_BITS   TVN_BITS)) {
        int i = (expires >> TVR_BITS) & TVN_MASK;
        vec = base->tv2.vec   i;
    } else if (idx < 1 << (TVR_BITS   2 * TVN_BITS)) {
        int i = (expires >> (TVR_BITS   TVN_BITS)) & TVN_MASK;
        vec = base->tv3.vec   i;
    } else if (idx < 1 << (TVR_BITS   3 * TVN_BITS)) {
        int i = (expires >> (TVR_BITS   2 * TVN_BITS)) & TVN_MASK;
        vec = base->tv4.vec   i;
    } else if ((signed long) idx < 0) {
        vec = base->tv1.vec   (base->timer_jiffies & TVR_MASK);
    } else {
        int i;
        if (idx > 0xffffffffUL) {
            idx = 0xffffffffUL;
            expires = idx   base->timer_jiffies;
        }
        i = (expires >> (TVR_BITS   3 * TVN_BITS)) & TVN_MASK;
        vec = base->tv5.vec   i;
    }
    list_add_tail(&timer->entry, vec);
}

    代碼解釋:

    計算該軟件時鍾的到期時間和 timer_jiffies (當前正在處理的軟件時鍾的到期時間)的差值,作為索引保存到 idx 變量中。
    判斷 idx 所在的區間,在
    [0, 【轉】Linux 時鍾處理機制-4 -开发者知识库,第2张]或者( 【轉】Linux 時鍾處理機制-4 -开发者知识库,第3张, 0)(該軟件時鍾已經到期),則將要添加到 tv1 中
   [【轉】Linux 時鍾處理機制-4 -开发者知识库,對象14,第4张, 【轉】Linux 時鍾處理機制-4 -开发者知识库,第5张],則將要添加到 tv2 中
   [【轉】Linux 時鍾處理機制-4 -开发者知识库,第6张, 【轉】Linux 時鍾處理機制-4 -开发者知识库,第7张],則將要添加到 tv3 中
    [【轉】Linux 時鍾處理機制-4 -开发者知识库,第8张, 【轉】Linux 時鍾處理機制-4 -开发者知识库,第9张],則將要添加到 tv4 中
    [【轉】Linux 時鍾處理機制-4 -开发者知识库,第10张, 【轉】Linux 時鍾處理機制-4 -开发者知识库,第11张),則將要添加到 tv5 中,但實際上最大值為 0xffffffffUL
    計算所要加入的具體位置(哪個鏈表中,即 tv1~tv5 的哪個子鏈表,參考圖3-1)
    最后將其添加到相應的鏈表中
    從這個函數可以得知,內核中是按照軟件時鍾到期時間的相對值(相對於 timer_jiffies 的值)將軟件時鍾添加到軟件時鍾所在的 base 中的。

    3.3.2 刪除軟件時鍾

    內核可調用 del_timer 函數刪除軟件時鍾, del_timer 的代碼如清單3-4


    清單3-4 del_timer 函數

   

int del_timer(struct timer_list *timer)
{
    struct tvec_base *base;
    unsigned long flags;
    int ret = 0;
    ……
    if (timer_pending(timer)) {
        base = lock_timer_base(timer, &flags);
        if (timer_pending(timer)) {
            detach_timer(timer, 1);
            ret = 1;
        }
        spin_unlock_irqrestore(&base->lock, flags);
    }
    return ret;
}

    代碼解釋:

    檢測該軟件時鍾是否處在 pending 狀態(在 base 中,准備運行),如果不是則直接函數返回
    如果處於 pending 狀態,則獲得鎖
    再次檢測軟件時鍾是否處於 pending 狀態(該軟件時鍾可能被卸載了),不是則釋放鎖然后函數返回
    如果還是 pending 狀態,則將其卸載,之后釋放鎖,函數返回
    如果在 SMP 系統中,則需使用 del_timer_sync 函數來刪除軟件時鍾。在講解 del_timer_sync 函數之前,先來看下 try_to_del_timer_sync 函數的實現(該函數被 del_timer_sync 函數使用),其代碼如清單3-5


    清單3-5 try_to_del_timer_sync 函數

   

int try_to_del_timer_sync(struct timer_list *timer)
{
    struct tvec_base *base;
    unsigned long flags;
    int ret = -1;
    base = lock_timer_base(timer, &flags);
    if (base->running_timer == timer)
        goto out;
    ret = 0;
    if (timer_pending(timer)) {
        detach_timer(timer, 1);
        ret = 1;
    }
out:
    spin_unlock_irqrestore(&base->lock, flags);
    return ret;
}

    該函數檢測當前運行的軟件時鍾是不是該軟件時鍾,如果是,則函數返回-1,表明目前不能刪除該軟件時鍾;如果不是檢測該軟件時鍾是否處於 pending 狀態,如果不是,則函數返回0,表明軟件時鍾已經被卸載,如果處於 pending 狀態再把軟件時鍾卸載,函數返回1,表明成功卸載該軟件時鍾。

    接下來,再來看看函數 del_timer_sync 定義,如清單3-6


    清單3-6 del_timer_sync 函數

int del_timer_sync(struct timer_list *timer)
{
    for (;;) {
        int ret = try_to_del_timer_sync(timer);
        if (ret >= 0)
            return ret;
        cpu_relax();
    }
}

    del_timer_sync 函數無限循環試圖卸載該軟件時鍾,直到該軟件時鍾能夠被成功卸載。從其實現中可以看出:如果一個軟件時鍾的處理函數正在執行時,對其的卸載操作將會失敗。一直等到軟件時鍾的處理函數運行結束后,卸載操作才會成功。這樣避免了在 SMP 系統中一個 CPU 正在執行軟件時鍾的處理函數,而另一個 CPU 則要將該軟件時鍾卸載所引發的問題。

    3.3 時鍾的軟中斷處理

    軟件時鍾的處理是在時鍾的軟中斷中進行的。

    3.3.1 軟中斷初始化

    軟中斷的一個重要的處理時機是在每個硬件中斷處理完成后(參見 irq_exit 函數),且由2.4節的內容可知:在硬件時鍾中斷處理中,會喚醒時鍾的軟中斷,所以每次硬件時鍾中斷處理函數執行完成后都要進行時鍾的軟中斷處理。和時鍾相關的軟中斷是 TIMER_SOFTIRQ ,其處理函數為 run_timer_softirq ,該函數用來處理所有的軟件時鍾。這部分初始化代碼在函數 init_timers 中進行,如清單3-7


    清單3-7 init_timers 函數

   

 
void __init init_timers(void)
{
    ……
    open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
}

    3.3.2 處理過程

    函數 run_timer_softirq 所作的工作就是找出所有到期的軟件時鍾,然后依次執行其處理函數。其代碼如清單3-8


    清單3-8 run_timer_softirq函數

   

static void run_timer_softirq(struct softirq_action *h)
{
    struct tvec_base *base = __get_cpu_var(tvec_bases);

    hrtimer_run_pending();
    if (time_after_eq(jiffies, base->timer_jiffies))
        __run_timers(base);
}

最佳答案:

本文经用户投稿或网站收集转载,如有侵权请联系本站。

发表评论

0条回复