裸機(jī)編程中使用中斷比較麻煩,需要配置寄存器、使能IRQ等等。而在Linux驅(qū)動(dòng)編程中,內(nèi)核提供了完善的終端框架,只需要申請(qǐng)中斷,然后注冊(cè)中斷處理函數(shù)即可,使用非常方便
1. 內(nèi)核中斷
Linux內(nèi)核中斷分為上半部和下半部,其主要目的就是實(shí)現(xiàn)中斷處理函數(shù)的快進(jìn)快出,兩者的區(qū)別如下:
-
上半部:就是中斷處理函數(shù),處理過(guò)程比較快,不會(huì)占用很長(zhǎng)時(shí)間
-
下半部:若中斷處理過(guò)程很耗時(shí),就將這些代碼提出來(lái),交給下半部去執(zhí)行,這樣中斷處理函數(shù)就會(huì)快進(jìn)快出
一般情況下,若要處理的內(nèi)容不希望被其他中斷打斷、要處理的任務(wù)對(duì)時(shí)間敏感或者要處理的任務(wù)與硬件有關(guān),通常放到上半部;其他情況優(yōu)先考慮放到下半部
?
2. 中斷下半部實(shí)現(xiàn)方式
上半部直接編寫中斷處理函數(shù)處理即可,下半部的實(shí)現(xiàn)方式主要有如下三種:?軟中斷:內(nèi)核使用結(jié)構(gòu)體softirq_action表示軟中斷,定義在文件include/linux/interrupt.h中
struct?softirq_action{
?void?(*action)(struct?softirq_action?*);
}
//文件?kernel/softirq.c?中共定義了10個(gè)軟中斷
static?struct?softirq_action?softirq_vec[NR_SOFTIRQS];
//NR_SOFTIRQS?是枚舉類型,定義在文件?include/linux/interrupt.h中
enum
{
?HI_SOFTIRQ=0,??? /*?高優(yōu)先級(jí)軟中斷?*/
?TIMER_SOFTIRQ,??? /*?定時(shí)器軟中斷?*/
?NET_TX_SOFTIRQ,?? /*?網(wǎng)絡(luò)數(shù)據(jù)發(fā)送軟中斷?*/
?NET_RX_SOFTIRQ,?? /*?網(wǎng)絡(luò)數(shù)據(jù)接收軟中斷?*/
?BLOCK_SOFTIRQ,
?BLOCK_IOPOLL_SOFTIRQ,
?TASKLET_SOFTIRQ,?? /*?tasklet?軟中斷?*/
?SCHED_SOFTIRQ,?????/*?調(diào)度軟中斷?*/
?HRTIMER_SOFTIRQ,?? /*?高精度定時(shí)器軟中斷?*/
?RCU_SOFTIRQ,???????/*?RCU?軟中斷?*/
?NR_SOFTIRQS
};
軟中斷的使用方法如下示:
-
先使用open_softirq函數(shù)注冊(cè)對(duì)應(yīng)的軟中斷處理函數(shù)
-
注冊(cè)好軟中斷后,需要通過(guò)raise_softirq函數(shù)觸發(fā)
-
軟中斷一定要在編譯的時(shí)候靜態(tài)注冊(cè),使用softirq_init函數(shù)來(lái)靜態(tài)注冊(cè)
tasklet:是利用軟中斷來(lái)實(shí)現(xiàn)的另一種下半部機(jī)制,兩者之間,建議使用 tasklet,內(nèi)核使用tasklet_struct結(jié)構(gòu)體來(lái)表示tasklet
struct?tasklet_struct
{
?struct?tasklet_struct?*next;?? /*?下一個(gè)?tasklet?*/
?unsigned?long?state;???? ???? /*?tasklet?狀態(tài)?*/
?atomic_t?count;????????????? /*?計(jì)數(shù)器,記錄對(duì)?tasklet?的引用數(shù)?*/
?void?(*func)(unsigned?long);?? /*?tasklet?執(zhí)行的函數(shù)?*/
?unsigned?long?data;????????????/*?函數(shù)?func?的參數(shù)?*/
};
tasklet?的使用方法如下示:
-
先定義一個(gè)tasklet,然后使用tasklet_init函數(shù)初始化tasklet
-
也可用宏DECLARE_TASKLET()來(lái)一次性完成tasklet的定義和初始化?
-
在上半部(即中斷處理函數(shù))中調(diào)用tasklet_schedule函數(shù)使tasklet處理函數(shù)在合適的時(shí)間運(yùn)行
tasklet的參考使用示例如下所示:
/*?定義?taselet?*/
struct?tasklet_struct?testtasklet;
/*?tasklet?處理函數(shù)?*/
void?testtasklet_func(unsigned?long?data)
{
?/*?tasklet?具體處理內(nèi)容?*/
}
/*?中斷處理函數(shù)?*/
irqreturn_t?test_handler(int?irq,?void?*dev_id)
{
?......
?/*?調(diào)度?tasklet?*/
?tasklet_schedule(&testtasklet);
?......
}
/*?驅(qū)動(dòng)入口函數(shù)?*/
static?int?__init?xxxx_init(void)
{
?......
?/*?初始化?tasklet?*/
?tasklet_init(&testtasklet,?testtasklet_func,?data);
?/*?注冊(cè)中斷處理函數(shù)?*/
?request_irq(xxx_irq,?test_handler,?0,?"xxx",?&xxx_dev);
?......
}
?工作隊(duì)列:在進(jìn)程上下文執(zhí)行,工作隊(duì)列將要推后的工作交給一個(gè)內(nèi)核線程去執(zhí)行,工作隊(duì)列允許睡眠或重新調(diào)度。因此若你要推后的工作需要睡眠功能,就可選擇工作隊(duì)列,否則就只能選擇軟中斷或tasklet
//內(nèi)核使用?work_struct?結(jié)構(gòu)體表示一個(gè)工作
struct?work_struct?{
?atomic_long_t?data;
?struct?list_head?entry;
?work_func_t?func;?? /*?工作隊(duì)列處理函數(shù)?*/
};
tasklet?的使用方法如下示:-
先定義一個(gè)work,然后使用INIT_WORK()宏函數(shù)來(lái)初始化工作
-
也可以使用DECLARE_WORK()宏函數(shù)一次性完成work的創(chuàng)建和初始化
-
在上半部(即中斷處理函數(shù))中調(diào)用schedule_work函數(shù)使work處理函數(shù)在合適的時(shí)間運(yùn)行
工作隊(duì)列的參考使用示例如下所示:
/*?定義工作(work)?*/
struct?work_struct?testwork;
/*?work?處理函數(shù)?*/
void?testwork_func_t(struct?work_struct?*work);
{
?/*?work?具體處理內(nèi)容?*/
}
/*?中斷處理函數(shù)?*/
irqreturn_t?test_handler(int?irq,?void?*dev_id)
{
?......
?/*?調(diào)度?work?*/
?schedule_work(&testwork);
?......
}
/*?驅(qū)動(dòng)入口函數(shù)?*/
static?int?__init?xxxx_init(void)
{
?......
?/*?初始化?work?*/
?INIT_WORK(&testwork,?testwork_func_t);
?/*?注冊(cè)中斷處理函數(shù)?*/
?request_irq(xxx_irq,?test_handler,?0,?"xxx",?&xxx_dev);
?......
}
?
3. 中斷API函數(shù)
每個(gè)中斷都有一個(gè)中斷號(hào),通過(guò)中斷號(hào)可區(qū)分不同的中斷。在Linux內(nèi)核中使用一個(gè) int 變量表示中斷號(hào)request_irq函數(shù):申請(qǐng)中斷,會(huì)激活中斷,所以無(wú)需手動(dòng)使能中斷
int?request_irq(unsigned?int??irq,
????irq_handler_t?handler,
????unsigned?long?flags,
????const?char??*name,
????void???*dev)
//irq:要申請(qǐng)中斷的中斷號(hào)
//handler:中斷處理函數(shù),中斷發(fā)生后會(huì)執(zhí)行此函數(shù)
//flags:中斷標(biāo)志,在include/linux/interrupt.h中定義
//name:中斷名字,設(shè)置后可以在/proc/interrupts中看到對(duì)應(yīng)的中斷名字
//dev:若flags設(shè)置為IRQF_SHARED的話,dev用來(lái)區(qū)分不同的中斷
//返回值:0表示中斷申請(qǐng)成功,其他負(fù)值表示中斷申請(qǐng)失敗,-EBUSY表示中斷已被申請(qǐng)
free_irq函數(shù):釋放中斷,會(huì)刪除中斷處理函數(shù)并且禁止中斷
void?free_irq(unsigned?int?irq,
?????void?*dev)
//irq:要釋放的中斷
//dev:若flags設(shè)置為IRQF_SHARED的話,dev用來(lái)區(qū)分不同的中斷
//?????共享中斷只有在釋放最后中斷處理函數(shù)的時(shí)候才會(huì)被禁止掉
//返回值:無(wú)
中斷處理函數(shù):申請(qǐng)中斷時(shí)需要設(shè)置中斷處理函數(shù),格式如下
irqreturn_t?(*irq_handler_t)?(int,?void?*)
//返回值irqreturn_t是一個(gè)枚舉類型,共有三種返回值:
enum?irqreturn?{
?IRQ_NONE?=?(0?<0),
?IRQ_HANDLED?=?(1?<0),
?IRQ_WAKE_THREAD?=?(1?<1),
};
typedef?enum?irqreturn?irqreturn_t;
//一般中斷服務(wù)函數(shù)返回值使用形式為:
return?IRQ_RETVAL(IRQ_HANDLED)
中斷使能與禁止函數(shù)
void?enable_irq(unsigned?int?irq);?//使能指定的中斷
void?disable_irq(unsigned?int?irq);?//禁止中斷,會(huì)等當(dāng)前處理函數(shù)執(zhí)行完才返回
void?disable_irq_nosync(unsigned?int?irq);?//禁止中斷,不等執(zhí)行完,立即返回
local_irq_enable();??//使能當(dāng)前處理器中斷系統(tǒng)
local_irq_disable();?//禁止當(dāng)前處理器中斷系統(tǒng)
local_irq_save(flags);?//禁止中斷,并將中斷狀態(tài)保存在flags中
local_irq_restore(flags);??//恢復(fù)中斷,并將中斷狀態(tài)保存在flags中
?
4. 內(nèi)核中斷使用模板
以上半部中斷為例,介紹內(nèi)核中斷的使用流程:設(shè)備樹中與中斷有關(guān)的設(shè)備樹屬性信息有:
#interrupt-cells?//指定中斷源的信息?cells?個(gè)數(shù)
interrupt-controller?//表示當(dāng)前節(jié)點(diǎn)為中斷控制器
interrupts?//指定中斷號(hào),觸發(fā)方式等
interrupt-parent?//指定父中斷,也就是中斷控制器
獲取中斷號(hào)函數(shù)有兩個(gè),如下示:
unsigned?int?irq_of_parse_and_map(struct?device_node?*dev,?int?index)
//dev:設(shè)備節(jié)點(diǎn)
//index:索引號(hào),通過(guò)索引號(hào)來(lái)指定要獲取的信息
//返回值:中斷號(hào)
/*****若使用GPIO中斷,可以下函數(shù)來(lái)獲取對(duì)應(yīng)中斷號(hào)*****/
int?gpio_to_irq(unsigned?int?gpio)
//gpio:要獲取的GPIO編號(hào)
//返回值:GPIO對(duì)應(yīng)的中斷號(hào)
?
審核編輯:湯梓紅
評(píng)論
查看更多