1. 程式人生 > >linux核心架構-中斷處理機制

linux核心架構-中斷處理機制

本文對中斷系統進行了全面的分析與探討,主要包括中斷控制器、中斷分類、中斷親和力、中斷執行緒化與 SMP 中的中斷遷徙等。首先對中斷工作原理進行了簡要分析,接著詳細探討了中斷親和力的實現原理,最後對中斷執行緒化與非執行緒化中斷之間的實現機理進行了對比分析。在每一部分都對linux3.2.0核心中arm架構的中斷機理進行了研究對比。

1 什麼是中斷?

Linux 核心需要對連線到計算機上的所有硬體裝置進行管理,毫無疑問這是它的份內事。如果要管理這些裝置,首先得和它們互相通訊才行,一般有兩種方案可實現這種功能:

輪詢(polling 讓核心定期對裝置的狀態進行查詢,然後做出相應的處理;

中斷(interrupt

讓硬體在需要的時候向核心發出訊號(變核心主動為硬體主動)。

輪詢因其週期性的執行,影響效率,故而採用中斷方式管理所有外設。

硬體中斷(hardware interrupt):由系統自身和與之連線的外設自動產生。它們用於實現更高效的實現裝置驅動程式,也用於引起處理器自身對異常和錯誤的關注,這些是需要與核心程式碼進行互動的。

軟中斷(SoftIRQ):用於有效實現核心中的延期操作。

中斷工作流程:

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

2 APIC vs 8259A

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

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

2.1 可程式設計中斷控制器8259A

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

2.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 所示。

目前大部分單處理器系統都包含一個 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,可以在命令列輸入如下命令:

[email protected]:~$ cat /proc/interrupts 
           CPU0       CPU1       
  0:         44          0   IO-APIC-edge      timer
  1:          3          0   IO-APIC-edge      i8042
  6:       1528       2111   IO-APIC-edge      floppy
  7:          0          0   IO-APIC-edge      parport0
  8:          0          1   IO-APIC-edge      rtc0
  9:          2          1   IO-APIC-fasteoi   acpi
 12:          1          3   IO-APIC-edge      i8042
 14:          0          0   IO-APIC-edge      ata_piix
 15:          0          0   IO-APIC-edge      ata_piix
 ......

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

2.3 ARM Cortex-A8中斷研究

ARM Cortex-A8處理器是第一款基於ARMv7架構的產品。Sitara家族AM335x系列 是基於ARM ® Cortex-A8 微處理器。中斷控制器能夠控制多達 128箇中斷請求。微處理器的ARM中斷控制器(AINTC)負責優化系統外設傳送的服務請求,產生nIRQ 或nFIQ,傳送給cpu。中斷型別和中斷輸入優先順序是可程式設計的。AINTC 和 ARM 微處理器通過AXI2OCP橋相連,遵循AXI協議,執行速率是微處理器的一半。

AINTC的通用特點:
• 多達128箇中斷請求
• 每個中斷輸入都可定製優先順序
• 每個中斷輸入能被指定為nFIQ 或 nIRQ
• nFIQ 和 nIRQ具有獨立的優先順序排序

注意:AXI(Advanced eXtensible Interface)是一種匯流排協議,該協議是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)3.0協議中最重要的部分,是一種面向高效能、高頻寬、低延遲的片內匯流排。它的地址/控制和資料相位是分離的,支援不對齊的資料傳輸,同時在突發傳輸中,只需要首地址,同時分離的讀寫資料通道、並支援Outstanding傳輸訪問和亂序訪問,並更加容易進行時序收斂。AXI 是AMBA 中一個新的高效能協議。AXI 技術豐富了現有的AMBA 標準內容,滿足超高效能和複雜的片上系統(SoC)設計的需求。

2.4 ARM Cortex-A8 和 x86架構的中斷硬體比較

ARM Cortex-A8一般使用ARM中斷控制器(AINTC)與MPU相連,連線方式與X86使用8259A的方式相似,只是遵循的協議不同。

3 中斷分類

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

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

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

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

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

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

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

X86 體系結構的每個中斷都被賦予一個唯一的編號或者向量(8 位無符號整數)。非遮蔽中斷和異常向量是固定的,而可遮蔽中斷向量可以通過對中斷控制器的程式設計來改變。

4 Linux 2.6 中斷處理原理簡介

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

當處於真實模式下時,IDT 被初始化並由 BIOS 程式所使用。然而,一旦 Linux 開始接管,IDT 就被移到 ARM 的另一個區域,並進行第二次初始化,因為 Linux 不使用任何 BIOS 程式,而使用自己專門的中斷服務程式(例程)(interrupt service routine,ISR)。中斷和異常處理程式很像常規的 C 函式,有三個主要的資料結構包含了與 IRQ 相關的所有資訊:hw_interrupt_typeirq_desc_tirqaction,圖3 解釋了它們之間是如何關聯的。

在 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_tirqaction 聯絡起來。

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

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

4.2 linux-3.2.0-m3352/arch/arm/include/asm$

4.2.1 IRQ控制器抽象定義

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:		per irq and chip data passed down to chip functions
 * @timer_rand_state:	pointer to timer rand state struct
 * @kstat_irqs:		irq stats per cpu
 * @handle_irq:		highlevel irq-events handler
 * @preflow_handler:	handler called before the flow handler (currently used by sparc)
 * @action:		the irq action chain
 * @status:		status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:		disable-depth, for nested irq_disable() calls
 * @wake_depth:		enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:		stats field to detect stalled irqs
 * @last_unhandled:	aging timer for unhandled count
 * @irqs_unhandled:	stats field for spurious unhandled interrupts
 * @lock:		locking for SMP
 * @affinity_hint:	hint to user space for preferred irq affinity
 * @affinity_notify:	context for notification of affinity changes
 * @pending_mask:	pending rebalanced interrupts
 * @threads_oneshot:	bitfield to handle shared oneshot threads
 * @threads_active:	number of irqaction threads currently running
 * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
 * @dir:		/proc/irq/ procfs entry
 * @name:		flow handler name for /proc/interrupts output
 */
struct irq_desc {
	struct irq_data		irq_data;
	struct timer_rand_state *timer_rand_state;
	unsigned int __percpu	*kstat_irqs;
	irq_flow_handler_t	handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
	irq_preflow_handler_t	preflow_handler;
#endif
	struct irqaction	*action;	/* IRQ action list */
	unsigned int		status_use_accessors;
	unsigned int		core_internal_state__do_not_mess_with_it;
	unsigned int		depth;		/* nested irq disables */
	unsigned int		wake_depth;	/* nested wake enables */
	unsigned int		irq_count;	/* For detecting broken IRQs */
	unsigned long		last_unhandled;	/* Aging timer for unhandled count */
	unsigned int		irqs_unhandled;
	raw_spinlock_t		lock;
	struct cpumask		*percpu_enabled;
#ifdef CONFIG_SMP
	const struct cpumask	*affinity_hint;
	struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
	cpumask_var_t		pending_mask;
#endif
#endif
	unsigned long		threads_oneshot;
	atomic_t		threads_active;
	wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*dir;
#endif
	struct module		*owner;
	const char		*name;
} ____cacheline_internodealigned_in_smp;


/**
 * struct irq_data - per irq and irq chip data passed down to chip functions
 * @irq:        中斷號
 * @hwirq:        硬體中斷號, local to the interrupt domain
 * @node:        node index useful for balancing
 * @state_use_accessors: status information for irq chip functions.
 *            Use accessor functions to deal with it
 * @chip:        low level interrupt hardware access
 * @domain:        Interrupt translation domain; responsible for mapping
 *            between hwirq number and linux irq number.
 * @handler_data:    per-IRQ data for the irq_chip methods
 * @chip_data:        platform-specific per-chip private data for the chip
 *            methods, to allow shared chip implementations
 * @msi_desc:        MSI descriptor
 * @affinity:        IRQ affinity on SMP
 *
 * The fields here need to overlay the ones in irq_desc until we
 * cleaned up the direct references and switched everything over to
 * irq_data.
 */


struct irq_data {
	unsigned int		irq;
	unsigned long		hwirq;
	unsigned int		node;
	unsigned int		state_use_accessors;
	struct irq_chip		*chip;
	struct irq_domain	*domain;
	void			*handler_data;
	void			*chip_data;
	struct msi_desc		*msi_desc;
#ifdef CONFIG_SMP
	cpumask_var_t		affinity;
#endif
};

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:     在/proc/interrupts中顯示的名稱,用於標識硬體控制器。在IA-32系統上可能值是“XTPIC”和“IO-APIC”,在AMD64系統上大多數時候也會使用後者。具體依據系統。
 * @irq_startup:    指向一個函式,用於第一次初始化一個IRQ。初始化工作僅限於啟用該IRQ。因而,startup函式實際上就是將工作轉給enable(預設情況下為enable)
 * @irq_shutdown:    關閉中斷 (defaults to ->disable if NULL)
 * @irq_enable:        啟用一個IRQ。也就是IRQ從禁用狀態到啟動狀態的轉換 (defaults to chip->unmask if NULL)
 * @irq_disable:    與enable相對
 * @irq_ack:        start of a new interrupt
 * @irq_mask:        mask an interrupt source
 * @irq_mask_ack:    ack and mask an interrupt source
 * @irq_unmask:        unmask an interrupt source
 * @irq_eoi:                   end of interrupt
 * @irq_set_affinity:    set the CPU affinity on SMP machines
 * @irq_retrigger:    resend an IRQ to the CPU
 * @irq_set_type:    set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @irq_set_wake:    enable/disable power-management wake-on of an IRQ
 * @irq_bus_lock:    function to lock access to slow bus (i2c) chips
 * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
 * @irq_cpu_online:    configure an interrupt source for a secondary CPU
 * @irq_cpu_offline:    un-configure an interrupt source for a secondary CPU
 * @irq_suspend:                    function called from core code on suspend once per chip
 * @irq_resume:                     function called from core code on resume once per chip
 * @irq_pm_shutdown:         function called from core code on shutdown once per chip
 * @irq_print_chip:                optional to print special chip info in show_interrupts
 * @flags:                                 chip specific flags
 *
 * @release:        release function solely used by UML
 */
struct irq_chip {
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);

	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);

	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);

	void		(*irq_bus_lock)(struct irq_data *data);
	void		(*irq_bus_sync_unlock)(struct irq_data *data);

	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);

	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);

	void		(*irq_print_chip)(struct irq_data *data, struct seq_file *p);

	unsigned long	flags;

	/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
	void		(*release)(unsigned int irq, void *dev_id);
