在硬體上,中斷源可以通過中斷控制器向CPU提交中斷,進而引發中斷處理程式的執行,不過這種硬體中斷體系每一種CPU都不一樣,而Linux作為操作系統,需要同時支持這些中斷體系,如此一來,Linux中就提出了 軟中斷 的概念,也有人叫 內核中斷 ,其本質就是使用統一的方式對不同硬體中斷體系中的中斷號進行 ...
在硬體上,中斷源可以通過中斷控制器向CPU提交中斷,進而引發中斷處理程式的執行,不過這種硬體中斷體系每一種CPU都不一樣,而Linux作為操作系統,需要同時支持這些中斷體系,如此一來,Linux中就提出了軟中斷的概念,也有人叫內核中斷,其本質就是使用統一的方式對不同硬體中斷體系中的中斷號進行再映射,在操作系統中操作的中斷號都是這些映射過的軟中斷號。就是下圖中的irq_num
Linux內核中定義了名為irq_desc的中斷常式描述符表來描述中斷服務常式,每一個irq_desc對象數組中的下標就是軟中斷號。其中的struct irqaction對象描述了這個中斷服務常式的中斷的具體信息。
//include/linux/irqdesc.h
40 struct irq_desc {
41 struct irq_data irq_data;
42 unsigned int __percpu *kstat_irqs;
43 irq_flow_handler_t handle_irq;
44 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
45 irq_preflow_handler_t preflow_handler;
46 #endif
47 struct irqaction *action; /* IRQ action list */
48 unsigned int status_use_accessors;
49 unsigned int core_internal_state__do_not_mess_with_it;
50 unsigned int depth; /* nested irq disables */
51 unsigned int wake_depth; /* nested wake enables */
52 unsigned int irq_count; /* For detecting broken IRQs */
53 unsigned long last_unhandled; /* Aging timer for unhandled count */
54 unsigned int irqs_unhandled;
55 raw_spinlock_t lock;
56 struct cpumask *percpu_enabled;
57 #ifdef CONFIG_SMP
58 const struct cpumask *affinity_hint;
59 struct irq_affinity_notify *affinity_notify;
60 #ifdef CONFIG_GENERIC_PENDING_IRQ
61 cpumask_var_t pending_mask;
62 #endif
63 #endif
64 unsigned long threads_oneshot;
65 atomic_t threads_active;
66 wait_queue_head_t wait_for_threads;
67 #ifdef CONFIG_PROC_FS
68 struct proc_dir_entry *dir;
69 #endif
70 int parent_irq;
71 struct module *owner;
72 const char *name;
73 } ____cacheline_internodealigned_in_smp;
74
76 extern struct irq_desc irq_desc[NR_IRQS]; //NR_IRQS表示中斷源的數目
//linux/interrupt.h
104 //一個irq action的描述結構
105 struct irqaction {
106 irq_handler_t handler;
107 void *dev_id;
108 void __percpu *percpu_dev_id;
109 struct irqaction *next;
110 irq_handler_t thread_fn;
111 struct task_struct *thread;
112 unsigned int irq;
113 unsigned int flags;
114 unsigned long thread_flags;
115 unsigned long thread_mask;
116 const char *name;
117 struct proc_dir_entry *dir;
118 } ____cacheline_internodealigned_in_smp;
struct irqaction
--105-->handler: 中斷處理函數
--106-->name: 設備名
--107-->dev_id: 設備識別id
--109-->next: 指向下一個irqaction的指針
--110-->irq: 硬體中斷號!!!
--113-->flags:IRQF_DISABLED .etc
--110-->thread_fn: 線程中斷的中斷處理函數
--111-->thread: 線程中斷的線程指針
--114-->thread_flags: 與@thread關聯的flags
--115-->thread_mask: 追蹤@thread activity的位掩碼
--116-->dir: 指向proc/irq/NN/name的入口指針
raw_local_irq_save(x) //禁止所有中斷
raw_local_irq_enable //取消禁止中斷
寫中斷處理程式
1. 編寫中斷處理函數
下麵這個就是中斷處理程式的原型,中斷發生後,內核會將軟中斷號和註冊時的data作為參數傳入。中斷處理程式ISR是在中斷發生時被調用的用來處理中斷的函數,不是進程上下文,在中斷期間運行,不能執行可能休眠的操作,不能同用戶空間交換數據,不能調用schedule()函數放棄調度
實現中斷處理有一個原則:儘可能快的處理並返回,冗長的計算處理工作應該交給tasklet或任務隊列在安全的時間內進行。此外,硬體系統中常使用共用中斷,即多個設備共用一根線。所以在中斷處理程式中要添加中斷識別代碼,通常就是讀取寄存器,看具體是哪個中斷被觸發了。
88 typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
...
int status = read_irq_status(); /* 讀取相應的寄存器獲取中斷源 */
if(!is_myirq(dev_id,status)){ /* 判斷是否是本設備中斷 */
return IRQ_NONE;
}
/* 中斷處理程式 */
return IRQ_HANDLED
}
2. 註冊中斷處理函數
下麵這個就是註冊中斷的API,其中最重要的就是flags參數,它的取值在interrupt.h有定義,常用的有IRQF_DISABLED和IRQF_SHARED,前者表示中斷程式調用時屏蔽所有中斷,IRQF_SHARED表示多個設備共用中斷,即該中斷號可以被多個設備共用-SPI(另外兩種SGI,PPI),此外,還要"位或"上觸發方式,eg:IRQF_DISABLED|IRQF_TRIGGER_RISING
/**
* @flags:中斷標誌位。
* @dev_id通常用於共用中斷,用來標識是哪個設備觸發了中斷
* @name 是中斷名稱,可以在/proc/interrupt中看到
*/
int requst_irq(unsigned int irq,irq_handler_t handler, unsigned long flags, const char *name,void * dev_id);
3. 釋放中斷處理函數
中斷號也是一種系統資源,使用完畢後要釋放,註意,free_irq的第二個參數應當與request_irq()中最後一個參數相同。
/**
* free_irq - 釋放irq
*/
void free_irq(unsigned int irqno,void * dev_id);
底半部
中斷不屬於進程上下文,所以不能被內核調度,如果進入了中斷處理函數,就只能將其執行完畢,不能被打斷,這樣帶來的一個問題是如果在中斷處理函數中執行耗時操作,就會極大的影響系統性能,為瞭解決這個問題,Linux內核中提出了中斷頂半部和`底半部(bottom half)的概念,對於耗時的中斷處理程式,將重要的、必要的操作放在頂半部執行,這部分和傳統的中斷概念一樣,一旦開始就必須執行完畢;將中斷處理程式中耗時的,不那麼緊要的操作放在底半部,防止整個中斷處理程式過多的占用系統資源。
Linux內核提供的3種中斷底半部機制:工作隊列,tasklet,軟中斷。其中軟中斷機制是內核底層的機制,包括定時器,非同步通知等很多機制都是基於軟中斷,開發者不應該直接操作,所以這裡僅討論前兩種
Tasklet
每個tasklet都和一個函數相關聯,當tasklet被運行時,該函數就被調用,並且tasklet可以調度自己。
//定義一個處理函數
void my_tasklet_fcn(unsigned long){}
//定義一個tasklet結構my_tasklet,並與處理函數相關聯,
DECLARE_TASKLET(my_tasklet,my_tasklet_fcn,data);
//調度tasklet
tasklet_schedule(&my_tasklet);
工作隊列
工作隊列和tasklet類型,tasklet多運行於中斷上下文,而工作隊列多運行與進程上下文
此外,tasklet中不能睡眠,而工作隊列處理函數允許睡眠(正是由於它被當作內核線程被調度)
//定義一個工作隊列
struct work_queue my_wq;
//定義一個處理函數
void my_wq_fcn(unsigned long){}
//初始化工作隊列並將其與處理函數綁定
INIT_WORK(&my_wq,my_wq_fcn);
//調度工作隊列執行
schedule_work(&my_wq);
static irqreturn_t handler_t(int irq, void *dev)
{
//top half
schedule_work(&ws);
return IRQ_HANDLED;
}
void work_func(struct work_struct *work)
{
//bottom half
ssleep(3);
}
static int __init demo_init(void)
{
...
INIT_WORK(&ws, work_func);
request_irq(irq, handler_t, IRQF_TRIGGER_RISING, "demo", NULL);
...
}
其他API
除了上述API,內核還提供了其他的中斷操作API,在內核代碼中被廣泛使用。
raw_local_irq_save(x) //禁止所有中斷
raw_local_irq_enable //取消禁止中斷
//下麵三個函數用於可編程中斷控制器,對所有CPU都有效
//屏蔽一個中斷
void disable_irq(int irq); //立即返回
void disable_irq_nosync(); //等待目前中斷處理返程
//使能一個中斷
void enable_irq()