Linux 核心中斷內幕__Linux

來源:互聯網
上載者:User
原文網址:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/index.html

簡介: 本文對中斷系統進行了全面的分析與探討,主要包括中斷控制器、中斷分類、中斷親和力、中斷線程化與 SMP 中的中斷遷徙等。首先對中斷工作原理進行了簡要分析,接著詳細探討了中斷親和力的實現原理,最後對中斷線程化與非線程化中斷之間的實現機理進行了對比分析。 標記本文。

發布日期: 2007 年 5 月 14 日
層級: 中級
訪問情況 : 15198 次瀏覽
評論: 2 (查看 | 添加評論 - 登入) 平均分 (39個評分)
為本文評分

什麼是中斷

Linux 核心需要對串連到電腦上的所有硬體裝置進行管理,毫無疑問這是它的份內事。如果要管理這些裝置,首先得和它們互相通訊才行,一般有兩種方案可實現這種功能: 輪詢(polling) 讓核心定期對裝置的狀態進行查詢,然後做出相應的處理; 中斷(interrupt) 讓硬體在需要的時候向核心發出訊號(變核心主動為硬體主動)。

第一種方案會讓核心做不少的無用功,因為輪詢總會周期性的重複執行,大量地耗用 CPU 時間,因此效率及其低下,所以一般都是採用第二種方案 。注釋 1

從物理學的角度看,中斷是一種電訊號,由硬體裝置產生,並直接送入中斷控制器(如 8259A)的輸入引腳上,然後再由中斷控制器向處理器發送相應的訊號。處理器一經檢測到該訊號,便中斷自己當前正在處理的工作,轉而去處理中斷。此後,處理器會通知 OS 已經產生中斷。這樣,OS 就可以對這個中斷進行適當的處理。不同的裝置對應的中斷不同,而每個中斷都通過一個唯一的數位識別碼,這些值通常被稱為插斷要求線。

回頁首

APIC vs 8259A

X86電腦的 CPU 為中斷只提供了兩條外接引腳:NMI 和 INTR。其中 NMI 是不可屏蔽中斷,它通常用於電源掉電和實體儲存體器同位;INTR是可屏蔽中斷,可以通過設定中斷屏蔽位來進行中斷屏蔽,它主要用於接受外部硬體的中斷訊號,這些訊號由中斷控制器傳遞給 CPU。

常見的中斷控制器有兩種:

1. 可程式化插斷控制器8259A

傳統的 PIC(Programmable Interrupt Controller)是由兩片 8259A 風格的外部晶片以“級聯”的方式串連在一起。每個晶片可處理多達 8 個不同的 IRQ。因為從 PIC 的 INT 輸出線串連到主 PIC 的 IRQ2 引腳,所以可用 IRQ 線的個數達到 15 個,如圖 1 所示。
圖 1:8259A 級聯原理圖

2. 進階可程式化插斷控制器(APIC)

8259A 只適合單 CPU 的情況,為了充分挖掘 SMP 體繫結構的並行性,能夠把中斷傳遞給系統中的每個 CPU 至關重要。基於此理由,Intel 引入了一種名為 I/O 進階可程式化控制器的新組件,來替代老式的 8259A 可程式化插斷控制器。該組件包含兩大組成部分:一是“本地 APIC”,主要負責傳遞中斷訊號到指定的處理器;舉例來說,一台具有三個處理器的機器,則它必須相對的要有三個本地 APIC。另外一個重要的部分是 I/O APIC,主要是收集來自 I/O 裝置的 Interrupt 訊號且在當那些裝置需要中斷時發送訊號到本地 APIC,系統中最多可擁有 8 個 I/O APIC。

每個本地 APIC 都有 32 位的寄存器,一個內部時鐘,一個本地定時裝置以及為本地中斷保留的兩條額外的 IRQ 線 LINT0 和 LINT1。所有本地 APIC 都串連到 I/O APIC,形成一個多級 APIC 系統,如圖 2 所示。
圖 2:多級I/O APIC系統

目前大部分單一處理器系統都包含一個 I/O APIC 晶片,可以通過以下兩種方式來對這種晶片進行配置:

1) 作為一種標準的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 串連到 CPU,兩條 LINT0 和 LINT1 分別串連到 INTR 和 NMI 引腳。

2) 作為一種標準外部 I/O APIC。本地 APIC 被啟用,且所有的外部中斷都通過 I/O APIC 接收。

辨別一個系統是否正在使用 I/O APIC,可以在命令列輸入如下命令:

