linux cpu softirq,linux softirq机制
Copyright ? 2003 by 詹榮開
E-mail:zhanrk@sohu.com
Linux-2.4.0
Version 1.0.0,2003-2-14
摘要:本文主要從內核實現的角度分析了Linux 2.4.0內核的Softirq機制。本文是為那些想要了解Linux I/O子系統的讀者和Linux驅動程序開發人員而寫的。
關鍵詞:Linux、Softirq、軟中斷、Bottom half、設備驅動程序
申明:這份文檔是按照自由軟件開放源代碼的精神發布的,任何人可以免費獲得、使用和重新發布,但是你沒有限制別人重新發布你發布內容的權利。發布本文的目的是希望它能對讀者有用,但沒有任何擔保,甚至沒有適合特定目的的隱含的擔保。更詳細的情況請參閱GNU通用公共許可證(GPL),以及GNU自由文檔協議(GFDL)。
你應該已經和文檔一起收到一份GNU通用公共許可證(GPL)的副本。如果還沒有,寫信給:
The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA
歡迎各位指出文檔中的錯誤與疑問。
前言
中斷服務程序往往都是在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)機制。
6.1 軟中斷請求機制
Linux的softirq機制是與SMP緊密不可分的。為此,整個softirq機制的設計與實現中自始自終都貫徹了一個思想:“誰觸發,誰執行”(Who marks,Who runs),也即觸發軟中斷的那個CPU負責執行它所觸發的軟中斷,而且每個CPU都由它自己的軟中斷觸發與控制機制。這個設計思想也使得softirq 機制充分利用了SMP系統的性能和特點。
6.1.1 軟中斷描述符
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[]數組中定義的軟中斷服務函數。
6.1.2 軟中斷觸發機制
要實現“誰觸發,誰執行”的思想,就必須為每個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<
}
為了保證“原子”性地完成軟中斷的觸發過程,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);
}
6.1.3 Linux對軟中斷的預定義分類
在軟中斷向量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已經足夠應付絕大多數應用。
6.1.4 軟中斷機制的初始化
函數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]。
6.1.5 開啟一個指定的軟中斷向量
函數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
softirq_mask(i) |= (1<
spin_unlock_irqrestore(&softirq_mask_lock, flags);
}
6.1.6 軟中斷服務的執行函數do_softirq()
函數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頭文件中。
總結
以上是生活随笔為你收集整理的linux cpu softirq,linux softirq机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转基因猪肾脏成功移植到人身上 专家:证明
- 下一篇: Epic剁手指南 帮你盘点哪些折扣游戏值