linux 非強制中斷機制(1)

來源:互聯網
上載者:User

中斷服務程式往往都是在CPU關中斷的條件下執行的,以避免中斷嵌套而使控制複雜化。但是CPU關中斷的時間不能太長,否則容易丟失中斷訊號。為此, Linux將中斷服務程式一分為二,各稱作“Top Half”和“Bottom Half”。前者通常對時間要求較為嚴格,必須在插斷要求發生後立即或至少在一定的時間限制內完成。因此為了保證這種處理能原子地完成,Top Half通常是在CPU關中斷的條件下執行的。具體地說,Top Half的範圍包括:從在IDT中登記的中斷入口函數一直到驅動程式註冊在中斷服務隊列中的ISR。而Bottom Half則是Top Half根據需要來調度執行的,這些操作允許延遲到稍後執行,它的時間要求並不嚴格,因此它通常是在CPU開中斷的條件下執行的。

但是, Linux的這種Bottom Half(以下簡稱BH)機制有兩個缺點,也即:(1)在任意一時刻,系統只能有一個CPU可以執行Bottom Half代碼,以防止兩個或多個CPU同時來執行Bottom Half函數而相互幹擾。因此BH代碼的執行是嚴格“序列化”的。(2)BH函數不允許嵌套。

這兩個缺點在單CPU系統中是無關緊要的,但在SMP系統中卻是非常致命的。因為BH機制的嚴格序列化執行顯然沒有充分利用SMP系統的多CPU特點。為此,Linux2.4核心在BH機制的基礎上進行了擴充,這就是所謂的“非強制中斷請求”(softirq)機制。

Linux 的softirq機制是與SMP緊密不可分的。為此,整個softirq機制的設計與實現中自始自終都貫徹了一個思想:“誰觸發,誰執行”(Who marks,Who runs),也即觸發非強制中斷的那個CPU負責執行它所觸發的非強制中斷,而且每個CPU都由它自己的非強制中斷觸發與控制機制。這個設計思想也使得softirq 機制充分利用了SMP系統的效能和特點。

Linux在include/linux/interrupt.h標頭檔中定義了資料結構softirq_action,來描述一個非強制中斷請求,如下所示:

/* softirq mask and active fields moved to irq_cpustat_t in* asm/hardirq.h to get better cache usage. KAO*/struct softirq_action{void (*action)(struct softirq_action *);  void *data;}

其中,函數指標action指向非強制中斷請求的服務函數,而指標data則指向由服務函數自行解釋的資料。

基於上述非強制中斷描述符,Linux在kernel/softirq.c檔案中定義了一個全域的softirq_vec[32]數組:

static struct softirq_action softirq_vec[32] __cacheline_aligned;

在這裡系統一共定義了32個非強制中斷請求描述符。非強制中斷向量i(0≤i≤31)所對應的非強制中斷請求描述符就是softirq_vec[i]。這個數組是個系統全域數組,也即它被所有的CPU所共用。這裡需要注意的一點是:每個CPU雖然都由它自己的觸發和控制機制,並且只執行他自己所觸發的非強制中斷請求,但是各個CPU所執行的非強制中斷服務常式卻是相同的,也即都是執行softirq_vec[]數組中定義的非強制中斷服務函數。

要實現“誰觸發,誰執行”的思想,就必須為每個CPU都定義它自己的觸發和控制變數。為此,Linux在include/asm-i386/hardirq.h標頭檔中定義了資料結構irq_cpustat_t來描述一個CPU的中斷統計資訊,其中就有用於觸發和控制非強制中斷的成員變數。資料結構irq_cpustat_t 的定義如下:

/* entry.S is sensitive to the offsets of these fields */ typedef struct {  unsigned int __softirq_active;  unsigned int __softirq_mask;   unsigned int __local_irq_count;  unsigned int __local_bh_count;   unsigned int __syscall_count;   unsigned int __nmi_count; /* arch dependent */ } ____cacheline_aligned irq_cpustat_t; 

結構中每一個成員都是一個32位的不帶正負號的整數。其中__softirq_active和__softirq_mask就是用於觸發和控制非強制中斷的成員變數。

①__softirq_active變數:32位的不帶正負號的整數,表示非強制中斷向量0~31的狀態。如果bit[i](0≤i≤31)為1,則表示非強制中斷向量i在某個CPU上已經被觸發而處於active狀態;為0表示處於非活躍狀態。

②__softirq_mask變數:32位的不帶正負號的整數,非強制中斷向量的屏蔽掩碼。如果bit[i](0≤i≤31)為1,則表示使能(enable)非強制中斷向量i,為0表示該非強制中斷向量被禁止(disabled)。