# cat /proc/interrupts           CPU0         0:      90504    IO-APIC-edge  timer  1:        131    IO-APIC-edge  i8042  8:          4    IO-APIC-edge  rtc  9:          0    IO-APIC-level  acpi 12:        111    IO-APIC-edge  i8042 14:       1862    IO-APIC-edge  ide0 15:         28    IO-APIC-edge  ide1177:          9    IO-APIC-level  eth0185:          0    IO-APIC-level  via82cxxx...         

如果輸出結果中列出了 IO-APIC,說明您的系統正在使用 APIC。如果看到 XT-PIC,意味著您的系統正在使用 8259A 晶片。

回頁首

中斷分類

中斷可分為同步(synchronous)中斷和非同步(asynchronous)中斷:

1. 同步中斷是當指令執行時由 CPU 控制單元產生,之所以稱為同步,是因為只有在一條指令執行完畢後 CPU 才會發出中斷,而不是發生在代碼指令執行期間,比如系統調用。

2. 非同步中斷是指由其他硬體裝置依照 CPU 時鐘訊號隨機產生,即意味著中斷能夠在指令之間發生,例如鍵盤中斷。

根據 Intel 官方資料,同步中斷稱為異常(exception),非同步中斷被稱為中斷(interrupt)。

中斷可分為可屏蔽中斷(Maskable interrupt)和非屏蔽中斷(Nomaskable interrupt)。異常可分為故障(fault)、陷阱(trap)、終止(abort)三類。

從廣義上講,中斷可分為四類:中斷故障陷阱終止。這些類別之間的異同點請參看 表 1。

表 1:中斷類別及其行為
類別 原因 非同步/同步 返回行為
中斷 來自I/O裝置的訊號 非同步 總是返回到下一條指令
陷阱 有意的異常 同步 總是返回到下一條指令
故障 潛在可恢複的錯誤 同步 返回到當前指令
終止 不可恢複的錯誤 同步 不會返回

X86 體繫結構的每個中斷都被賦予一個唯一的編號或者向量(8 位不帶正負號的整數)。非屏蔽中斷和異常向量是固定的,而可屏蔽中斷向量可以通過對中斷控制器的編程來改變。

回頁首

Linux 2.6 中斷處理原理簡介

中斷描述符表(Interrupt Descriptor Table,IDT)是一個系統資料表,它與每一個中斷或異常向量相聯絡,每一個向量在表中存放的是相應的中斷或例外處理常式的入口地址。核心在允許中斷髮生前,也就是在系統初始化時,必須把 IDT 表的初始化地址裝載到 idtr 寄存器中,初始化表中的每一項。

當處於實模式下時,IDT 被初始化並由 BIOS 程式所使用。然而,一旦 Linux 開始接管,IDT 就被移到 ARM 的另一個地區,並進行第二次初始化,因為 Linux 不使用任何 BIOS 程式,而使用自己專門的中斷服務程式(常式)(interrupt service routine,ISR)。中斷和例外處理常式很像常規的 C 函數

有三個主要的資料結構包含了與 IRQ 相關的所有資訊:hw_interrupt_type、irq_desc_t 和 irqaction,圖3 解釋了它們之間是如何關聯的。
圖 3:IRQ 結構之間的關係

在 X86 系統中,對於 8259A 和 I/O APIC 這兩種不同類型的中斷控制器,hw_interrupt_type 結構體被賦予不同的值,具體區別參見表 2。

表 2:8259A 和 I/O APIC PIC 的區別
8259A I/O APIC
static struct hw_interrupt_type i8259A_irq_type = {"XT-PIC",startup_8259A_irq,shutdown_8259A_irq,enable_8259A_irq,disable_8259A_irq,mask_and_ack_8259A,end_8259A_irq,NULL}; static struct hw_interrupt_type ioapic_edge_type = {.typename = "IO-APIC-edge",.startup = startup_edge_ioapic,.shutdown = shutdown_edge_ioapic,.enable = enable_edge_ioapic,.disable = disable_edge_ioapic,.ack = ack_edge_ioapic,.end = end_edge_ioapic,.set_affinity = set_ioapic_affinity,};static struct hw_interrupt_type ioapic_level_type = {.typename = "IO-APIC-level",.startup = startup_level_ioapic,.shutdown = shutdown_level_ioapic,.enable = enable_level_ioapic,.disable = disable_level_ioapic,.ack = mask_and_ack_level_ioapic,.end = end_level_ioapic,.set_affinity = set_ioapic_affinity,};