#endif
};
中斷控制器晶片實現的一個特定例子,就是AMD64系統上的IO-APIC(程式碼位置linux/arch/x86/kernel/apic/io_apic.c)。有下列定義給出:
static struct irq_chip ioapic_chip __read_mostly = {
	.name			= "IO-APIC",
	.irq_startup		= startup_ioapic_irq,
	.irq_mask		= mask_ioapic_irq,
	.irq_unmask		= unmask_ioapic_irq,
	.irq_ack		= ack_apic_edge,
	.irq_eoi		= ack_apic_level,
#ifdef CONFIG_SMP
	.irq_set_affinity	= ioapic_set_affinity,
#endif
	.irq_retrigger		= ioapic_retrigger_irq,
};

基於AXI的ARM中斷控制器的資料結構的例項化。
static struct irq_chip gic_chip = {
	.name			= "GIC",
	.irq_mask		= gic_mask_irq,
	.irq_unmask		= gic_unmask_irq,
	.irq_eoi		= gic_eoi_irq,
	.irq_set_type		= gic_set_type,
	.irq_retrigger		= gic_retrigger,
#ifdef CONFIG_SMP
	.irq_set_affinity	= gic_set_affinity,
#endif
	.irq_set_wake		= gic_set_wake,
};

處理程式函式用到的資料結構體如下所示,在<linux/include/linux/interrupt.h>中定義:
/**
 * struct irqaction - per interrupt action descriptor
 * @handler:	interrupt handler function
 * @flags:	flags (see IRQF_* above)
 * @name:	name of the device
 * @dev_id:	cookie to identify the device
 * @percpu_dev_id:	cookie to identify the device
 * @next:	pointer to the next irqaction for shared interrupts
 * @irq:	interrupt number
 * @dir:	pointer to the proc/irq/NN/name entry
 * @thread_fn:	interrupt handler function for threaded interrupts
 * @thread:	thread pointer for threaded interrupts
 * @thread_flags:	flags related to @thread
 * @thread_mask:	bitmask for keeping track of @thread activity
 */
