天天看點

下半部和推後執行的工作

版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/47834089

(一):下半部

下半部的任務就是執行與中斷處理密切相關但中斷處理程式本身不執行的工作.那麼有一些提示可以借鑒哪些工作放在上半部中執行,哪些工作放在下半部執行.

1:如果一個任務對時間非常敏感,将其放在中斷處理程式中進行
2:如果一個任務與硬體相關,将其放在中斷處理程式中進行
3:如果一個任務保證不被其他中斷打斷,将其放在中斷處理程式中進行
4:其他所有任務,考慮放在下半部執行
           

1:為什麼要用下半部

我們希望的是盡快減少中斷處理程式需要完成的工作量,因為他在運作的時候,目前中斷線在所有處理器上都會被屏蔽.更糟糕的是,如果一個中斷處理程式是IRQF_DISABLE的類型,他執行的時候會禁止所有本地中斷(而且把本地中斷線全局的屏蔽掉).而縮短中斷被屏蔽的時間對系統的響應能力和性能都至關重要.再加上中斷處理程式與其他程式異步執行,是以,我們必須盡力縮短中斷處理程式的執行.

2:下半部的環境

和上半部隻能通過中斷處理程式實作不同,下半部可以通過多種機制實作.這些用來實作下半部的機制分别由不同的接口和子系統組成.

1):”下半部”的起源

最早的linux隻提供”botton half”這種機制用于實作下半部,也被稱作”BH”.BH提供了一個靜态建立,有32個bottom halves組成的連結清單.上半部通過一個32位整數中的一位來辨別出哪個bottom half可以執行.每個BH都在全局範圍内進行同步.即使分屬于不同的處理器,也不允許任何兩個bottom half同時執行.這種機制使用友善卻不夠靈活,簡單卻有性能瓶頸.

2):任務隊列

後來,任務對來代替BH機制來實作下半部執行的工作.核心定義了一組隊列,其中每個隊列都包含一個有等待調用的函數組成的連結清單,根據其所在的位置,這些函數會在某個時刻執行.驅動程式可以把他們自己的下半部注冊到合适的隊列上.

3):軟中斷和tasklet

在2.3版本中,核心引入了軟中斷和tasklet.軟中斷是一組靜态定義的下半部接口,有32個,可以在所有處理器上同時執行--及時兩個類型相同也可以.tasklet是一種基于軟中斷實作的靈活性強,動态建立的下半部實作機制,兩個不同類型的tasklet可以在不同的處理器上同時執行,但類型相同的tasklet不能同時執行.tasklet其實是一種在性能和易用性之間尋求平衡的産物.對于大部分下半部處理來說,用tasklet就夠了.像網絡這樣對性能要求非常高的情況才需要使用軟中斷.但是,使用軟中斷需要小心,因為兩個相同的軟中斷有可能同時執行.同時,軟中斷必須在編譯期間就進行靜态注冊,但是tasklet可以通過代碼動态注冊.

在中間的發展過程中,一直到2.6版本,核心提供了三種不同形式的下半部實作機制:軟中斷,tasklet和工作隊列.

注意,核心定時器也可以實作将工作推後執行.核心定時器把操作推遲到某個确定的時間段之後執行.在剛剛的三種機制中,也需要使用核心定時器來确定推遲的時間段.

(二)軟中斷

首先需要指明的是,軟中斷為于kernel/softirq.c

1:軟中斷的實作

軟中斷在編譯期間是靜态配置設定的.她不像tasklet那樣能夠動态的注冊或者是登出.軟中斷由softirq_action結構表示,這個結構定義在

struct softirq_action
{
    void (*action)(struct softirq_action *);
};           

kernel/siftirq.c中定義了一個包含有32個該結構體的數組.

static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;           

每個注冊的軟中斷都占據該數組的一項,是以,最多可能有32個軟中斷.在2.6.34中,這32項隻用到其中的9項.

1):軟中斷處理程式

軟中斷處理程式action的函數原型如下:

void softirq_handler(struct softirq_action*)           

