姓名:鄭煜爍? 學號:19029100010? ?學院:電子工程學院
轉自:https://blog.csdn.net/u012142460/article/details/79272207
【嵌牛導讀】介紹linux系統(tǒng)中如何對突發(fā)事件進行處理
【嵌牛鼻子】中斷處理
【嵌牛提問】如何進行中斷處理
【嵌牛正文】
一、中斷介紹
所謂中斷是指CPU在執(zhí)行程序的過程中澡屡,出現(xiàn)了某些突發(fā)事件需要緊急處理瓶埋,CPU必須暫時停止當前的工作赞弥,轉去執(zhí)行處理突發(fā)事件,處理完畢又返回原程序被中斷的位置繼續(xù)執(zhí)行锦亦。
在ARM多核處理器中最常用的中斷控制器是GIC锰提,支持三類中斷
1、SGI:Software Generated Interrupt腰耙,軟件產(chǎn)生的中斷,用于多核的核間通信
2铲球、PPI:Private Peripheral Interrupt挺庞,某個CPU私有外設的中斷,這類外設的中斷只能發(fā)給綁定的那個CPU
3稼病、SPI:Shared Peripheral Interrupt 共享外設中斷选侨,這類外設的中斷可以路由到任何一個CPU
在proc/interrupts文件可以獲得中斷信息。
二 然走、中斷API
看一下常用的中斷相關的API函數(shù)
1援制、申請中斷
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
? ? ? ? const char *name, void *dev)
功能:申請中斷
參數(shù):irq:中斷號,這個中斷號不是硬件手冊上的中斷號丰刊,而是linux的中斷號隘谣。
? ? ? ? handler:中斷處理函數(shù)
? ? ? ? flags:標志位? ? ? ? ? ? #define IRQF_TRIGGER_RISING0x00000001? 上升沿觸發(fā)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #define IRQF_TRIGGER_FALLING0x00000002? 下降沿觸發(fā)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #define IRQF_TRIGGER_HIGH0x00000004? ? ? ? ? 高電平觸發(fā)
? ? ? ? ? ? ? ? ? ? ? #define IRQF_TRIGGER_LOW 0x00000008? ? ? ? ? 低電平觸發(fā)? ? IRQF_SHARED? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 共享標志增拥,表示該中斷可以被多個設備共享
? ? ? name:中斷的名稱
? ? ? dev:要傳遞給中斷服務程序的私有數(shù)據(jù)啄巧,一般設置為這個設備的結構體或者為NULL
返回值:成功返回0,返回-EINVAL表示中斷號無效或處理函數(shù)指針為NULL掌栅,返回-EBUSY表示中斷已經(jīng)占用且不能共享秩仆。
2、釋放中斷
void free_irq(unsigned int irq, void *dev_id)
參數(shù)與申請中斷中的參數(shù)一樣猾封。
三澄耍、中斷上下部機制
? ? ? ? 中斷會打斷內核進程中的正常調度,系統(tǒng)對更高吞吐率的追求勢必要求中斷服務程序盡量短小精悍晌缘。但實際上齐莲,很多中斷程序需要處理很多事務,可能進行大量的耗時操作磷箕。
? ? ? Linux為了解決這個問題选酗,找出一個平衡點,將中斷程序分成了兩個部分:頂半部和底半部岳枷。頂半部處理緊急事務芒填,底半部處理耗時操作呜叫。頂半部和底半部最大不同在于,底半部是可以被其他中斷打斷的殿衰,這樣就不會耽誤其他中斷的進行了朱庆。
? ? ? 頂半部處理緊急事務,一些中斷必須的事務在此處理闷祥,比如讀中斷狀態(tài)娱颊,清中斷標志等等,這些都是必要但很簡單的工作凯砍。在底半部主要處理一些不太緊要的工作维蒙,必要數(shù)據(jù)的處理等等。
? ? ? 盡管上述機制能夠改善系統(tǒng)的響應能力果覆,但不能僵化的把所有中斷驅動分成兩個半部颅痊,如果本身處理情況就比較簡單,則是完全可以在頂半部來全部完成的局待。
我們使用tasklet和工作隊列來完成頂半部底半部機制
1斑响、tasklet
taskelet實際上是linux中軟中斷的一種,Linux提供了一系列宏和函數(shù)來完成tasklet
DECLARE_TASKLET(name, func, data)
功能:定義一個tasklet對象
參數(shù):name:名稱
? ? ? ? ? func:底半部函數(shù)
? ? ? ? ? data:傳遞給底半部函數(shù)的參數(shù)
DECLARE_TASKLET_DISABLED(name, func, data)
功能和上面類似钳榨,但還需要調用task_enable使能一下舰罚。
static inline void tasklet_schedule(struct tasklet_struct *tasklet)
功能:將指定的tasklet對象添加的加入到tasklet列表中,要執(zhí)行底半部函數(shù)需要先執(zhí)行這個函數(shù)薛耻,一般在頂半部函數(shù)執(zhí)行营罢。
2、工作隊列
工作隊列的方法也可以實現(xiàn)頂半部底半部機制
struct work_struct {
atomic_long_t data;? ? ? //傳遞給工作函數(shù)的參數(shù)
struct list_head entry;
work_func_t func;? ? ? ? //工作函數(shù)饼齿,可以理解就是底半部函數(shù)饲漾。
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
工作隊列對象結構體。使用工作隊列需要首先定義一個工作隊列對象
? INIT_WORK(_work, _func)
功能:將工作隊列與底半部函數(shù)綁定缕溉。
參數(shù): _work考传,定義的工作隊列
? ? ? ? ? _func,定義的底半部函數(shù)证鸥,可以簡單的理解成將底半部函數(shù)賦值給定義的工作隊列對象中func變量僚楞。
int schedule_work(struct work_struct *work)
功能:將工作隊列節(jié)點加入到工作隊列鏈表中,和tasklet中的tasklet_schedule函數(shù)功能類似枉层,一般在頂半部函數(shù)中完成泉褐。
參數(shù):work,工作隊列對象
3鸟蜡、taskelet與工作隊列的異同
? ? ? ? 兩者有什么區(qū)別呢膜赃?taskelet是在中斷上下文來完成的,而中斷中是不能進行進程調度的矩欠,所以在tasklet的頂半部和底半部都能進行進程的調度财剖。工作隊列是處于進程上下文中的悠夯,所以底半部是工作隊列時,是可以進程進程調度的
? ? ? ? 在使用時躺坟,步驟基本一致沦补,綁定工作隊列/tasklet和底半部函數(shù),然后在頂半部中執(zhí)行schedule函數(shù)咪橙,執(zhí)行底半部函數(shù)夕膀。
實例,在迅為4412開發(fā)板上美侦,實現(xiàn)按鍵中斷
原理圖如下
我們只拿一個來做例子产舞,UART_RING,看他對應的GPIO口
它對應的是GPIOX1_1
得到中斷號有一個API? gpio_to_irq(gpio)菠剩,三星定義了GPIOX1_1的gpio值為EXYNOS4_GPX1(1)易猫,這樣就可以得到中斷號了。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <mach/gpio-exynos4.h>
#define TASKLET;
int irq1;
#ifdef WORK_QUEUE
struct work_struct? key_wq;? //定義一個工作隊列對象
#endif
void key_tasklet_func(unsigned long data);
#ifdef TASKLET
DECLARE_TASKLET(key_tasklet,key_tasklet_func,0); //綁定tasklet對象和函數(shù)
#endif
void key_tasklet_func(unsigned long data)
{
printk(KERN_INFO"key_tasklet_func enter...\n");
}
irqreturn_t key_handler(int irq, void *date)
{
printk(KERN_INFO"key1 enter...\n");
#ifdef TASKLET
tasklet_schedule(&key_tasklet); ? //執(zhí)行tasklet底半部
#endif
#ifdef WORK_QUEUE
schedule_work(&key_wq);? ? ? ? ? ? //執(zhí)行工作隊列底半部
#endif
return IRQ_HANDLED;
}
static int __init demo_key_init(void)
{
int ret = 0;
irq1 = gpio_to_irq(EXYNOS4_GPX1(1));
ret = request_irq(irq1,key_handler,IRQF_TRIGGER_FALLING,"KEY222",NULL);
if(ret < 0){
printk(KERN_INFO"request_irq fail...%s,%d,ret:%d\n",__func__,__LINE__,ret);
return -EINVAL;
printk(KERN_INFO"%s,%d\n",__func__,__LINE__);
#ifdef WORK_QUEUE
INIT_WORK(&key_wq,key_tasklet_func);? //初始化一個工作隊列
#endif
return 0;
}
static void? __exit demo_key_exit(void)
{
free_irq(irq1,NULL);
printk(KERN_INFO"%s,%d\n",__func__,__LINE__);
}
module_init(demo_key_init);
module_exit(demo_key_exit);
MODULE_LICENSE("GPL");
————————————————
版權聲明:本文為CSDN博主「念念有余」的原創(chuàng)文章具壮,遵循CC 4.0 BY-SA版權協(xié)議准颓,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012142460/article/details/79272207