當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > linux中斷編程、中斷編程詳解
Linux中斷處理驅(qū)動程序編寫
中斷處理是操作系統(tǒng)必須具備的上要功能之一,下面我們一起來探討一下Linux中的中斷處理。
1. 什么是中斷
中斷就是CPU正常運(yùn)行期間,由于內(nèi)、外部事件引起的CPU暫時(shí)停止正在運(yùn)行的程序,去執(zhí)行該內(nèi)部事件或外部事件的引起的服務(wù)中去,服務(wù)執(zhí)行完畢后再返回?cái)帱c(diǎn)處繼續(xù)執(zhí)行的情形。這樣的中斷機(jī)制極大的提高了CPU運(yùn)行效率。
1.1. 中斷的分類:
1) 根據(jù)中斷的來源可分為內(nèi)部中斷和外部中斷,內(nèi)部中斷的中斷源來自于CPU內(nèi)部(軟件中斷指令、溢出、除法錯誤等),例如操作系統(tǒng)從用戶態(tài)切換到內(nèi)核態(tài)需要借助CPU內(nèi)部的軟件中斷,外部中斷的中斷源來自于CPU外部,由外設(shè)觸發(fā)。
2) 根據(jù)中斷是否可以被屏蔽,中斷可分為可屏蔽中斷和不可屏蔽中斷,可屏蔽中斷可以通過設(shè)置中斷控制器寄存器等方法被屏蔽,屏蔽后,該中斷不再得到響應(yīng),而不可屏蔽中斷不能被屏蔽。
3) 根據(jù)中斷入口跳轉(zhuǎn)方式的不同,中斷可分為向量中斷和非向量中斷。采用向量中斷的CPU通常為不同的中斷分配不同的中斷號,當(dāng)檢測到中斷的中斷號到來時(shí),就自動跳轉(zhuǎn)到該中斷對應(yīng)的地址處去執(zhí)行程序。不同的中斷號對應(yīng)不同的中斷入口地址。非向量中斷的多個(gè)中斷共享一個(gè)入口程序處理入口地址,中斷程序跳轉(zhuǎn)到該入口地址執(zhí)行時(shí),再通過中斷程序來判斷中斷標(biāo)志來識別具體是哪一個(gè)中斷,也就是說向量中斷由硬件提供中斷服務(wù)程序入口地址,非向量中斷由軟件提供中斷服務(wù)程序入口地址。
4) 非向量中斷處理流程:
/*典型的非向量中斷首先會判斷中斷源,然后調(diào)用不同中斷源的中斷處理程序*/
irq_handler()
{
...
int int_src = read_int_status();/*讀硬件的中斷相關(guān)寄存器*/
switch(int_src)
{
//判斷中斷標(biāo)志
case DEV_A:
dev_a_handler();
break;
case DEV_B:
dev_b_handler();
break;
...
default:
break;
}
...
}
2. linux中斷頂部、底部概念
為保證系統(tǒng)實(shí)時(shí)性,中斷服務(wù)程序必須足夠簡短,但實(shí)際應(yīng)用中某些時(shí)候發(fā)生中斷時(shí)必須處理大量的工作,這時(shí)候如果都在中斷服務(wù)程序中完成,則會嚴(yán)重降低中斷的實(shí)時(shí)性,基于這個(gè)原因,linux系統(tǒng)提出了一個(gè)概念:把中斷服務(wù)程序分為兩部分:頂半部、底半部。
2.1. 頂半部
完成盡可能少的比較急的功能,它往往只是簡單的讀取寄存器的中斷狀態(tài),并清除中斷標(biāo)志后就進(jìn)行“中斷標(biāo)記”(也就是把底半部處理程序掛到設(shè)備的底半部執(zhí)行隊(duì)列中)的工作。特點(diǎn)是響應(yīng)速度快。
2.2. 底半部
中斷處理的大部分工作都在底半部,它幾乎做了中斷處理程序的所有事情。 特點(diǎn):處理相對來說不是非常緊急的事件 ,底半部機(jī)制主要有:tasklet、工作隊(duì)列和軟中斷。
Linux中查看/proc/interrupts文件可以獲得系統(tǒng)中斷的統(tǒng)計(jì)信息:
3. Linux中斷編程
3.1. 申請和釋放中斷
3.1.1. 申請中斷:
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags,const char *devname,void *dev_id);
參數(shù)介紹:irq是要申請的硬件中斷號。
Handler:是向系統(tǒng)登記的中斷處理程序(頂半部),是一個(gè)回調(diào)函數(shù),中斷發(fā)生時(shí),系統(tǒng)調(diào)用它,將dev_id參數(shù)傳遞給它。
irqflags:是中斷處理的屬性,可以指定中斷的觸發(fā)方式和處理方式:
觸發(fā)方式:IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW,處理方式:IRQF_DISABLE表明中斷處理程序是快速處理程序,快速處理程序被調(diào)用時(shí)屏蔽所有中斷,IRQF_SHARED表示多個(gè)設(shè)備共享中斷,dev_id在中斷共享時(shí)會用到,一般設(shè)置為NULL。
返回值:為0表示成功,返回-EINVAL表示中斷號無效,返回-EBUSY表示中斷已經(jīng)被占用,且不能共享。
頂半部的handler的類型irq_handler_t定義為:
typedef irqreturn_t (*irq_handler_t)(int,void*);
typedef int irqreturn_t;
3.1.2. 釋放IRQ
有請求當(dāng)然就有釋放。中斷的釋放函數(shù)為:
void free_irq(unsigned int irq,void *dev_id);
參數(shù)定義與request_irq類似。
3.1.3. 中斷的使能和屏蔽
void disable_irq(int irq);//等待目前中斷處理完成(最好別在頂板部使用,你懂得)
void disable_irq_nosync(int irq);//立即返回
void enable_irq(int irq);//
3.1.4. 屏蔽本CPU內(nèi)所有中斷:
#define local_irq_save(flags)...//禁止中斷并保存狀態(tài)。
void local_irq_disable(void); //禁止中斷,不保存狀態(tài)。
下面來分別介紹一下頂半部和底半部的實(shí)現(xiàn)機(jī)制
3.1.5. 底半部機(jī)制:
簡介:底半部機(jī)制主要有tasklet、工作隊(duì)列和軟中斷
3.1.5.1. 底半部實(shí)現(xiàn)方法之一tasklet
(1) 我們需要定義tasklet機(jī)器處理器并將兩者關(guān)聯(lián),例如:
void my_tasklet_func(unsigned long);/*定義一個(gè)處理函數(shù)*/
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);
/*上述代碼定義了名為my_tasklet的tasklet并將其與my_tasklet_func()函數(shù)綁定,傳入的參數(shù)為data*/
(2)調(diào)度
tasklet_schedule(&my_tasklet);
//使用此函數(shù)就能在是當(dāng)?shù)臅r(shí)候進(jìn)行調(diào)度運(yùn)行
(3)tasklet使用模板:
/*定義tasklet和底半部函數(shù)并關(guān)聯(lián)*/
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet,0);
/*中斷處理底半部*/
void xxx_do_tasklet(unsigned long)
{
...
}
/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
tasklet_schedule(&xxx_tasklet);//調(diào)度地半部
...
}
/*設(shè)備驅(qū)動模塊加載函數(shù)*/
int __init xxx_init(void)
{
...
/*申請中斷*/
result = request_irq(xxx_irq,xxx_interrupt, IRQF_DISABLED,"xxx",NULL);
...
return IRQ_HANDLED;
}
/*設(shè)備驅(qū)動模塊卸載函數(shù)*/
void __exit xxx_exit(void)
{
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
...
}
3.1.5.2. 底半部實(shí)現(xiàn)方法之二---工作隊(duì)列
使用方法和tasklet類似,相關(guān)操作:
struct work_struct my_wq;/*定義一個(gè)工作隊(duì)列*/
void my_wq_func(unsigned long);/*定義一個(gè)處理函數(shù)*/
通過INIT_WORK()可以初始化這個(gè)工作隊(duì)列并將工作隊(duì)列與處理函數(shù)綁定INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作隊(duì)列并將其與處理函數(shù)綁定*/
schedule_work(&my_wq);/*調(diào)度工作隊(duì)列執(zhí)行*/
/*工作隊(duì)列使用模板*/
/*定義工作隊(duì)列和關(guān)聯(lián)函數(shù)*/
struct work_struct(unsigned long);
void xxx_do_work(unsigned long);
/*中斷處理底半部*/
void xxx_do_work(unsigned long)
{
...
}
/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
schedule_work(&my_wq);//調(diào)度底半部
...
return IRQ_HANDLED;
}
/*設(shè)備驅(qū)動模塊加載函數(shù)*/
int xxx_init(void)
{
...
/*申請中斷*/
result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);
...
/*初始化工作隊(duì)列*/
INIT_WORK(&my_wq,(void (*)(void *))xxx_do_work,NULL);
}
/*設(shè)備驅(qū)動模塊卸載函數(shù)*/
void xxx_exit(void)
{
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
...
}
4. 中斷共享
中斷共享是指多個(gè)設(shè)備共享一根中斷線的情況,中斷共享的使用方法:
(1).在申請中斷時(shí),使用IRQF_SHARED標(biāo)識;
(2).在中斷到來時(shí),會遍歷共享此中斷的所有中斷處理程序,直到某一個(gè)函數(shù)返回IRQ_HANDLED,在中斷處理程序頂半部中,應(yīng)迅速根據(jù)硬件寄存器中的信息參照dev_id參數(shù)判斷是否為本設(shè)備的中斷,若不是立即返回IR1_NONE
/*共享中斷編程模板*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
int status = read_int_status();/*獲知中斷源*/
if(!is_myint(dev_id,status))/*判斷是否為本設(shè)備中斷*/
return IRQ_NONE;/*不是本設(shè)備中斷,立即返回*/
/*是本設(shè)備中斷,進(jìn)行處理*/
...
return IRQ_HANDLED;/*返回IRQ_HANDLER表明中斷已經(jīng)被處理*/
}
/*設(shè)備模塊加載函數(shù)*/
int xxx_init(void)
{
...
/*申請共享中斷*/
result = request_irq(sh_irq,xxx_interrupt,IRQF_SHARE,"xxx",xxx_dev);
...
}
/*設(shè)備驅(qū)動模塊卸載函數(shù)*/
void xxx_exit()
{
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
...
}
5. 內(nèi)核定時(shí)器
內(nèi)核定時(shí)器編程:
簡介:軟件意義上的定時(shí)器最終是依賴于硬件定時(shí)器實(shí)現(xiàn)的,內(nèi)核在時(shí)鐘中斷發(fā)生后檢測各定時(shí)器是否到期,到期后定時(shí)器處理函數(shù)作為軟中斷在底半部執(zhí)行。
Linux內(nèi)核定時(shí)器操作:
5.1. timer_list結(jié)構(gòu)體
每一個(gè)timer_list對應(yīng)一個(gè)定時(shí)器
struct timer_list{
struct list_head entry;/*定時(shí)器列表*/
unsigned long expires;/*定時(shí)器到期時(shí)間*/
void (*function)(unsigned long);/*定時(shí)器處理函數(shù)*/
unsigned long data;/*作為參數(shù)被傳遞給定時(shí)器處理函數(shù)*/
struct timer_base_s *base;
...
};
當(dāng)定時(shí)器滿的時(shí)候,定時(shí)器處理函數(shù)將被執(zhí)行
5.2. 初始化定時(shí)器
void init_timer(struct timer_list * timer);
//初始化timer_list的entry的next為NULL,并給base指針賦值。
TIMER_INITIALIZER(_function,_expires,_data);//此宏用來
//賦值定時(shí)器結(jié)構(gòu)體的function、expires、data和base成員
#define TIMER_INITIALIZER(function,_expires,_data)
{
.entry = {.prev = TIMER_ENTRY_STATIC},\
.function= (_function), \
.expires = (_expire), \
.data = (_data), \
.base = &boot_tvec_bases,\
}
DEFINE_TIMER(_name,_function,_expires,_data)//定義一個(gè)定時(shí)器結(jié)構(gòu)體變量//并為此變量取名_name
//還有一個(gè)setup_timer()函數(shù)也可以用于定時(shí)器結(jié)構(gòu)體的初始化。
5.3. 增加定時(shí)器
void add_timer(struct timer_list * timer);//注冊內(nèi)核定時(shí)器,也就是將定時(shí)器加入到內(nèi)核動態(tài)定時(shí)器鏈表當(dāng)中。
5.4. 刪除定時(shí)器
del_timer(struct timer_list *timer);
del_timer_sync()//在刪除一個(gè)定時(shí)器時(shí)等待刪除操作被處理完(不能用于中斷上下文中)
5.5. 修改定時(shí)器expires
int mod_timer(struct timer_list * timer,unsigned long expires);//修改定時(shí)器的到期時(shí)間
/*內(nèi)核定時(shí)器使用模板*/
/*xxx設(shè)備結(jié)構(gòu)體*/
struct xxx_dev
{
struct cdev cdev;
...
timer_list xxx_timer;/*設(shè)備要使用的定時(shí)器*/
};
/*xxx驅(qū)動中的某函數(shù)*/
xxx_funcl(...)
{
struct xxx_dev *dev = filp->private_data;
...
/*初始化定時(shí)器*/
init_timer(&dev->xxx_timer);
dev->xxx_timer.function = &xxx_do_timer;
dev->xxx_timer.data = (unsigned long)dev;
/*設(shè)備結(jié)構(gòu)體指針作為定時(shí)器處理函數(shù)參數(shù)*/
dev->xxx_timer.expires = jiffes + delays;
/*添加(注冊)定時(shí)器*/
add_timer(&dev->xxx_timer);
...
}
/*xxx驅(qū)動中的某函數(shù)*/
xxx_func2(...)
{
...
/*刪除定時(shí)器*/
del_timer(&dev->xxx_timer);
...
}
/*定時(shí)器處理函數(shù)*/
static void xxx_do_timer(unsigned long arg)
{
struct xxx_device *dev = (struct xxx_device *)(arg);
...
/*調(diào)度定時(shí)器再執(zhí)行*/
dev->xxx_timer.expires = jiffes + delay;
add_timer(&dev -> xxx_timer);
...
}
//定時(shí)器到期時(shí)間往往是在jiffies的基礎(chǔ)上添加一個(gè)時(shí)延,若為HZ則表示延遲一秒
5.6. 內(nèi)核中的延遲工作:
簡介:對于這種周期性的工作,Linux提供了一套封裝好的快捷機(jī)制,本質(zhì)上利用工作隊(duì)列和定時(shí)器實(shí)現(xiàn),這其中用到兩個(gè)結(jié)構(gòu)體:
(1)struct delayed_work
{
struct work_struct work;
struct timer_list timer;
};
(2) struct work_struct
{
atomic_long_t data;
...
}
相關(guān)操作:
int schedule_delay_work(struct delayed_work *work,unsigned long delay);//當(dāng)指定的delay到來時(shí)delay_work中的work成員的work_func_t類型成員func()會被執(zhí)行work_func_t類型定義如下:
typedef void (*work_func_t)(struct work_struct *work);//delay參數(shù)的單位是jiffes
mescs_to_jiffies(unsigned long mesc);//將毫秒轉(zhuǎn)化成jiffes單位
int cancel_delayed_work(struct delayed_work *work);
int cancel_delayed_work_sync(struct delayed_work *work);//等待直到刪除(不能用于中斷上下文)
內(nèi)核延遲的相關(guān)函數(shù):
短延遲:
Linux內(nèi)核提供了如下三個(gè)函數(shù)分別進(jìn)行納秒、微妙和毫秒延遲:
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
機(jī)制:根據(jù)CPU頻率進(jìn)行一定次數(shù)的循環(huán)(忙等待)
注意:在Linux內(nèi)核中最好不要使用毫秒級的延時(shí),因?yàn)檫@樣會無謂消耗CPU的資源。
對于毫秒以上的延時(shí),Linux提供如下函數(shù):
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);//可以被打斷
void ssleep(unsigned int seconds);
//上述函數(shù)使得調(diào)用它的進(jìn)程睡眠指定的時(shí)間
長延遲:
機(jī)制:設(shè)置當(dāng)前jiffies加上時(shí)間間隔的jiffies,直到未來的jiffies達(dá)到目標(biāo)jiffires
/*實(shí)例:先延遲100個(gè)jiffies再延遲2s*/
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay));
/*再延遲2s*/
unsigned long delay = jiffies + 2*Hz;
while(time_before(jiffies,delay));//循環(huán)直到到達(dá)指定的時(shí)間與timer_before()相對應(yīng)的還有一個(gè)time_after
睡著延遲:
睡著延遲是比忙等待更好的一種方法
機(jī)制:在等待的時(shí)間到來之前進(jìn)程處于睡眠狀態(tài),CPU資源被其他進(jìn)程使用,實(shí)現(xiàn)函數(shù)有:
schedule_timeout()
schedule_timeout_uninterruptible()
其實(shí)在短延遲中的msleep() msleep_interruptible()
本質(zhì)上都是依賴于此函數(shù)實(shí)現(xiàn)的,下面兩個(gè)函數(shù)可以讓當(dāng)前進(jìn)程加入到等待隊(duì)列中,從而在等待隊(duì)列上睡眠,當(dāng)超時(shí)發(fā)生時(shí),進(jìn)程被喚醒
sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);
interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout);