/************************************************************************************html
*本文爲我的學習記錄,若有錯誤,歡迎指正。node
* https://blog.csdn.net/fridayll/article/details/51854126
數組
* http://www.javashuo.com/article/p-nsjepedc-bc.html
網絡
* https://www.linuxidc.com/Linux/2017-08/146264.htm數據結構
* https://www.cnblogs.com/amanlikethis/p/6941666.html?utm_source=itdadao&utm_medium=referral
架構
* http://www.javashuo.com/article/p-vtczcjzz-bs.html
app
************************************************************************************/ide
中斷是指在CPU正常運行期間,因爲內外部事件或由程序預先安排的事件引發的CPU暫時中止正在運行的程序,轉而爲該內部或外部事件或預先安排的事件服務的程序中去,服務完畢後再返回去繼續運行被暫時中斷的程序。Linux中一般分爲外部中斷(又叫硬件中斷)和內部中斷(又叫異常)。函數
linux內核將全部的中斷統一編號,使用一個irq_desc[NR_IRQS]的結構體數組來描述這些中斷:每一個數組項對應着一箇中斷源,記錄了中斷的入口處理函數(不是用戶註冊的處理函數)、中斷標記,並提供了中斷的底層硬件訪問函數(中斷清除、屏蔽、使能)。另外,經過這個結構體數組項成員action,可以找到用戶註冊的中斷處理函數。
(1)irq_desc[NR_IRQS]數組
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { [0 ... NR_IRQS-1] = { .status = IRQ_DISABLED, .chip = &no_irq_chip, .handle_irq = handle_bad_irq, .depth = 1, .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), } };
(2)struct irq_desc
Linux內核中使用struct irq_desc來描述一箇中斷源。Linux內核中將CPU的全部中斷進行編號,具體編號定義在/kernel/arch/arm/mach-s5pv210/include/mach/irqs.h。
struct irq_desc { unsigned int irq; /*中斷號*/ irq_flow_handler_t handle_irq; /* 當前中斷的處理函數入口 */ struct irq_chip *chip; /* 低層的硬件訪問 */ struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* 用戶提供的中斷處理函數鏈表 */ unsigned int status; /* IRQ狀態 */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; spinlock_t lock; const char *name; /* 中斷名稱 */ } ____cacheline_internodealigned_in_smp;
(3)struct irq_chip
struct irq_chip 是中斷控制器描述符, CPU所對應的一個具體的中斷控制器。
struct irq_chip { const char *name; unsigned int (*startup)(unsigned int irq); //啓動中斷 void (*shutdown)(unsigned int irq); //關閉中斷 void (*enable)(unsigned int irq); //使能中斷 void (*disable)(unsigned int irq); //禁止中斷 void (*ack)(unsigned int irq); //中斷應答函數,就是清除中斷標識函數 void (*mask)(unsigned int irq); //中斷屏蔽函數 void (*mask_ack)(unsigned int irq); //屏蔽中斷應答函數,通常用於電平觸發方式,須要先屏蔽再應答 void (*unmask)(unsigned int irq); //開啓中斷 void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); int (*set_affinity)(unsigned int irq, const struct cpumask *dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type);//設置中斷類型,其中包括設置GPIO口爲中斷輸入 int (*set_wake)(unsigned int irq, unsigned int on); void (*bus_lock)(unsigned int irq); //上鎖函數 void (*bus_sync_unlock)(unsigned int irq); //解鎖 /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
(4)struct irqaction
struct irqaction是中斷服務程序描述符,該IRQ對應的一系列中斷服務程序。
struct irqaction { irq_handler_t handler; //用戶註冊的中斷處理函數 unsigned long flags; //中斷標識 const char *name; //用戶註冊的中斷名字,cat/proc/interrupts時能夠看到 void *dev_id; //能夠是用戶傳遞的參數或者用來區分共享中斷 struct irqaction *next; //irqaction結構鏈,一個共享中斷能夠有多箇中斷處理函數 int irq; //中斷號 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; };
Linux內核在初始化階段調用 init_IRQ()函數用來初始化中斷體系結構,即初始化irq_desc[NR_IRQS]數組。
//所在文件:/kernel/arch/arm/kernel/irq.c void __init init_IRQ(void) { int irq; for (irq = 0; irq < NR_IRQS; irq++) irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; init_arch_irq(); }
s5pv210_init_irq()函數在arch/arm/mach-s5pv210/cpu.c中定義,它爲全部的中斷設置了芯片相關的數據結構irq_desc[irq].chip,設置了處理函數入口irq_desc[irq].handle_irq。
void __init s5pv210_init_irq(void) { u32 vic[4]; /* S5PV210 supports 4 VIC */ /* All the VICs are fully populated. */ vic[0] = ~0; vic[1] = ~0; vic[2] = ~0; vic[3] = ~0; s5p_init_irq(vic, ARRAY_SIZE(vic)); }
中斷的處理流程以下:
1) 發生中斷後,CPU執行異常向量vector_irq的代碼;
2)在vector_irq裏面,最終會調用中斷處理C程序總入口函數asm_do_IRQ();
3)asm_do_IRQ()根據中斷號調用irq_des[NR_IRQS]數組中的對應數組項中的handle_irq();
4)handle_irq()會使用chip的成員函數來設置硬件,例如清除中斷,禁止中斷,從新開啓中斷等;
5)handle_irq逐個調用用戶在action鏈表中註冊的處理函數。
可見,中斷體系結構的初始化,就是構造irq_desc[NR_IRQS]這個數據結構;用戶註冊中斷就是構造action鏈表;用戶卸載中斷就是從action鏈表中去除對應的項。
(1)申請IRQ
/* 參數: ** irq:要申請的硬件中斷號
** ** handler:中斷處理函數(頂半部)
** ** irqflags:觸發方式及工做方式 ** 觸發方式:IRQF_TRIGGER_RISING 上升沿觸發 ** IRQF_TRIGGER_FALLING 降低沿觸發 ** IRQF_TRIGGER_HIGH 高電平觸發 ** IRQF_TRIGGER_LOW 低電平觸發
** ** 工做方式:默認是快速中斷(一個設備佔用,且中斷例程回調過程當中會屏蔽中斷) ** IRQF_SHARED:共享中斷
** ** dev_id:在共享中斷時會用到(中斷註銷與中斷註冊的此參數應保持一致)
** ** 返回值:成功返回 - 0;失敗返回 - 負值(絕對值爲錯誤碼) */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
(2)釋放IRQ
/* 參數: ** irq:要註銷的硬件中斷號 ** dev_id:在共享中斷時會用到(中斷註銷與中斷註冊的此參數應保持一致) */ void free_irq(unsigned int irq, void *dev_id);
(1)頂半部和底半部機制
因爲中斷服務程序的執行並不存在於進程上下文,所以,要求中斷服務程序的時間儘量的短。 爲了在中斷執行事件儘量短和中斷處理需完成大量耗時工做之間找到一個平衡點,Linux將中斷處理分爲兩個部分:頂半部(top half)和底半部(bottom half)。
頂半部完成儘量少的比較緊急的功能,它每每只是簡單地讀取寄存器中的中斷狀態並清除中斷標誌後進行「登記中斷」的工做。「登記」意味着將底半部的處理程序掛載到該設備的底半部指向隊列中去。底半部做爲工做重心,完成中斷事件的絕大多數任務。
(2)頂半部和底半部劃分原則
1) 若是一個任務對時間很是敏感,將其放在頂半部中執行;
2) 若是一個任務和硬件有關,將其放在頂半部中執行;
3) 若是一個任務要保證不被其餘中斷打斷,將其放在頂半部中執行;
4) 若是中斷要處理的工做自己不多,全部的工做可在頂半部所有完成;
5) 其餘全部任務,考慮放置在底半部執行。
(3)舉例分析
當網卡接受到數據包時,通知內核,觸發中斷,所謂的頂半部就是,及時讀取數據包到內存,防止由於延遲致使丟失,這是很急迫的工做。讀到內存後,對這些數據的處理再也不緊迫,此時內核能夠去執行中斷前運行的程序,而對網絡數據包的處理則交給底半部處理。
引入tasklet,最主要的是考慮支持SMP,提升SMP多個cpu的利用率;不一樣的tasklet能夠在不一樣的cpu上運行。可是tasklet屬於中斷上下文,所以不能被阻塞,不能睡眠,不可被打斷。
tasklet使用模版:
/* 定義tasklet和底半部處理函數並關聯 */ void xxx_tasklet(unsigned long data); //定義一個名爲 my_tasklet 的 struct tasklet 並將其與 my_tasklet_func 綁定,data爲傳入 my_tasklet_func的參數 DECLARE_TASKLET(xxx_tasklet, xxx_tasklet_func, data); /* 中斷處理底半部 */ void xxx_tasklet_func() { /* 中斷處理具體操做 */ } /* 中斷處理頂半部 */ irqreturn xxx_interrupt(int irq, void *dev_id) { //do something task_schedule(&xxx_tasklet);//調用tasklet_schedule()函數使系統在適當的時候調度運行底半部 //do something return IRQ_HANDLED; } /* 設備驅動模塊 init */ int __init xxx_init(void) { ... /* 申請設備中斷 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL); ... return 0; } /* 設備驅動模塊exit */ void __exit xxx_exit(void) { ... /* 釋放中斷 */ free_irq(xxx_irq, NULL); } module_init(xxx_init); module_exit(xxx_exit);
workqueue的突出特色是下半部會交給worker thead,所以下半部處於進程上下文,能夠被從新調度,能夠阻塞,也能夠睡眠。
workqueue使用模板:
/* 定義工做隊列和底半部函數 */ struct work_struct xxx_wq; void xxx_do_work(unsigned long); /* 中斷處理底半部 */ void xxx_work(unsigned long) { /* do something */ } /* 中斷處理頂半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id) { ... schedule_work(&xxx_wq); ... return IRQ_HANDLED; } /* 設備驅動模塊 init */ int __init xxx_init(void) { ... /* 申請設備中斷 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL); /* 初始化工做隊列 */ INIT_WORK(&xxx_wq, xxx_do_work);//關聯工做隊列和底半部函數 ... return 0; } /* 設備驅動模塊exit */ void __exit xxx_exit(void) { ... /* 釋放中斷 */ free_irq(xxx_irq, NULL); } module_init(xxx_init); module_exit(xxx_exit);