當核心執行一個軟中斷處理函數的時候,他就會執行這個action函數,其唯一的參數就是執行這個軟中斷的softirq_action結構體的指針.例如,如果my_softirq指向softirqvec數組中的某一項,則核心會調用如下方法來調用軟中斷處理程式中的函數.

my_softirq->action(my_softirq);           

在這裡傳遞整個結構體而不是傳遞數值有一些本身的優點,可以保證将來在結構體中加入新的域的時候,無須對所有的軟中斷處理程式都進行變動.如果需要,軟中斷處理程式可以友善的解析他們的參數,從資料成員中提取數值.

一個軟中斷不會搶占另外一個軟中斷,實際上,唯一可以搶占軟中斷的就是中斷處理程式.不過,其他的軟中斷可以在其他處理器上同時運作.

2):執行軟中斷

一個注冊的軟中斷必須在被标記之後才會執行.這被稱作出發軟中斷.通常,中斷處理程式會在傳回前标記他的軟中斷,使其在稍後執行.于是,在合适的時刻,軟中斷就會運作.在下列地方,待處理的軟中斷會被檢查和執行.

​1:從一個硬體中斷代碼處傳回時
​2:在ksoftirq核心線程中    ​
​3:在那些顯示檢查和執行待處理的軟中斷的代碼中,如網絡子系統中.不管是用什麼辦法喚起,軟中斷都要在do_softirq()中執行.這個函數比較簡單,如果有帶運作的軟中斷,do_softirq()會循環周遊每一個,調用他們的處理程式.
           

下面看一下do_softirq()函數的實作過程.

asmlinkage void __do_softirq(void)
{
    //軟中斷結構體
    struct softirq_action *h;
    //儲存待處理的軟中斷的32位位圖
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;
    //擷取目前待處理的軟中斷的32位位圖
    pending = local_softirq_pending();
    account_system_vtime(current);
    __local_bh_disable((unsigned long)__builtin_return_address(0));
    lockdep_softirq_enter();
    cpu = smp_processor_id();
restart:
    /* 由于目前位圖已經儲存下來了,是以可以允許中斷了*/
    /* 需要在允許中斷之前,重設待處理的位圖 */
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0);
    //允許中斷
    local_irq_enable();
    /* 使h指向數組的第一個 */
    h = softirq_vec;
    do {
        if (pending & 1) {
            int prev_count = preempt_count();
            kstat_incr_softirqs_this_cpu(h - softirq_vec);
            trace_softirq_entry(h, softirq_vec);
            //執行中斷處理函數
            h->action(h);
            trace_softirq_exit(h, softirq_vec);
            if (unlikely(prev_count != preempt_count())) {
                printk(KERN_ERR "huh, entered softirq %td %s %p"
                       "with preempt_count %08x,"
                       " exited with %08x?\n", h - softirq_vec,
                       softirq_to_name[h - softirq_vec],
                       h->action, prev_count, preempt_count());
                preempt_count() = prev_count;
            }
            rcu_bh_qs(cpu);
        }
        //移動到下一個位置,接着進行判斷
        h++;
        //同時位圖也要移動一個位置
        pending >>= 1;
        //由于位圖是32位的,數組長度也是32,是以他們是一一對應的.
    } while (pending);
    //禁止中斷
    local_irq_disable();
    //擷取目前待處理的軟中斷的32位位圖
    pending = local_softirq_pending();
    if (pending && --max_restart)
        goto restart;
    if (pending)
        wakeup_softirqd();
    lockdep_softirq_exit();
    account_system_vtime(current);
    _local_bh_enable();
}
#ifndef __ARCH_HAS_DO_SOFTIRQ
asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;
    if (in_interrupt())
        return;
    local_irq_save(flags);
    //擷取目前待處理的軟中斷的32位位圖
    pending = local_softirq_pending();
    //如果有待處理的軟中斷,也就是有被标記的軟中斷
    //則進行軟中斷執行
    if (pending)
        __do_softirq();
    local_irq_restore(flags);
}
#endif
           

這兩個函數檢查并且執行軟中斷.具體要做的包括:

​1):用局部變量pending儲存local_softirq_pending()宏的傳回值.他是待處理的軟中斷的32位位圖.如果第n位被設定為1,那麼第n位對應類型的軟中斷等待處理.
​2):現在待處理的軟中斷位圖已經被儲存,可以将實際的軟中斷位圖清零了
​3):将指針h指向softirq_vec的第一項
​4):如果pending的第一位被設定為1,則h->action(h)被調用
​5):指針加1,是以他現在指向softirq_vec數組的第二位
​6):位掩碼pending右移一位.這樣會丢棄第一位,然後讓其他各位以此向右移動一個位置.于是原來在第二位現在就在第一個位置上了.
​7):現在指針h指向數組的第二項,pending位掩碼的第二位現在也到了第一位上.重複上面的步驟.
​8):一直重複下去,知道pending變為0,這表明已經沒有待處理的軟中斷了,我們的任務也就完成了.
           

2:使用軟中斷

軟中斷保留給系統中對時間要求最嚴格以及最重要的下半部使用.目前隻有兩個子系統(網絡和SCSI)直接使用軟中斷.此外tasklet和核心定時器都是建立在軟中斷上的.tasklet相對于軟中斷來說,可以動态生成,對加鎖要求不高,性能也不錯.

1):配置設定索引

在編譯期間,通過在linux/interrupt.h中定義的一個枚舉類型來靜态的聲明軟中斷.核心用這些從0開始的索引來表示一種相對優先級.索引号小的軟中斷在索引号大的軟中斷之前執行.

建立一個新的軟中斷必須在此枚舉類型中加入新的項.而加入的時候,我們不能像在其他地方一樣,簡單的把新項加到清單的末尾.相反,你必須根據希望賦予它的優先級來決定加入的位置.習慣上,HI_SOFTIRQ作為第一項,RCU_SOFTIRQ作為第二項.

下面列出已有的tasklet類型.

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */
enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
};
           

上面的注釋說的是如果不是對時間要求特别高的話,tasklet就足夠滿足我們的要求了.

2):注冊你的處理程式

在運作的時候,通過調用open_softirq()注冊軟中斷處理程式,該函數有兩個參數:軟中斷索引号和處理函數.例如網絡子系統,在net/coreldev.c,通過以下方式注冊自己的軟中斷.

open_softirq(NET_TX_SOFTIRQ,net_tx_action);
open_softirq(NET_RX_SOFTIRQ,net_rx_action);           

軟中斷處理程式執行的時候,允許響應中斷.但他自己不能休眠.在一個處理程式運作的時候,目前處理器上的軟中斷被禁止.但是其他的處理器仍可以執行别的軟中斷.實際上,如果同一個軟中斷在他被執行的時候再次被觸發了,那麼另外一個處理器可以同時運作其處理程式.這意味着任何共享資料(甚至是僅在軟中斷處理程式内部使用的全局變量)都需要嚴格的鎖保護.tasklet隻不過是同一個處理程式的多個執行個體不能在多個處理器上同時運作.

3):觸發你的軟中斷

通過在枚舉類型的清單中添加新項以及調用open_softirq()進行注冊以後,新的中斷處理程式就能夠運作.raise_softirq()函數可以将一個軟中斷設定為挂起狀态,讓他在下次調用do_softirq()函數的時候投入運作.例如,網絡子系統可能會調用:

raise_softirq(NET_TX_SOFTIRQ);           

這會觸發NET_TX_SOFTIRQ軟中斷.他的處理程式net_tx_action()就會在核心下一次執行軟中斷的時候投入運作.該函數在觸發一個軟中斷之前先要禁止中斷,觸發後再恢複到原來的狀态.如果中斷本來就已經禁止了,那麼可以調用另一個函數raise_softirq_irqoff(),這會帶來一些優化效果.

/* 
 *  中斷已經禁止
 */
raise_softirq_irqoff(NET_TX_SOFTIRQ);           

在中斷處理程式中觸發軟中斷是最常見的形式.在這種情況下,中斷處理程式執行硬體裝置的相關操作,然後觸發相應的軟中斷,最後退出.核心在執行完中斷處理程式之後,馬上就會調用do_softirq()函數.于是軟中斷開始執行中斷處理程式留給他去完成的剩餘任務.

繼續閱讀