struct irqaction {
	irq_handler_t		<span style="background-color: rgb(255, 255, 102);"><span style="color:#FF0000;">handler;</span></span>
	unsigned long		flags;
	void			*dev_id;
	void __percpu		*percpu_dev_id;
	struct irqaction	*next;
	int			irq;
	irq_handler_t		thread_fn;
	struct task_struct	*thread;
	unsigned long		thread_flags;
	unsigned long		thread_mask;
	const char		*name;
	struct proc_dir_entry	*dir;
} ____cacheline_internodealigned_in_smp;

該結構中最重要的成員便是處理程式函式本身,即handler成員,這是一個函式指標,位於結構的起始處。在裝置請求一個系統中斷,而中斷控制器通過引發中斷將該請求轉發到處理器的時候,將呼叫該處理程式函式。

name和dev_id唯一地標示一個指標,指向在所有核心資料結構中唯一表示了該裝置的資料結構例項,例如網絡卡的net_device例項。如果幾個裝置共享一個IRQ,那麼IRQ編號自身不能標識該裝置,此時,在刪除處理程式函式時,將需要上述資訊。

flags是一個標誌變數,通過點陣圖描述了IRQ(和相關的中斷)的一些特性,點陣圖中的各個標誌位照例可以通過預定義的常數訪問。<interrupt.h>


