整個linux協議棧是運行在軟中斷環境中,所以學習協議棧首先要了解軟中斷。第一節就總結一下linux內核中軟中斷的具體實現。
中斷的作用:
當一箇中斷信號到達時,CPU必須停止它當前正做的工作,轉而去做中斷要求其做的事情。
中斷分爲同步中斷和異步中斷兩種。
1、同步中斷又稱異常,是由CPU執行指令時由CPU控制單元產生的。異常又分兩種:
(1)、 一種是由程序執行出錯造成的,內核通過發送一個unix的信號來處理異常。
(2)、一種是由內核必須處理的異常條件產生的,比如缺頁異常,內核執行恢復異常的所有步驟。
2、異步中斷,通常我們就叫中斷。由其他硬件設備按照CPU時鐘信號隨機產生。
中斷處理程序的一般步驟:
一箇中斷處理程序的幾個中斷服務例程之間是串行執行的,並且在一箇中斷處理程序結束前,不應該再次出現這個中斷,所有一般中斷處理程序是先禁止該中斷,然後處理中斷,處理完成後在使能該中斷。
有一些中斷是可以延遲處理的,這種可延遲中斷可以在開中斷的情況下執行,執行時允許其他中斷搶佔他。把可延遲中斷從中斷處理程序中抽出來有助於使內核保持較短的響應時間。
Linux內核使用三種方法來處理這種可延遲的中斷任務:可延遲函數(軟中斷和tasklets)以及工作隊列。工作隊列是工作在進程上下文中,可以睡眠,軟中斷和tasklets 是工作在中斷上下文,不可以睡眠。本節只討論軟中斷和tasklets。
軟中斷:
Linux 2.6 版本使用如下幾個軟中斷,不同版本之間略有差異。但一下幾個不同版本都包含。
HI_SOFTIRQ=0, 處理高優先級的tasklet
TIMER_SOFTIRQ, 時鐘中斷相關的tasklet.
NET_TX_SOFTIRQ, 內核把數據報文傳送給網卡。
NET_RX_SOFTIRQ, 內核從網卡接收數據報文。
TASKLET_SOFTIRQ, 處理常規tasklet。
低下標代表高優先級。
內核中定義了softirq_vec數組來存放各種軟中斷。定義如下:
static struct softirq_action softirq_vec[32]__cacheline_aligned_in_smp;
數組元素爲 softirq_action,一個元素代碼一個軟中斷。不同的軟中斷號對應不同的數組的下標。
struct softirq_action
{
void (*action)(struct softirq_action *); //軟中斷髮生時執行軟中斷的處理函數。
void *data; //軟中斷的處理函數的參數指針。
};
初始化軟中斷時調用函數 open_softirq().如下代碼。
void open_softirq(int nr, void (*action)(struct softirq_action*),
void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
另外一個跟軟中斷相關的關鍵字段是 32 位的 preempt_counte字段,用它來跟蹤內核搶佔和內核控制路徑的嵌套,該字段放在每個進程描述符的 thread_info 字段中。用函數preempt_count()來返回該字段的值。
preempt_count字段 | |
位 | 描述 |
0~7 | 搶佔計數器,記錄顯示禁用本地cpu內核搶佔的次數,值爲0時代表內核允許搶佔。 |
8~15 | 軟中斷計數器。記錄軟中斷被禁用的次數,0表示軟中斷被激活。 |
16~27 | 硬中斷計數器。記錄硬中斷嵌套的層數。irq_entry()增加它的值,irq_exit()遞減它的值。 |
28 |
當內核明確不允許發生搶佔或內核正在中斷上下文中運行時,必須禁止內核的搶佔功能。爲了確定當前進程是否能夠被搶佔,內核快速檢查preempt_counte字段是否等於0.
另一個跟軟中斷相關的字段是每個CPU都有一個32位掩碼的字段
typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;
他描述掛起的軟中斷。每一位對應相應的軟中斷。比如0位代表HI_SOFTIRQ.
宏local_softirq_pending()來獲取該字段的值。
使用函數raise_softirq()來激活軟中斷。
即把響應的軟中斷號對應的__softirq_pending中的位置1.表示該軟中斷被掛起。如果當前CPU不在中斷上下文中,喚醒內核線程ksoftirqd來檢查被掛起的軟中斷,然後執行相應軟中斷處理函數。
內核在如下幾個點上檢查被掛起的軟中斷:
1、當調用local_bh_enable()函數激活本地CPU的軟中斷時。條件滿足就調用do_softirq() 來處理軟中斷。
2、當do_IRQ()完成硬中斷處理時調用irq_exit()時調用do_softirq()來處理軟中斷。
3、當一個特殊內核線程ksoftirq/n被喚醒時,處理軟中斷。
軟中斷處理函數詳解:
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
/*如果當前處於硬中斷中,在硬中斷處理函數退出時會調用irq_exit()函數來處理軟中斷,或當前軟中斷被禁用.所以in_interrupt()返回不爲1 就沒必要處理軟中斷,直接返回*/
if (in_interrupt())
return;
/*保持中斷寄存器的狀態並禁用本地CPU的中斷*/
local_irq_save(flags);
/*取得當前cpu上__softirq_pending字段,獲取本地CPU上掛起的軟中斷*/
pending = local_softirq_pending();
/*如果當前CPU上有掛起的軟中斷,執行__do_softirq()來處理軟中斷*/
if (pending)
{
__do_softirq();
}
/*恢復中斷寄存器的狀態*/
local_irq_restore(flags);
}
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART; //10
int cpu;
/*取得當前cpu上__softirq_pending字段,獲取本地CPU上掛起的軟中斷*/
pending = local_softirq_pending();
/*debug 用,不討論*/
account_system_vtime(current);
/*禁止本地cpu的軟中斷,現在本地cpu上掛起的軟中斷已經存入pending臨時變量中了*/
__local_bh_disable((unsigned long)__builtin_return_address(0));
/*debug 用,不討論*/
trace_softirq_enter();
/*取本地cpu id 號*/
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
/*清空本地cpu的__softirq_pending字段*/
set_softirq_pending(0);
/*開啓本地cpu的硬中斷*/
local_irq_enable();
/*循環執行被掛起的軟中斷處理函數。相應的軟中斷的處理函數存在數組softirq_ver[nr]中的元素 softirq_action->action中*/
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
/*禁止本地CPU的硬中斷*/
local_irq_disable();
/*取本地CPU的__softirq_pending,查看是否還有新的被掛起的軟中斷並且檢查被掛起軟中斷的次數小於10次,如果條件滿足,繼續處理新的被掛起的軟中斷*/
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
/*如果有新的掛起的軟中斷並且處理循環次數已經夠了10次,喚醒ksoftirq內核線程來處理軟中斷*/
if (pending)
wakeup_softirqd();
/*debug 用,不討論*/
trace_softirq_exit();
account_system_vtime(current);
/*使能本地CPU的軟中斷*/
_local_bh_enable();
}
本文出自 “耀洋的博客” 博客,請務必保留此出處http://yaoyang.blog.51cto.com/7657153/1260888