1. 程式人生 > >下半部和推後運行的工作

下半部和推後運行的工作

eno 最重要的 rpo bit handler 平衡 2個 自己的 timer

(一):下半部

下半部的任務就是運行與中斷處理密切相關但中斷處理程序本身不運行的工作.那麽有一些提示能夠借鑒哪些工作放在上半部中運行,哪些工作放在下半部運行.

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()函數.於是軟中斷開始運行中斷處理程序留給他去完畢的剩余任務.

下半部和推後運行的工作