根據系統中當前的CPU個數(由宏NR_CPUS表示),Linux在kernel/softirq.c檔案中為每個CPU都定義了它自己的中斷統計資訊結構,如下所示:

/* No separate irq_stat for s390, it is part of PSA */#if !defined(CONFIG_ARCH_S390) irq_cpustat_t irq_stat[NR_CPUS]; #endif /* CONFIG_ARCH_S390 */

這樣,每個CPU都只操作它自己的中斷統計資訊結構。假設有一個編號為id的CPU,那麼它只能操作它自己的中斷統計資訊結構irq_stat[id](0≤id≤NR_CPUS-1),從而使各CPU之間互不影響。這個數組在include/linux/irq_cpustat.h標頭檔中也作了原型聲明。

l 觸發非強制中斷請求的操作函數

函數__cpu_raise_softirq()用於在編號為cpu的處理器上觸發非強制中斷向量nr。它通過將相應的__softirq_active成員變數中的相應位設定為1來實現非強制中斷觸發。如下所示(include/linux/interrupt.h):

static inline void __cpu_raise_softirq(int cpu, int nr) {  softirq_active(cpu) |= (1<<nr); } 

為了保證“原子”性地完成非強制中斷的觸發過程,Linux在interrupt.h標頭檔中對上述內嵌函式又作了高層封裝,也即函數 raise_softirq()。該函數向下通過調用__cpu_raise_softirq()函數來實現非強制中斷的觸發,但在調用該函數之前,它先通過 local_irq_save()函數來關閉當前CPU的中斷並儲存標誌寄存器的內容,如下所示:

/* I do not want to use atomic variables now, so that cli/sti */static inline void raise_softirq(int nr){  unsigned long flags;  local_irq_save(flags);   __cpu_raise_softirq(smp_processor_id(), nr);   local_irq_restore(flags); } 

在非強制中斷向量0~31中,Linux核心僅僅使用了非強制中斷向量0~3,其餘被留待系統以後擴充。Linux在標頭檔include/linux/interrupt.h中對非強制中斷向量0~3進行了預定義:

/* 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,  NET_TX_SOFTIRQ,  NET_RX_SOFTIRQ,  TASKLET_SOFTIRQ}

其中,非強制中斷向量0(即HI_SOFTIRQ)用於實現高優先順序的非強制中斷,如:高優先順序的tasklet(將在後面詳細描述)。非強制中斷向量1和2則分別用於網路資料的發送與接收。非強制中斷向量3(即TASKLET_SOFTIRQ)則用於實現諸如tasklet這樣的一般性非強制中斷。關於tasklet我們將在後面詳細描述。NOTE!Linix核心並不鼓勵一般使用者擴充使用剩餘的非強制中斷向量,因為它認為其預定義的非強制中斷向量HI_SOFTIRQ和 TASKLET_SOFTIRQ已經足夠應付絕大多數應用。

函數softirq_init()完成softirq機制的初始化。該函數由核心啟動常式start_kernel()所調用。函數源碼如下所示(kernel/softirq.c):

void __init softirq_init(){  int i;  for (i=0; i<32; i++)    tasklet_init(bh_task_vec+i, bh_action, i);  open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);  open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); } 

初始化的過程如下:

(1)先用一個for迴圈來初始化用於實現BH機制的bh_task_vec[32]數組。這一點我們將在後面詳細解釋。

(2)調用open_softirq()函數開啟使用非強制中斷向量TASKLET_SOFTIRQ和HI_SOFTIRQ,並將它們的非強制中斷服務函數指標分別指向 tasklet_action()函數和tasklet_hi_action()函數。函數open_softirq()的主要作用是初始化設定非強制中斷請求描述符softirq_vec[nr]。

函數open_softirq()用於開啟一個指定的非強制中斷向量nr,也即適當地初始化非強制中斷向量nr所對應的非強制中斷描述符softirq_vec[nr]。它主要做兩件事情:(1)初始化設定非強制中斷向量nr所對應的非強制中斷描述符 softirq_vec[nr]。(2)將所有CPU的非強制中斷屏蔽掩碼變數__softirq_mask中的對應位設定為1,以使能該非強制中斷向量。該函數的源碼如下所示(kernel/softirq.c):

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) {  unsigned long flags;   int i;   spin_lock_irqsave(&softirq_mask_lock, flags);   softirq_vec[nr].data = data;   softirq_vec[nr].action = action;   for (i=0; i<NR_CPUS; i++)   softirq_mask(i) |= (1<<nr);   spin_unlock_irqrestore(&softirq_mask_lock, flags); } 

函數do_softirq()負責執行數組softirq_vec[32]中設定的非強制中斷服務函數。每個CPU都是通過執行這個函數來執行非強制中斷服務的。由於同一個CPU上的非強制中斷服務常式不允許嵌套,因此,do_softirq()函數一開始就檢查當前CPU是否已經正出在中斷服務中,如果是則 do_softirq()函數立即返回。舉個例子,假設CPU0正在執行do_softirq()函數,執行過程產生了一個高優先順序的硬體中斷,於是 CPU0轉去執行這個高優先順序中斷所對應的中斷服務程式。總所周知,所有的中斷服務程式最後都要跳轉到do_IRQ()函數並由它來依次執行中斷服務隊列中的ISR,這裡我們假定這個高優先順序中斷的ISR請求觸發了一次非強制中斷,於是do_IRQ()函數在退出之前看到有非強制中斷請求,從而調用 do_softirq()函數來服務非強制中斷請求。因此,CPU0再次進入do_softirq()函數(也即do_softirq()函數在CPU0上被重入了)。但是在這一次進入do_softirq()函數時,它馬上發現CPU0此前已經處在中斷服務狀態中了,因此這一次do_softirq()函數立即返回。於是,CPU0回到該開始時的do_softirq()函數繼續執行,並為高優先順序中斷的ISR所觸發的非強制中斷請求補上一次服務。從這裡可以看出,do_softirq()函數在同一個CPU上的執行是串列的。

函數源碼如下(kernel/softirq.c):

asmlinkage void do_softirq(){  int cpu = smp_processor_id();  __u32 active, mask;  if (in_interrupt())    return;  local_bh_disable();  local_irq_disable();  mask = softirq_mask(cpu);  active = softirq_active(cpu) & mask;  if (active) {    struct softirq_action *h;restart:    /* Reset active bitmask before enabling irqs */    softirq_active(cpu) &= ~active;    local_irq_enable();    h = softirq_vec;    mask &= ~active;    do {      if (active & 1)         h->action(h);      h++;      active >>= 1;    } while (active);    local_irq_disable();    active = softirq_active(cpu);    if ((active &= mask) != 0)      goto retry;  }  local_bh_enable();  /* Leave with locally disabled hard irqs. It is critical to close  * window for infinite recursion, while we help local bh count,  * it protected us. Now we are defenceless.  */  return;retry:  goto restart;} 

