Linux中斷編程
中斷:是指CPU在運(yùn)行過程中,出現(xiàn)了某種異常事件,需要CPU先暫停當(dāng)前工作,轉(zhuǎn)而去處理新產(chǎn)生的異常事件,處理完后再返回暫停的事件繼續(xù)往下執(zhí)行。就例如我們正在使用手機(jī)進(jìn)行微信視頻聊天,這時(shí)突然有人打電話過來,這時(shí)手機(jī)的處理方式是手機(jī)來來電鈴聲響起,通知用戶電話來了。
中斷,就是來處理未來時(shí)間內(nèi)可能會(huì)發(fā)生的事件, 中斷事件也稱為異常事件。有了中斷處理,則可大大提高CPU處理效率。
在單片機(jī)中,我們也常用中斷方式來處理一些緊急事件,幫我們實(shí)現(xiàn)快速響應(yīng)一些實(shí)時(shí)性的事件。因此我們在編寫中斷服務(wù)函數(shù)時(shí)都是代碼盡可能簡潔、一定不能處理死循環(huán)、若需要處理的事情比較多則應(yīng)在中斷中設(shè)定標(biāo)志位,然后將邏輯代碼放到主函數(shù)中去實(shí)現(xiàn)。
在Linux內(nèi)核中,我們一般會(huì)將中斷分為頂半部分和底半部分。頂半部分主要是處理耗時(shí)短的代碼(像單片機(jī)中設(shè)置標(biāo)志位),啟動(dòng)底半部分代碼;底半部分主要是處理耗時(shí)比較長的代碼,完成中斷響應(yīng)后的事件處理。
1. Linux下外部中斷
??要使用外部中斷,則需要完成中斷三要素的配置:中斷號(hào)(irq)、中斷服務(wù)函數(shù)、中斷觸發(fā)方式(電平觸發(fā)、邊沿觸發(fā))。
1.1 相關(guān)接口函數(shù)
- 獲取中斷號(hào)gpio_to_irq
??在Linux內(nèi)核中提供了方便函數(shù)獲取引腳中斷號(hào)
int gpio_to_irq(unsigned gpio)
函數(shù)功能: 獲取中斷號(hào)
返回值: 成功返回對應(yīng)GPIO的中斷號(hào)irq
- 注冊中斷request_irq
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
函數(shù)功能: 注冊中斷
形 參: irq --中斷號(hào),gpio_to_irq函數(shù)返回值。
?? ?handler --中斷服務(wù)函數(shù)。
?? ??服務(wù)函數(shù)原型:typedef irqreturn_t (*irq_handler_t)(int, void *);
?? ?flags --中斷觸發(fā)方式。
?? ??#define IRQF_TRIGGER_RISING 0x00000001 //上升沿
?? ??#define IRQF_TRIGGER_FALLING 0x00000002 //下升沿
???? #define IRQF_TRIGGER_HIGH 0x00000004//高電平
????#define IRQF_TRIGGER_LOW 0x00000008//低電平
????#define IRQF_SHARED 0x00000080 //共享中斷
???name --中斷注冊標(biāo)志。
???dev --傳給中斷服務(wù)函數(shù)的參數(shù)。
返回值: 成功返回0,失敗返回其它值。
- 中斷服務(wù)函數(shù)
typedef irqreturn_t (*irq_handler_t)(int, void *);
函數(shù)功能: 中斷服務(wù)函數(shù)
形 參: 第一個(gè)參數(shù)為中斷號(hào);第二個(gè)參數(shù)為注冊函數(shù)傳入的參數(shù)dev
返回值:
?? enum irqreturn {
????IRQ_NONE = (0 << 0), //如果不是本中斷的則返回這個(gè)值,只在共享中斷中使用
????IRQ_HANDLED = (1 << 0), //正確執(zhí)行中斷程序返回這個(gè)值,常用
????IRQ_WAKE_THREAD = (1 << 1), //表示去喚醒中斷處理者的線程
??};
??注意: irqreturn_t (*irq_handler_t)(int, void *);函數(shù)中不能出現(xiàn)帶休眠的函數(shù),如msleep函數(shù);該函數(shù)必須要返回值。
- 注銷free_irq
free_irq(unsigned int irq, void *dev_id)
函數(shù)功能: 注銷中斷
形 參: irq --中斷號(hào),gpio_to_irq函數(shù)返回值。
???dev --傳給中斷服務(wù)函數(shù)的參數(shù)。需和注冊時(shí)保持一致
2. 工作隊(duì)列
??中斷處理函數(shù)分為中斷頂半部分和中斷底半部分。頂半部分代碼實(shí)現(xiàn)即為中斷服務(wù)函數(shù),而底半部分代碼則可由工作隊(duì)列完成。
2.1 工作隊(duì)列簡介
在操作系統(tǒng)中,如果我們需要進(jìn)行一項(xiàng)工作處理,往往需要?jiǎng)?chuàng)建一個(gè)任務(wù)來加入內(nèi)核的調(diào)度隊(duì)列。一個(gè)任務(wù)對應(yīng)一個(gè)處理函數(shù),如果要進(jìn)行不同的事務(wù)處理,則需要?jiǎng)?chuàng)建多個(gè)不同的任務(wù)。任務(wù)作為CPU調(diào)度的基本單元,任務(wù)數(shù)量越大,則調(diào)度成本越高。工作隊(duì)列(workqueue)機(jī)制簡化了基礎(chǔ)的任務(wù)創(chuàng)建和處理機(jī)制,一個(gè)workqueue對應(yīng)一個(gè)實(shí)體task任務(wù)處理,工作隊(duì)列中可以掛載多個(gè)工作實(shí)體,每一個(gè)工作都能對應(yīng)不同的工作處理函數(shù)。即用戶只需要?jiǎng)?chuàng)建一個(gè)workqueue,則可以完成多個(gè)掛接不同處理函數(shù)的工作隊(duì)列。
工作隊(duì)列還具有將工作推后執(zhí)行機(jī)制,工作隊(duì)列可以把工作推后,交由一個(gè)內(nèi)核線程去執(zhí)行,也就是說,這個(gè)下半部分可以在進(jìn)程上下文中執(zhí)行。最重要的就是工作隊(duì)列允許被重新調(diào)度甚至是睡眠。
workqueue的處理依賴于task任務(wù)。一個(gè)workqueue隊(duì)列會(huì)創(chuàng)建關(guān)聯(lián)其對應(yīng)的task任務(wù),一個(gè)workqueue會(huì)掛載多個(gè)工作進(jìn)行處理,每個(gè)工作都有工作處理函數(shù)。當(dāng)workqueue得到調(diào)度,即其關(guān)聯(lián)的task得到運(yùn)行,在每次task的調(diào)度期間,都會(huì)從工作隊(duì)列中按照先后順序取出一個(gè)work來進(jìn)行處理。workqueue模塊在初始化時(shí),會(huì)創(chuàng)建一個(gè)系統(tǒng)默認(rèn)的工作隊(duì)列,用戶可根據(jù)需要將work添加到該隊(duì)列中去執(zhí)行。
2.2 工作相關(guān)函數(shù)接口
- 工作結(jié)構(gòu)體struct work_struct
#include
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /*工作處理函數(shù)*/
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
??在工作結(jié)構(gòu)體體中,我們需要關(guān)心的成員是工作處理函數(shù):work_func_t func,簡單來說即一個(gè)工作會(huì)對應(yīng)有一個(gè)處理函數(shù)。工作處理函數(shù)原型如下:
#include
typedef void (*work_func_t)(struct work_struct *work);
- 初始化工作INIT_WORK
#define INIT_WORK(_work, _func)
函數(shù)功能: 初始化工作,以宏的方式實(shí)現(xiàn)
形 參: _work --工作結(jié)構(gòu)體體指針
???_func --工作處理函數(shù)
- 工作調(diào)度schedule_work
int schedule_work(struct work_struct *work)
2.3工作隊(duì)列使用步驟
- 定義工作結(jié)構(gòu)體struct work_struct,初始化工作INIT_WORK;
- 編寫工作處理函數(shù)void (*work_func_t)(struct work_struct *work);
- 在合適的地方調(diào)調(diào)度工作(一般在中斷頂半部分);
2.4工作隊(duì)列使用示例
??下面以按鍵為例,實(shí)現(xiàn)中斷方式按鍵檢測,通過工作隊(duì)列處理底半部分代碼,雜項(xiàng)設(shè)備框架實(shí)現(xiàn)設(shè)備注冊。
- K1 – GPX3_2
- K2 --GPX3_3
- K3 --GPX3_4
- K4 --GPX3_5
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct work_struct key_work;/*工作結(jié)構(gòu)體*/
struct _KEY
{
unsigned int gpio;/*按鍵引腳*/
char name[20];/*按鍵名*/
int irq;/*中斷號(hào)*/
int key_num;/*按鍵編號(hào)*/
};
static struct _KEY KEY_GPIO_PIN[]=
{
{EXYNOS4_GPX3(2),"key1",0,1},
{EXYNOS4_GPX3(3),"key2",0,2},
{EXYNOS4_GPX3(4),"key3",0,3},
{EXYNOS4_GPX3(5),"key4",0,4},
};
static struct _KEY *key_p;
static unsigned int key_val;
/*工作服務(wù)函數(shù)*/
void key_work_func(struct work_struct *work)
{
msleep(30);/*按鍵消抖*/
if(gpio_get_value(key_p->gpio)==0)
{
printk(" key%d 按下n",key_p->key_num);
}
else
{
printk(" key%d 松開n",key_p->key_num);
}
key_val=key_p->key_num;
}
/*中斷服務(wù)函數(shù)*/
static irqreturn_t key_irq_handler(int irq, void *dev)
{
key_p=(struct _KEY *)dev;
schedule_work(&key_work);/*調(diào)度工作*/
return IRQ_HANDLED;/*中斷正常處理*/
}
static int key_open(struct inode *inode, struct file *file)
{
printk("設(shè)備打開成功n");
return 0;
}
static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *seek)
{
int res=copy_to_user(buf,&key_val, 4);
key_val=0;
return 4-res;
}
static int key_release(struct inode *inode, struct file *file)
{
printk("設(shè)備關(guān)閉成功n");
return 0;
}
/*文件操作集合*/
static struct file_operations key_fops=
{
.owner= THIS_MODULE, /*當(dāng)前模塊文件操作集合所有者*/
.open=key_open,/*open函數(shù)接口*/
.read=key_read,/*read函數(shù)接口*/
.release=key_release,/*close函數(shù)接口*/
};
/*
字符設(shè)備注冊:主設(shè)備+次設(shè)備號(hào)
主設(shè)備 --用來區(qū)分類(雜項(xiàng)設(shè)備、輸入設(shè)備)
次設(shè)備號(hào) --對應(yīng)哪個(gè)設(shè)備
雜項(xiàng)設(shè)備的主設(shè)備號(hào)固定為:10
*/
static struct miscdevice key_miscdev = {
.minor = MISC_DYNAMIC_MINOR,/*次設(shè)備號(hào)255由內(nèi)核分配*/
.name = "tiny4412_key",/*設(shè)備節(jié)點(diǎn)名字,會(huì)在/dev下生成*/
.fops = &key_fops,/**/
};
static int __init tiny4412_key_module_init(void)
{
int i=0;
int res;
printk("hello,驅(qū)動(dòng)注冊成功n");
/*初始化工作*/
INIT_WORK(&key_work,key_work_func);
/*注冊中斷*/
for(i=0;i(key_gpio_pin)>
審核編輯:湯梓紅
-
Linux
+關(guān)注
關(guān)注
87文章
11292瀏覽量
209318 -
編程
+關(guān)注
關(guān)注
88文章
3614瀏覽量
93685 -
中斷
+關(guān)注
關(guān)注
5文章
898瀏覽量
41470
發(fā)布評論請先 登錄
相關(guān)推薦
評論