在中斷初始化階段,調用 hw_interrupt_type 類型的變數初始化 irq_desc_t 結構中的 handle 成員。在早期的系統中使用級聯的8259A,所以將用 i8259A_irq_type 來進行初始化,而對於SMP系統來說,要麼以 ioapic_edge_type,或以 ioapic_level_type 來初始化 handle 變數。

對於每一個外設,要麼以靜態(聲明為 static 類型的全域變數)或動態(調用 request_irq 函數)的方式向 Linux 核心註冊中斷處理常式。不管以何種方式註冊,都會聲明或分配一塊 irqaction 結構(其中 handler 指向中斷服務程式),然後調用 setup_irq() 函數,將 irq_desc_t 和 irqaction 聯絡起來。

當中斷髮生時,通過中斷描述符表 IDT 擷取中斷服務程式入口地址,對於 32≤ i ≤255(i≠128) 之間的中斷向量,將會執行 push $i-256,jmp common_interrupt 指令。隨之將調用 do_IRQ() 函數,以中斷向量為 irq_desc[] 結構的下標,擷取 action 的指標,然後調用 handler 所指向的中斷服務程式。

從以上描述,我們不難看出整個中斷的流程,如圖 4 所示:
圖 4:X86中斷流

本文作者之一曾經對2.6.10的中斷系統進行過情景分析,有興趣的讀者可以和作者取得聯絡,擷取相關資料。

回頁首

中斷綁定——中斷親和力(IRQ Affinity)

在 SMP 體繫結構中,我們可以通過調用系統調用和一組相關的宏來設定 CPU 親和力(CPU affinity),將一個或多個進程綁定到一個或多個處理器上運行。中斷在這方面也毫不示弱,也具有相同的特性。中斷親和力是指將一個或多個中斷源綁定到特定的 CPU 上運行。中斷親和力最初由 Ingo Molnar 設計並實現。

在 /proc/irq 目錄中,對於已經註冊中斷處理常式的硬體裝置,都會在該目錄下存在一個以該中斷號命名的目錄 IRQ# ,IRQ# 目錄下有一個 smp_affinity 檔案(SMP 體繫結構才有該檔案),它是一個 CPU 的位元遮罩,可以用來設定該中斷的親和力, 預設值為 0xffffffff,表明把中斷髮送到所有的 CPU 上去處理。如果中斷控制器不支援 IRQ affinity,不能改變此預設值,同時也不能關閉所有的 CPU 位元遮罩,即不能設定成 0x0。

我們以網卡(eth1,中斷號 44 )為例,在具有 8 個 CPU 的伺服器上來設定網卡中斷的親和力(以下資料出自核心源碼 Documentation\IRQ-affinity.txt):

[root@moon 44]# cat smp_affinityffffffff[root@moon 44]# echo 0f > smp_affinity[root@moon 44]# cat smp_affinity0000000f[root@moon 44]# ping -f hPING hell (195.4.7.3): 56 data bytes...--- hell ping statistics ---6029 packets transmitted, 6027 packets received, 0% packet lossround-trip min/avg/max = 0.1/0.1/0.4 ms[root@moon 44]# cat /proc/interrupts | grep 44: 44:   0   1785   1785   1783   1783   1   1   0   IO-APIC-level   eth1[root@moon 44]# echo f0 > smp_affinity[root@moon 44]# ping -f hPING hell (195.4.7.3): 56 data bytes..--- hell ping statistics ---2779 packets transmitted, 2777 packets received, 0% packet lossround-trip min/avg/max = 0.1/0.5/585.4 ms[root@moon 44]# cat /proc/interrupts | grep 44: 44:  1068  1785  1785  1784   1784   1069   1070   1069   IO-APIC-level  eth1[root@moon 44]#

在上例中,我們首先只允許在 CPU0~3 上處理網卡中斷,接著運行 ping 程式,不難發現在 CPU4~7 上並沒有對網卡中斷進行處理。然後只在 CPU4~7 上對網卡中斷進行處理, CPU0~3 不對網卡中斷進行任何處理,運行 ping 程式之後,再次查看 /proc/interrupts 檔案時,不難發現 CPU4~7 上的中斷次數明顯增加,而 CPU0~3 上的中斷次數沒有太大的變化。

在探討中斷親和力的實現原理之前,我們首先來瞭解 I/O APIC 中的組成。