結合上述源碼,我們可以看出非強制中斷服務的執行過程如下:

(1)調用宏in_interrupt()來檢測當前CPU此次是否已經處於中斷服務中。該宏定義在hardirq.h,請參見5.7節。

(2)調用local_bh_disable()宏將當前CPU的中斷統計資訊結構中的__local_bh_count成員變數加1,表示當前CPU已經處在非強制中斷服務狀態。

(3)由於接下來要讀寫當前CPU的中斷統計資訊結構中的__softirq_active變數和__softirq_mask變數,因此為了保證這一個操作過程的原子性,先用local_irq_disable()宏(實際上就是cli指令)關閉當前CPU的中斷。

(4)然後,讀當前CPU的__softirq_active變數值和__softirq_mask變數值。當某個非強制中斷向量被觸發時(即 __softirq_active變數中的相應位被置1),只有__softirq_mask變數中的相應位也為1時,它的非強制中斷服務函數才能得到執行。因此,需要將__softirq_active變數和__softirq_mask變數作一次“與”邏輯操作。

(5)如果active變數非 0,說明需要執行非強制中斷服務函數。因此:①先將當前CPU的__softirq_active中的相應位清零,然後用local_irq_enable ()宏(實際上就是sti指令)開啟當前CPU的中斷。②將局部變數mask中的相應位清零,其目的是:讓do_softirq()函數的這一次執行不對同一個非強制中斷向量上的再次非強制中斷請求進行服務,而是將它留待下一次do_softirq()執行時去服務,從而使do_sottirq()函數避免陷入無休止的非強制中斷服務中。③用一個do{}while迴圈來根據active的值去執行相應的非強制中斷服務函數。④由於接下來又要檢測當前CPU的 __softirq_active變數,因此再一次調用local_irq_disable()宏關閉當前CPU的中斷。⑤讀取當前CPU的 __softirq_active變數的值,並將它與局部變數mask進行與操作,以看看是否又有其他非強制中斷服務被觸發了(比如前面所說的那種情形)。如果有的話,那就跳轉到entry程式段(實際上是跳轉到restart程式段)重新執行非強制中斷服務。如果沒有的話,那麼此次非強制中斷服務過程就宣告結束。

(6)最後,通過local_bh_enable()宏將當前CPU的__local_bh_count變數值減1,表示當前CPU已經離開非強制中斷服務狀態。宏local_bh_enable()也定義在include/asm-i386/softirq.h標頭檔中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.