5 中斷繫結——中斷親和力(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):

[[email protected] 44]# cat smp_affinity
ffffffff
[[email protected] 44]# echo 0f > smp_affinity
[[email protected] 44]# cat smp_affinity
0000000f
[[email protected] 44]# ping -f h
PING hell (195.4.7.3): 56 data bytes
...
--- hell ping statistics ---
6029 packets transmitted, 6027 packets received, 0% packet loss
round-trip min/avg/max = 0.1/0.1/0.4 ms
[[email protected] 44]# cat /proc/interrupts | grep 44:
 44:   0   1785   1785   1783   1783   1   1   0   IO-APIC-level   eth1
[[email protected] 44]# echo f0 > smp_affinity
[[email protected] 44]# ping -f h
PING hell (195.4.7.3): 56 data bytes
..
--- hell ping statistics ---
2779 packets transmitted, 2777 packets received, 0% packet loss
round-trip min/avg/max = 0.1/0.5/585.4 ms
[[email protected] 44]# cat /proc/interrupts | grep 44:
 44:  1068  1785  1785  1784   1784   1069   1070   1069   IO-APIC-level  eth1
[[email protected] 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。

6 新特性展望——中斷執行緒化(Interrupt Threads)

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

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

下面將對中斷執行緒化進行簡要分析。

在初始化階段,中斷執行緒化的中斷初始化與常規中斷初始化大體上相同,在 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_NODELAYIRQ_NODELAY 之間的轉換,是在 setup_irq() 函式中完成的。

7 中斷負載均衡—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上進行處理。

8 總結

隨著中斷親和力和中斷執行緒化的相繼實現,Linux 核心在 SMP 和實時效能方面的表現越來越讓人滿意,完全有理由相信,在不久的將來,中斷執行緒化將被合併到基線版本中。本文對中斷執行緒化的分析只是起一個拋磚引玉的作用,當新特性發布時,不至於讓人感到迷茫。

  • 註釋 1:輪詢也不是毫無用處,比如NAPI,就是輪詢與中斷相結合的經典案例。
9 參考資料:
http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/

相關推薦

linux核心架構-中斷處理機制

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

linux核心中非同步通知機制--訊號處理機制

       什麼是非同步通知:很簡單,一旦裝置準備好,就主動通知應用程式,這種情況下應用程式就不需要查詢裝置狀態, 特像硬體上常提的“中斷的概念”。 比較準確的說法其實應該叫做“訊號驅動的非同步I/O”,訊號是在軟體層次上對中斷機制的一種模擬。阻塞I/O意味著一直等待裝置

linux核心分析--中斷中斷處理程式

寫在前面:       在前面的這篇文章講解了關於中斷的基本概念,從巨集觀的角度瞭解了中斷的基本知識。從這篇文章中要明白幾點知識       1、中斷是由硬體產生的非同步中斷,而異常則是處理器產生的同步中斷       2、中斷質上是一種特殊的電訊號,由硬體裝置發向處理器,

Linux核心實現中斷中斷處理(一)

Linux實現中斷處理 核心是怎麼知道應用程式要呼叫系統呼叫的呢?或者說應用程式怎麼通知系統核心自己需要執行一個系統呼叫,這是通過軟中斷實現的,通過引發一個異常來促使系統切換到核心態去執行異常處理程式

linux驅動之中斷處理過程匯編部分

ont .cn stub sta 拷貝 ror c函數 當前 main函數 linux系統下驅動中,中斷異常的處理過程,與裸機開發中斷處理過程非常類似。通過簡單的回顧裸機開發中斷處理部分,來參考學習linux系統下中斷處理流程。 一、ARM裸機開發中斷處理過程

Linux缺頁中斷處理

1.如果訪問的虛擬地址在程序空間沒有對應的VMA(mmap和malloc可以分配vma),則缺頁處理失敗,程式出現段錯誤. 2.Linux把沒有對映到檔案的對映叫做匿名對映(malloc和mmap的匿名對映) 3.remap_pfn_range把核心記憶體對映到使用者空間,一般在裝置驅動的m

記憶體管理十 linux核心併發與同步機制

一、臨界資源:   臨界區是指訪問或操作共享資源的程式碼段,這些資源無法同時被多個執行執行緒訪問,為了避免臨界區的併發 訪問,需要保證臨界區的原子性,臨界區不能有多個併發源同時執行,原子性保護的是資源和資料,包括靜態區域性 變數、全域性變數、共享的資料結構、Buffer快取等各種資源資料

深入Linux核心架構——程序虛擬記憶體

逆向對映(reverse mapping)技術有助於從虛擬記憶體頁跟蹤到對應的實體記憶體頁; 缺頁處理(page fault handling)允許從塊裝置按需讀取資料填充虛擬地址空間。 一、簡介 使用者虛擬地址空間的管理比核心地址空間的管理複雜: 每個應用程式都有自身的地址空間,與

Linux 核心中的 static_key 機制

目錄 GOTO  問題解決: 問題來源:惡意程式檢測 最近,主要由於在研討一些關於LINUX被惡意程式ROOT後,可能會被修改程式碼段中的資料。為了防止程式碼段被修改,採用幾種特殊的機制來保護程式碼段的資料不被篡改,當有惡意程式試圖修改程式

深入Linux核心架構——鎖與程序間通訊

Linux作為多工系統,當一個程序生成的資料傳輸到另一個程序時,或資料由多個程序共享時,或程序必須彼此等待時,或需要協調資源的使用時,應用程式必須彼此通訊。 一、控制機制 1、競態條件 幾個程序在訪問資源時彼此干擾的情況通常稱之為競態條件(race condition)。在對分散式應用程式設計時,這種情

Linux核心入門: __attribute__ 機制

GNU C的一大特色(卻不被初學者所知)就是__attribute__機制。__attribute__是用來設定函式屬性(Function Attribute)、變數屬性(Variable Attribute)和型別屬性(Type Attribute)。 __attribu

淺談Linux中的訊號處理機制(三)

       一晃眼,已經到9月底了,都來不及去感慨時間匆匆。最近常常會想明年的今天我將會在那裡幹著什麼樣的工作?對未來又是憧憬又是擔憂,壓力山大。無論如何現在還是踏踏實實的學習吧,能這樣安安靜靜學習的日子也不多了。不扯了,還是接著前面的寫吧。 SA_RESTART語義        在上篇提到過,SA_

淺談Linux中的訊號處理機制(二)

      首先謝謝 @小堯弟 這位朋友對我昨天夜裡寫的一篇《淺談Linux中的訊號處理機制(一)》的指正,之前的題目我用的“淺析”一詞,給人一種要剖析核心的感覺。本人自知功力不夠,尚且不能對著Linux核心原始碼評頭論足。以後的路還很長,我還是一步一個腳印的慢慢走著吧,Linux核心這座山,我才剛剛抵達山腳

淺談Linux中的訊號處理機制(一)

     有好些日子沒有寫部落格了,自己想想還是不要荒廢了時間,寫點兒東西記錄自己的成長還是百利無一害的。今天是9月17號,暑假在某家遊戲公司實習了一段時間,做的事情是在Windows上用c++寫一些遊戲英雄技能的邏輯實現。雖然時間不算長,但是也算學了一點東西,對團隊專案開發流程也有了一個直觀的感受,專案裡c

linux程式設計---訊號中斷處理

訊號 linux提供的訊號機制是一種程序間非同步的通訊機制,在實現上是一種軟中斷。訊號可以導致一個正在執行的程序被另一個非同步程序中斷。 訊號的處理流程 產生訊號:產生訊號有多種說法。一個程序建立一個訊號用於傳送給另一個程序叫傳送一個訊號;核心建立一個訊號叫生成一個訊號

Linux核心中斷、軟中斷、tasklet

http://blog.csdn.net/jansonzhe/article/details/48786207 在之前我所寫的Linux驅動程式中,會經常使用到中斷機制,像CC1100高頻驅動、倒車雷達驅動等等。但所用到的中斷機制都基本上是用到中斷的頂半部,即:編寫中斷

VxWorks/MIPS中斷處理機制

在《中斷處理》中,梳理了中斷處理的一些通用框架和概念,下面我們來探討一下VxWorks/MIPS具體平臺下的中斷處理機制。時鐘中斷作為最高優先順序的中斷,其處理不失一般性,可作為分析理解VxWor

Linux核心架構核心裁剪

Linux系統分為核心空間和使用者空間;使用者空間主要包括:使用者應用程式和基本的庫檔案核心空間主要包括:                        系統呼叫介面SCI:系統封裝給使用者空間的應用函式呼叫介面                        程序管理PM:管

Linux體系結構、Linux核心架構

Linux體系結構 Linux由使用者空間和核心空間兩部分組成。 現代CPU通常實現了不同的工作模式,以ARM為例,實現了7種工作模式: 使用者模式(usr)、快速中斷(fiq)、外部中斷(irq)、管理模式(svc)、資料訪問中止(abt)、系統模式(sys)、未定義指

《深入Linux核心架構與底層原理》讀書筆記一——核心架構核心資料結構知識

1、核心架構常見架構正規化:Linux核心上下層通訊方式橫向系統和縱向系統橫向系統如cgroup,proc,sys檔案系統,系統呼叫的組織,除錯系統,Core Dump,訊號,記憶體管理等;縱向系統是指具體的功能模組,如USB功能,一個對USB檔案的操作要走完核心中的很多個層