I/O APIC 由一組 24 條 IRQ 線,一張 24 項的中斷重新導向表(Interrupt Redirection Table),可程式化寄存器,以及通過 APIC 匯流排發送和接收 APIC 資訊的一個資訊單元組成。其中與中斷親和力息息相關的是中斷重新導向表,中斷重新導向表表中的每一項都可以被單獨編程以指明中斷向量和優先順序、目標處理器及選擇處理器的方式

通過表 2,不難發現 8259A 和 APIC 中斷控制器最大不同點在於 hw_interrupt_type 類型變數的最後一項。對於 8259A 類型,set_affinity 被置為 NULL,而對於 SMP 的 APIC 類型,set_affinity 被賦值為 set_ioapic_affinity。

在系統初始化期間,對於 SMP 體繫結構,將會調用 setup_IO_APIC_irqs() 函數來初始化 I/O APIC 晶片,晶片中的中斷重新導向表的 24 項被填充。在系統啟動期間,所有的 CPU 都執行 setup_local_APIC() 函數,完成本地的 APIC 初始化。當有中斷被觸發時,將相應的中斷重新導向表中的值轉換成一條訊息,然後,通過 APIC 匯流排把訊息發送給一個或多個本地 APIC 單元,這樣,中斷就能立即被傳遞給一個特定的 CPU,或一組 CPU,或所有的 CPU,從而來實現中斷親和力。

當我們通過 cat 命令將 CPU 掩碼寫進 smp_affinity 檔案時,此時的調用路線圖為:write() ->sys_write() ->vfs_write() ->proc_file_write() ->irq_affinity_write_proc() ->set_affinity() ->set_ioapic_affinity() ->set_ioapic_affinity_irq() ->io_apic_write();其中在調用 set_ioapic_affinity_irq() 函數時,以中斷號和 CPU 掩碼作為參數,接著繼續調用 io_apic_write(),修改相應的中斷重新導向中的值,來完成中斷親和力的設定。當執行 ping 命令時,網卡中斷被觸發,產生了一個中斷訊號,多 APIC 系統根據中斷重新導向表中的值,依照仲裁機制,選擇 CPU0~3 中的某一個 CPU,並將該訊號傳遞給相應的本地 APIC,本地 APIC 又中斷它的 CPU,整個事件不通報給其他所有的 CPU。

回頁首

新特性展望——中斷線程化(Interrupt Threads)

在嵌入式領域,業界對 Linux 即時性的呼聲越來越高,對中斷進行改造勢在必行。在 Linux 中,中斷具有最高的優先順序。不論在任何時刻,只要產生中斷事件,核心將立即執行相應的中斷處理常式,等到所有掛起的中斷和非強制中斷處理完畢後才能執行正常的任務,因此有可能造成即時任務得不到及時的處理。中斷線程化之後,中斷將作為核心線程運行而且被賦予不同的即時優先順序,即時任務可以有比中斷線程更高的優先順序。這樣,具有最高優先順序的即時任務就能得到優先處理,即使在嚴重負載下仍有即時性保證。

目前較新的 Linux 2.6.17 還不支援中斷線程化。但由 Ingo Molnar 設計並實現的即時補丁,實現了中斷線程化。最新的下載地址為:

http://people.redhat.com/~mingo/realtime-preempt/patch-2.6.17-rt9

下面將對中斷線程化進行簡要分析。

在初始化階段,中斷線程化的中斷初始化與常規中斷初始化大體上相同,在 start_kernel() 函數中都調用了 trap_init() 和 init_IRQ() 兩個函數來初始化 irq_desc_t 結構體,不同點主要體現在核心初始化建立 init 線程時,中斷線程化的中斷在 init() 函數中還將調用 init_hardirqs(kernel/irq/manage.c(已經打過上文提到的補丁)),來為每一個 IRQ 建立一個核心線程,最高即時優先順序為 50,依次類推直到 25,因此任何 IRQ 線程的最低即時優先順序為 25。

void __init init_hardirqs(void){……for (i = 0; i < NR_IRQS; i++) {irq_desc_t *desc = irq_desc + i;if (desc->action && !(desc->status & IRQ_NODELAY))desc->thread = kthread_create(do_irqd, desc, "IRQ %d", irq);    ……}}static int do_irqd(void * __desc){    ……/* * Scale irq thread priorities from prio 50 to prio 25 */param.sched_priority = curr_irq_prio;if (param.sched_priority > 25)curr_irq_prio = param.sched_priority - 1;   ……}

