linux內(nèi)核軟中斷
中斷的作用:當一個中斷信號到達時,CPU必須停止它當前正做的工作,轉(zhuǎn)而去做中斷要求其做的事情。
中斷分為同步中斷和異步中斷兩種。
1、同步中斷又稱異常,是由CPU執(zhí)行指令時由CPU控制單元產(chǎn)生的。異常又分兩種:
?。?)、 一種是由程序執(zhí)行出錯造成的,內(nèi)核通過發(fā)送一個unix的信號來處理異常。
?。?)、一種是由內(nèi)核必須處理的異常條件產(chǎn)生的,比如缺頁異常,內(nèi)核執(zhí)行恢復異常的所有步驟。
2、異步中斷,通常我們就叫中斷。由其他硬件設備按照CPU時鐘信號隨機產(chǎn)生。
中斷處理程序的一般步驟:
一個中斷處理程序的幾個中斷服務例程之間是串行執(zhí)行的,并且在一個中斷處理程序結(jié)束前,不應該再次出現(xiàn)這個中斷,所以一般中斷處理程序是先禁止該中斷,然后處理中斷,處理完成后在使能該中斷。
有一些中斷是可以延遲處理的,這種可延遲中斷可以在開中斷的情況下執(zhí)行,執(zhí)行時允許其他中斷搶占他。把可延遲中斷從中斷處理程序中抽出來有助于使內(nèi)核保持較短的響應時間。
Linux內(nèi)核使用三種方法來處理這種可延遲的中斷任務:可延遲函數(shù)(軟中斷和tasklets)以及工作隊列。工作隊列是工作在進程上下文中,可以睡眠,軟中斷和tasklets 是工作在中斷上下文,不可以睡眠。本節(jié)只討論軟中斷和tasklets。
軟中斷:
Linux 2.6 版本使用如下幾個軟中斷,不同版本之間略有差異。但一下幾個不同版本都包含。
HI_SOFTIRQ=0, 處理高優(yōu)先級的tasklet
TIMER_SOFTIRQ, 時鐘中斷相關(guān)的tasklet.
NET_TX_SOFTIRQ, 內(nèi)核把數(shù)據(jù)報文傳送給網(wǎng)卡。
NET_RX_SOFTIRQ, 內(nèi)核從網(wǎng)卡接收數(shù)據(jù)報文。
TASKLET_SOFTIRQ, 處理常規(guī)tasklet。
低下標代表高優(yōu)先級。
內(nèi)核中定義了softirq_vec數(shù)組來存放各種軟中斷。定義如下:
static struct softirq_action softirq_vec[32]__cacheline_aligned_in_smp;
數(shù)組元素為 softirq_action,一個元素代碼一個軟中斷。不同的軟中斷號對應不同的數(shù)組的下標。
struct softirq_action
{
void (*action)(struct softirq_action *); //軟中斷發(fā)生時執(zhí)行軟中斷的處理函數(shù)。
void *data; //軟中斷的處理函數(shù)的參數(shù)指針。
};
初始化軟中斷時調(diào)用函數(shù) open_softirq()。如下代碼。
void open_softirq(int nr, void (*action)(struct softirq_action*),void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
另外一個跟軟中斷相關(guān)的關(guān)鍵字段是 32 位的 preempt_counte字段,用它來跟蹤內(nèi)核搶占和內(nèi)核控制路徑的嵌套,該字段放在每個進程描述符的 thread_info 字段中。用函數(shù)preempt_count()來返回該字段的值。
preempt_count字段
位描述
0~7搶占計數(shù)器,記錄顯示禁用本地cpu內(nèi)核搶占的次數(shù),值為0時代表內(nèi)核允許搶占。
8~15軟中斷計數(shù)器。記錄軟中斷被禁用的次數(shù),0表示軟中斷被激活。
16~27硬中斷計數(shù)器。記錄硬中斷嵌套的層數(shù)。irq_entry()增加它的值,irq_exit()遞減它的值。
28
當內(nèi)核明確不允許發(fā)生搶占或內(nèi)核正在中斷上下文中運行時,必須禁止內(nèi)核的搶占功能。為了確定當前進程是否能夠被搶占,內(nèi)核快速檢查preempt_counte字段是否等于零。
另一個跟軟中斷相關(guān)的字段是每個CPU都有一個32位掩碼的字段
typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;
他描述掛起的軟中斷。每一位對應相應的軟中斷。比如0位代表HI_SOFTIRQ.
宏local_softirq_pending()來獲取該字段的值。
使用函數(shù)raise_softirq()來激活軟中斷。即把響應的軟中斷號對應的__softirq_pending中的位置1.表示該軟中斷被掛起。如果當前CPU不在中斷上下文中,喚醒內(nèi)核線程ksoftirqd來檢查被掛起的軟中斷,然后執(zhí)行相應軟中斷處理函數(shù)。
內(nèi)核在如下幾個點上檢查被掛起的軟中斷:
1、當調(diào)用local_bh_enable()函數(shù)激活本地CPU的軟中斷時。條件滿足就調(diào)用do_softirq() 來處理軟中斷。
2、當do_IRQ()完成硬中斷處理時調(diào)用irq_exit()時調(diào)用do_softirq()來處理軟中斷。
3、當一個特殊內(nèi)核線程ksoftirq/n被喚醒時,處理軟中斷。
軟中斷處理函數(shù)詳解:
點擊(此處)折疊或打開asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
/*如果當前處于硬中斷中,在硬中斷處理函數(shù)退出時會調(diào)用irq_exit()函數(shù)來處理軟中斷,
或當前軟中斷被禁用。所以in_interrupt()返回不為1 就沒必要處理軟中斷,直接返回*/
if (in_interrupt())
return;
/*保持中斷寄存器的狀態(tài)并禁用本地CPU的中斷*/
local_irq_save(flags);
/*取得當前cpu上__softirq_pending字段,獲取本地CPU上掛起的軟中斷*/
pending = local_softirq_pending();
/*如果當前CPU上有掛起的軟中斷,執(zhí)行__do_softirq()來處理軟中斷*/
if (pending)
{
__do_softirq();
}
/*恢復中斷寄存器的狀態(tài)*/
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的軟中斷,現(xiàn)在本地cpu上掛起的軟中斷已經(jīng)存入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();
/*循環(huán)執(zhí)行被掛起的軟中斷處理函數(shù)。相應的軟中斷的處理函數(shù)存在數(shù)組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,查看是否還有新的被掛起的軟中斷并且檢查被掛起軟中斷的次數(shù)小于10次,如果條件滿足,
繼續(xù)處理新的被掛起的軟中斷*/
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
/*如果有新的掛起的軟中斷并且處理循環(huán)次數(shù)已經(jīng)夠了10次,
喚醒ksoftirq內(nèi)核線程來處理軟中斷*/
if (pending)
wakeup_softirqd();
/*debug 用,不討論*/
trace_softirq_exit();
account_system_vtime(current);
/*使能本地CPU的軟中斷*/
_local_bh_enable();
}
Linux內(nèi)核結(jié)構(gòu)
Linux內(nèi)核由七個部分構(gòu)成,具體如下圖:
a) 系統(tǒng)調(diào)用接口(SCI):open、read、write等系統(tǒng)調(diào)用
b) 進程管理(PM):創(chuàng)建進程、刪除進程、調(diào)度進程等
c) 內(nèi)存管理(MM):內(nèi)存分配、管理等
d) 虛擬文件系統(tǒng)(VFS):為多種文件系統(tǒng)提供統(tǒng)一的操作接口
e) 網(wǎng)絡協(xié)議棧:提供各種網(wǎng)絡協(xié)議
f) CPU架構(gòu)相關(guān)代碼(Arch):為的是提高至移植性
g) 設備驅(qū)動程序(DD):各種設備驅(qū)動,占到內(nèi)核的70%左右代碼
linux內(nèi)核源碼詳解
1. 源碼獲取
Linux內(nèi)核獲取有兩種方法,一種是在www.kernel.org 直接獲取,另一種是使用git獲?。ň唧w方法參考網(wǎng)絡)。
2. 源碼目錄簡介
其源碼主要有以下目錄(介紹重要目錄):
a) Arch目錄:存放處理器相關(guān)的代碼。下設子目錄,分別對應具體的CPU,每個子目錄有boot,mm,以及kernel三個子目錄,分別對應系統(tǒng)引導以及存儲管理,和系統(tǒng)調(diào)用
b) Include目錄:內(nèi)核所需要的大部分頭文件目錄。與平臺無關(guān)的在include/linux子目錄下,與平臺相關(guān)的則放在include相應的子目錄中。
c) fs目錄:存放各種文件系統(tǒng)的實現(xiàn)代碼。
d) init目錄:init子目錄包含核心的初始化代碼(不是系統(tǒng)的引導代碼)。其包含兩個文件main.c和version.c,可以用來研究核心如何工作。
e) ipc目錄:包含核心進程間的通信代碼。
f) kernel目錄:包含內(nèi)核管理的核心代碼。與硬件相關(guān)代碼放在arch/*/kernel目錄下。
g) mm目錄:包含了所有的內(nèi)存管理代碼。與硬件相關(guān)的內(nèi)存管理代碼位于arch/*/mm目錄下。
h) scripts目錄:包含用于配置核心的腳本文件。
i) lib目錄:包含了核心的庫代碼,與硬件相關(guān)的庫代碼被放在arch/*/lib/目錄下
評論