在硬件上,中斷源可以通過中斷控制器向CPU提交中斷,進(jìn)而引發(fā)中斷處理程序的執(zhí)行,不過這種硬件中斷體系每一種CPU都不一樣,而Linux作為操作系統(tǒng),需要同時(shí)支持這些中斷體系,如此一來,Linux中就提出了軟中斷的概念,也有人叫內(nèi)核中斷,其本質(zhì)就是使用統(tǒng)一的方式對不同硬件中斷體系中的中斷號進(jìn)行再映射,在操作系統(tǒng)中操作的中斷號都是這些映射過的軟中斷號。就是下圖中的irq_num
Linux內(nèi)核中定義了名為irq_desc的中斷例程描述符表來描述中斷服務(wù)例程,每一個(gè)irq_desc對象數(shù)組中的下標(biāo)就是軟中斷號。其中的struct irqaction對象描述了這個(gè)中斷服務(wù)例程的中斷的具體信息。
//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表示中斷源的數(shù)目
//linux/interrupt.h104 //一個(gè)irq action的描述結(jié)構(gòu)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: 中斷處理函數(shù)
--106-->name: 設(shè)備名
--107-->dev_id: 設(shè)備識別id
--109-->next: 指向下一個(gè)irqaction的指針
--110-->irq: 硬件中斷號!!!
--113-->flags:IRQF_DISABLED .etc
--110-->thread_fn: 線程中斷的中斷處理函數(shù)
--111-->thread: 線程中斷的線程指針
--114-->thread_flags:?與@thread關(guān)聯(lián)的flags
--115-->thread_mask:?追蹤@thread?activity的位掩碼
--116-->dir: 指向proc/irq/NN/name的入口指針
raw_local_irq_save(x) //禁止所有中斷raw_local_irq_enable //取消禁止中斷
寫中斷處理程序
1. 編寫中斷處理函數(shù)
下面這個(gè)就是中斷處理程序的原型,中斷發(fā)生后,內(nèi)核會將軟中斷號和注冊時(shí)的data作為參數(shù)傳入。中斷處理程序ISR是在中斷發(fā)生時(shí)被調(diào)用的用來處理中斷的函數(shù),不是進(jìn)程上下文,在中斷期間運(yùn)行,不能執(zhí)行可能休眠的操作,不能同用戶空間交換數(shù)據(jù),不能調(diào)用schedule()函數(shù)放棄調(diào)度
實(shí)現(xiàn)中斷處理有一個(gè)原則:盡可能快的處理并返回,冗長的計(jì)算處理工作應(yīng)該交給tasklet或任務(wù)隊(duì)列在安全的時(shí)間內(nèi)進(jìn)行。此外,硬件系統(tǒng)中常使用共享中斷,即多個(gè)設(shè)備共享一根線。即該(軟硬)中斷號可以被多個(gè)設(shè)備共享,這在實(shí)際的硬件連接中經(jīng)常見到,可以節(jié)約很多資源,但是如此一來,就給軟件的編寫的提出了要求,內(nèi)核給出的方案是一旦接收到來自一條中斷線的中斷,它就會循環(huán)執(zhí)行所有注冊到該中斷線(->硬中斷號->軟中斷號)的handler,這樣,每一個(gè)handler就有義務(wù)判斷到底是不是自己負(fù)責(zé)的設(shè)備來的中斷,handler原型的dev_id也正是為了這個(gè)目的而存在,我們可以將該handler負(fù)責(zé)的設(shè)備的中斷狀態(tài)寄存器的地址作為dev_id和handler一起注冊,當(dāng)內(nèi)核遍歷執(zhí)行所有的handler的時(shí)候,會將每一個(gè)中斷的dev_id作為參數(shù)依次傳入每一個(gè)handler,在每一個(gè)handler內(nèi)部首次通過讀取這個(gè)寄存器來快速判斷是否是自己負(fù)責(zé)的設(shè)備發(fā)出的。這也就是共享中斷必須設(shè)置dev_id參數(shù)的原因之一。至此,就可以實(shí)現(xiàn)多個(gè)設(shè)備對這一中斷線的"shared"。注意,對于這個(gè)"shared",并不是在時(shí)間上允許多個(gè)中斷同時(shí)發(fā)生,而是一種空間上的、中斷號上的"shared",此外,這個(gè)"shared"和三星芯片中常見的Shared Peripheral Interrupts(SPI)不是一回事,后者表示這個(gè)中斷可以被GIC router到任意一個(gè)CPU中。
88 typedef irqreturn_t (*irq_handler_t)(int, void *);
irqreturn_t xxx_interrupt(int irq, void *dev_id){ ... int status = read_irq_status(); /* 讀取相應(yīng)的寄存器獲取中斷源 */ if(!is_myirq(dev_id,status)){ /* 判斷是否是本設(shè)備中斷 */ return IRQ_NONE; } /* 中斷處理程序 */ return IRQ_HANDLED}
2. 注冊中斷處理函數(shù)
下面這個(gè)就是注冊中斷的API,flags取值在"interrupt.h"有定義,常用的有IRQF_DISABLED和IRQF_SHARED,前者表示中斷程序調(diào)用時(shí)屏蔽所有中斷,"所有"指的是屏蔽所有中斷線的中斷,本中斷線的中斷本來就是屏蔽的,內(nèi)核默認(rèn)不允許中斷嵌套。IRQF_SHARED表示多個(gè)設(shè)備共享中斷,即該中斷線上連接了多個(gè)相同或不同的設(shè)備。
除了中斷類型,flags還需要"位或"上觸發(fā)方式,eg:IRQF_DISABLED|IRQF_TRIGGER_RISING
/** * @flags:中斷標(biāo)志位。 * @dev_id用于共享中斷,用來標(biāo)識是哪個(gè)設(shè)備觸發(fā)了中斷,通常傳入相應(yīng)設(shè)備的中斷狀態(tài)寄存器的地址 * @name 是中斷名稱,可以在/proc/interrupt中看到 */int requst_irq(unsigned int irq,irq_handler_t handler, unsigned long flags, const char *name,void * dev_id);
3. 釋放中斷處理函數(shù)
中斷號也是一種系統(tǒng)資源,使用完畢后要釋放,注意,free_irq的第二個(gè)參數(shù)應(yīng)當(dāng)與request_irq()中最后一個(gè)參數(shù)相同,這樣才能將這個(gè)handler從這個(gè)中斷線中的handler鏈表中刪除。
/** * free_irq - 釋放irq */void free_irq(unsigned int irqno,void * dev_id);
底半部
中斷不屬于進(jìn)程上下文,所以不能被內(nèi)核調(diào)度,如果進(jìn)入了中斷處理函數(shù),就只能將其執(zhí)行完畢,不能被打斷,這樣帶來的一個(gè)問題是如果在中斷處理函數(shù)中執(zhí)行耗時(shí)操作,就會極大的影響系統(tǒng)性能,為了解決這個(gè)問題,Linux內(nèi)核中提出了中斷頂半部和`底半部(bottom half)的概念,對于耗時(shí)的中斷處理程序,將重要的、必要的操作放在頂半部執(zhí)行,這部分和傳統(tǒng)的中斷概念一樣,一旦開始就必須執(zhí)行完畢;將中斷處理程序中耗時(shí)的,不那么緊要的操作放在底半部,防止整個(gè)中斷處理程序過多的占用系統(tǒng)資源。
Linux內(nèi)核提供的3種中斷底半部機(jī)制:工作隊(duì)列,tasklet,軟中斷。其中軟中斷機(jī)制是內(nèi)核底層的機(jī)制,包括定時(shí)器,異步通知等很多機(jī)制都是基于軟中斷,開發(fā)者不應(yīng)該直接操作,所以這里僅討論前兩種
Tasklet
每個(gè)tasklet都和一個(gè)函數(shù)相關(guān)聯(lián),當(dāng)tasklet被運(yùn)行時(shí),該函數(shù)就被調(diào)用,并且tasklet可以調(diào)度自己。
//定義一個(gè)處理函數(shù)void my_tasklet_fcn(unsigned long){}//定義一個(gè)tasklet結(jié)構(gòu)my_tasklet,并與處理函數(shù)相關(guān)聯(lián),DECLARE_TASKLET(my_tasklet,my_tasklet_fcn,data);//調(diào)度tasklettasklet_schedule(&my_tasklet);
工作隊(duì)列
工作隊(duì)列和tasklet類型,tasklet多運(yùn)行于中斷上下文,而工作隊(duì)列多運(yùn)行與進(jìn)程上下文
此外,tasklet中不能睡眠,而工作隊(duì)列處理函數(shù)允許睡眠(正是由于它被當(dāng)作內(nèi)核線程被調(diào)度)
//定義一個(gè)工作隊(duì)列struct work_queue my_wq;//定義一個(gè)處理函數(shù)void my_wq_fcn(unsigned long){}//初始化工作隊(duì)列并將其與處理函數(shù)綁定INIT_WORK(&my_wq,my_wq_fcn);//調(diào)度工作隊(duì)列執(zhí)行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,內(nèi)核還提供了其他的中斷操作API,在內(nèi)核代碼中被廣泛使用。
raw_local_irq_save(x) //禁止所有中斷raw_local_irq_enable //取消禁止中斷//下面三個(gè)函數(shù)用于可編程中斷控制器,對所有CPU都有效//屏蔽一個(gè)中斷void disable_irq(int irq); //立即返回void disable_irq_nosync(); //等待目前中斷處理返程//使能一個(gè)中斷void enable_irq()
?
評論
查看更多