如果某個中斷號狀態位中的 IRQ_NODELAY 被置位,那麼該中斷不能被線程化。

在中斷處理階段,兩者之間的異同點主要體現在:兩者相同的部分是當發生中斷時,CPU 將調用 do_IRQ() 函數來處理相應的中斷,do_IRQ() 在做了必要的相關處理之後調用 __do_IRQ()。兩者最大的不同點體現在 __do_IRQ() 函數中,在該函數中,將判斷該中斷是否已經被線程化(如果中斷描述符的狀態欄位不包含 IRQ_NODELAY 標誌,則說明該中斷被線程化了),對於沒有線程化的中斷,將直接調用 handle_IRQ_event() 函數來處理。

fastcall notrace unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs){……if (redirect_hardirq(desc))goto out_no_end;……action_ret = handle_IRQ_event(irq, regs, action);……}int redirect_hardirq(struct irq_desc *desc){……if (!hardirq_preemption || (desc->status & IRQ_NODELAY) || !desc->thread)return 0;……if (desc->thread && desc->thread->state != TASK_RUNNING)wake_up_process(desc->thread);……}

對於已經線程化的情況,調用 wake_up_process() 函數喚醒中斷處理線程,並開始運行,核心線程將調用 do_hardirq() 來處理相應的中斷,該函數將判斷是否有中斷需要被處理,如果有就調用 handle_IRQ_event() 來處理。handle_IRQ_event() 將直接調用相應的中斷處理函數來完成中斷處理。

不難看出,不管是線程化還是非線程化的中斷,最終都會執行 handle_IRQ_event() 函數來調用相應的中斷處理函數,只是線程化的中斷處理函數是在核心線程中執行的。

並不是所有的中斷都可以被線程化,比如時鐘中斷,主要用來維護系統時間以及定時器等,其中定時器是作業系統的脈搏,一旦被線程化,就有可能被掛起,這樣後果將不堪設想,所以不應當被線程化。如果某個中斷需要被即時處理,它可以像時鐘中斷那樣,用 SA_NODELAY 標誌來聲明自己非線程化,例如:

static struct irqaction irq0 = {timer_interrupt, SA_INTERRUPT | SA_NODELAY, CPU_MASK_NONE, "timer", NULL, NULL};

其中,SA_NODELAY 到 IRQ_NODELAY 之間的轉換,是在 setup_irq() 函數中完成的。

回頁首

中斷負載平衡—SMP體繫結構下的中斷

中斷負載平衡的實現主要封裝在 arch\ arch\i386\kernel\io-apic.c 檔案中。如果在編譯核心時配置了 CONFIG_IRQBALANCE 選項,那麼 SMP 體繫結構中的中斷負載平衡將以模組的形式存在於核心中。

late_initcall(balanced_irq_init);#define late_initcall(fn)module_init(fn)  //include\linux\init.h

在 balanced_irq_init() 函數中,將建立一個核心線程來負責中斷負載平衡:

static int __init balanced_irq_init(void){   ……printk(KERN_INFO "Starting balanced_irq\n");if (kernel_thread(balanced_irq, NULL, CLONE_KERNEL) >= 0) return 0;else printk(KERN_ERR "balanced_irq_init: failed to spawn balanced_irq");    ……}

在 balanced_irq() 函數中,每隔 5HZ=5s 的時間,將調用一次 do_irq_balance() 函數,進行中斷的遷徙。將重負載 CPU 上的中斷遷移到較閒置CPU上進行處理。

回頁首

總結

隨著中斷親和力和中斷線程化的相繼實現,Linux 核心在 SMP 和即時效能方面的表現越來越讓人滿意,完全有理由相信,在不久的將來,中斷線程化將被合并到基準版本中。本文對中斷線程化的分析只是起一個拋磚引玉的作用,當新特性發布時,不至於讓人感到迷茫。 注釋 1:輪詢也不是毫無用處,比如NAPI,就是輪詢與中斷相結合的經典案例。

參考資料 Rebert Love,《Linux Kernel Development,2rd Edition》,機械工業出版社,2006。 Daniel P. Bovet,Marco Cesati,《Understanding the Linux Kernel,3rd Edition》,東南大學出版社,2006。 Jonatban Corbet 等,魏永明等譯,《Linux裝置驅動程式》,中國電力出版社,2006。 Gordon Fischer 等,《The Linux Kernel Prime》,機械工業出版社,2006。 developerWorks 中國網站 Linux 技術專區

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.