姓名:李萌怡? 學(xué)號:19020100103? 學(xué)院:電子工程學(xué)院
轉(zhuǎn)自:https://blog.csdn.net/light_in_dark/article/details/73372520
【嵌牛導(dǎo)讀】:學(xué)習(xí)了嵌入式linux設(shè)備系統(tǒng)開發(fā)的內(nèi)核部分中關(guān)于時(shí)間和定時(shí)器的設(shè)置后,需要了解中斷源和中斷號的定義宅楞,并學(xué)習(xí)對此進(jìn)行設(shè)置的方法歼狼。
【嵌牛鼻子】:中斷源? 中斷號
【嵌牛提問】:中斷源和中斷號是什么蚪缀?應(yīng)該怎樣處理敛滋?
【嵌牛正文】
一、中斷源和中斷號
中斷是硬件通過硬件電路產(chǎn)生的年扩,因此慌申,如果一個(gè)外設(shè)沒有獨(dú)立的中斷線,也就不能產(chǎn)生中斷睡汹。
中斷控制器的工作是收集硬件產(chǎn)生的中斷肴甸,然后根據(jù)預(yù)先設(shè)定好的優(yōu)先級依次提交給cpu。
對arm處理器來說囚巴,中斷控制器的發(fā)展經(jīng)歷了3個(gè)階段:
PIC (Programmable Interrupt Controller)
VIC (Vector Interrupt Controller)
GIC (General Interrupt Controller)
中斷源: 是硬件概念原在;中斷號: 是軟件概念,從0開始依次向后排彤叉。在linux內(nèi)核中中斷號和中斷源有一定的對應(yīng)關(guān)系庶柿。每種不同處理器的中斷源都不一樣,從中斷源到中斷號的轉(zhuǎn)換方式也不一樣秽浇。中斷號都定義在“mach/irqs.h”中浮庐,可以用兩種不同的方法來查找中斷號:
(1)芯片內(nèi)部的外設(shè)
首先明確設(shè)備的名字,然后利用名字匹配柬焕,自行在irqs.h中找到對應(yīng)的中斷號审残;
比如看門狗設(shè)備對應(yīng)的中斷號為IRQ_WDT, rtc硬件對應(yīng)的為IRQ_RTC_ALARAM/IRQ_RTC_TIC。
(2)芯片外部連接的設(shè)備
由于設(shè)備的中斷引腳通常都連接到GPIO斑举,因此可以利用GPIO號來找到中斷號:中斷號 = gpio_to_irq(GPIO號)
在注冊中斷處理函數(shù)時(shí)搅轿,可以用如下方式包含該頭文件,并引用其中的宏:
#include <mach/irqs.h>
二懂昂、Linux的中斷處理
(1)核心結(jié)構(gòu)體
A介时、irq_desc
定義在“l(fā)inux/irqdesc.h”
對應(yīng)一個(gè)中斷號。linux內(nèi)核在啟動時(shí)分配了一個(gè)irq_desc的數(shù)組凌彬,數(shù)組中共有NR_IRQS個(gè)成員沸柔。每個(gè)irq_desc中記錄對應(yīng)中斷的各類信息,比如中斷的處理函數(shù)铲敛,中斷的發(fā)生次數(shù)等褐澎。
irq_desc由內(nèi)核負(fù)責(zé)準(zhǔn)備。
B伐蒋、irqaction
定義在“l(fā)inux/interrupt.h”
每個(gè)irqaction用于封裝一個(gè)中斷處理函數(shù)工三。結(jié)構(gòu)體由驅(qū)動人員負(fù)責(zé)分配。
irqaction中包含中斷號先鱼;中斷處理函數(shù)指針俭正;中斷的執(zhí)行標(biāo)志;中斷名等
C焙畔、irq_handler_t
定義在“l(fā)inux/interrupt.h”掸读,如下:
typedef irqreturn_t (*irq_handler_t)(int, void *);
中斷處理函數(shù)類型,中斷處理函數(shù)由驅(qū)動負(fù)責(zé)實(shí)現(xiàn)儿惫,記錄在irqaction中澡罚。
irqreturn_t只有兩個(gè)值,IRQ_NONE/IRQ_HANDLED肾请。如果中斷不是由本設(shè)備引起的留搔,則返回IRQ_NONE,否則返回IRQ_HANDLED铛铁。
函數(shù)參數(shù)irq為中斷號隔显,void *為傳遞給中斷處理函數(shù)的參數(shù),對應(yīng)irqaction->dev_id避归。
(2)中斷處理函數(shù)
驅(qū)動人員在設(shè)計(jì)中斷處理函數(shù)時(shí)荣月,要遵循的要求是:
A、可嵌套不可重入
B梳毙、不能睡眠
C哺窄、如果硬件有中斷的狀態(tài)寄存器,軟件要負(fù)責(zé)清除中斷的標(biāo)志位账锹。一般來說萌业,如果不清除標(biāo)志位,設(shè)備無法再次產(chǎn)生中斷奸柬。
kzalloc(size, GFP_KERNEL); //可能睡眠
kzalloc(size, GFP_ATOMIC); //不會睡眠
D生年、中斷處理函數(shù)的注冊和注銷
#include <linux/interrupt.h>
#include <mach/irqs.h> //片內(nèi)外設(shè)
#include <linux/gpio.h> //片外外設(shè)
#include <mach/gpio.h>
//確定中斷號
#define KEY_IRQ? ? gpio_to_irq(gpio號);
//中斷處理函數(shù)
static irqreturn_t key_service(int irq, void *dev_id)
{
? ? //根據(jù)硬件及軟件的相關(guān)要求完成工作
? ? ...
? ? return IRQ_HANDLED 或 IRQ_NONE;
}
//注冊中斷處理函數(shù),必須檢查返回值
//request_irq內(nèi)部會分配并初始化irqaction
//如果是內(nèi)部設(shè)備產(chǎn)生的中斷廓奕,一般不需要共享抱婉,也不用配置IO,則flags可以為0
u32 flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
int ret;
ret = request_irq(KEY_IRQ, /* 中斷號 */
? ? ? ? ? ? ? ? ? key_service, /* 中斷處理函數(shù) */
? ? ? ? ? ? ? ? ? flags, /* 中斷的標(biāo)志 */
? ? ? ? ? ? ? ? ? "xxx", /* 中斷處理函數(shù)的名字 */
? ? ? ? ? ? ? ? ? dev_id);
//最后的參數(shù)dev_id為傳給中斷處理函數(shù)的參數(shù)桌粉,一般會設(shè)置為私有結(jié)構(gòu)體的指針蒸绩,不能為NULL。實(shí)際上铃肯,如果是非共享的中斷患亿,dev_id可以為NULL。
if (ret) {
? ? printk("Cannot register interrupt handler\n");
? ? return -1;
}
//注銷中斷處理函數(shù)
free_irq(irq, dev_id);
//參數(shù)為中斷號和dev_id押逼。dev_id一定要和request_irq中的最后一個(gè)參數(shù)一致步藕。
//可以人為關(guān)閉(mask)/打開某個(gè)中斷:
disable_irq(int irq);
enable_irq(int irq);
//上面的兩個(gè)函數(shù)支持嵌套,也就是說挑格,如果調(diào)用了3次disable_irq咙冗,需要enable_irq3次,才能真正使能中斷漂彤,要確保先調(diào)用disable_irq雾消,再調(diào)用enable_irq;
//如果要屏蔽整個(gè)cpu的中斷瞬逊,可以用:
local_irq_disable();
local_irq_enable();
(3)中斷下半部
A、下半部的含義
在進(jìn)入中斷處理函數(shù)前仪或,會默認(rèn)關(guān)閉本中斷。對于某些要求迅速響應(yīng)或數(shù)據(jù)吞吐量很大的中斷士骤,要考慮將中斷處理函數(shù)的工作分為兩個(gè)部分范删,分別稱為中斷的上半部和下半部。
下半部的實(shí)現(xiàn)有多種方法拷肌,包括softirq到旦,tasklet和工作隊(duì)列(work queue)。
對于驅(qū)動來說巨缘,只會使用tasklet和工作隊(duì)列(work queue)添忘。
可以打開或關(guān)閉本cpu的下半部:
local_bh_enable();
local_bh_disable();
B、tasklet
tasklet特點(diǎn)
a若锁、在上半部執(zhí)行完后馬上執(zhí)行搁骑,但此時(shí)中斷是全部打開的;
b、執(zhí)行tasklet時(shí)內(nèi)核仍處于中斷上下文又固,因此不能睡眠;
c仲器、tasklet的執(zhí)行函數(shù)不會重入;
d、如果在tasklet的執(zhí)行期間再次發(fā)生調(diào)度仰冠,第二次調(diào)度無效;
#include <linux/interrupt.h>
//聲明tasklet結(jié)構(gòu)體
struct tasklet_struct mytask;
//tasklet的執(zhí)行函數(shù)
void bo_service(unsigned long data)
{
? ? ...
}?
//上半部的執(zhí)行函數(shù)
irqreturn_t up_service(int irq, void *dev_id)
{
? ? //首先完成和硬件交互之類的重要工作
? ? //觸發(fā)tasklet下半部
? ? tasklet_schedule(&mytask);
? ? 或tasklet_hi_schedule(&mytask);
? ? ...
? ? return IRQ_HANDLED 或 IRQ_NONE;
}
//初始化tasklet
tasklet_init(&mytask, bo_service, (unsigned long)dev);
C乏冀、工作隊(duì)列(work queue)
工作隊(duì)列特點(diǎn)
a、推后到進(jìn)程上下文執(zhí)行洋只,此時(shí)中斷是全部打開的;
b辆沦、執(zhí)行work時(shí)處于進(jìn)程上下文,因此可以睡眠;
c识虚、work的執(zhí)行函數(shù)不會重入;
d肢扯、如果在work的執(zhí)行期間再次發(fā)生調(diào)度,第二次調(diào)度無效;
如果是redhat舷礼,則內(nèi)核創(chuàng)建一組內(nèi)核線程鹃彻,名字為events/x,由該內(nèi)核線程負(fù)責(zé)執(zhí)行下半部函數(shù)妻献;
x為cpu的編號蛛株,可以為0,1等育拨;
如果是Android谨履,則內(nèi)核創(chuàng)建的內(nèi)核線程名字為kworker/x:y
x是cpu編號,y應(yīng)該是該cpu上的第幾個(gè)內(nèi)核線程熬丧;
#include <linux/interrupt.h>
#include <linux/workqueue.h>
//聲明tasklet結(jié)構(gòu)體
struct work_struct mywork;
//工作隊(duì)列的執(zhí)行函數(shù)
void bo_service(struct work_struct *data)
{
? ? ...
}?
//上半部的執(zhí)行函數(shù)
irqreturn_t up_service(int irq, void *dev_id)
{
? ? //首先完成和硬件交互之類的重要工作
? ? //默認(rèn)情況下笋粟,上下半部在同一cpu上執(zhí)行
? ? //可以喚醒給定cpu上的線程執(zhí)行下半部
? ? //1代表cpu1,0代表cpu0
? ? schedule_work(&mywork);
? ? 或schedule_work_on(1, &mywork);
? ? ...
? ? return IRQ_HANDLED 或 IRQ_NONE;
}
//初始化工作隊(duì)列
INIT_WORK(&mywork, bo_service);
//注冊上半部
ret = request_irq(KEY_IRQ, up_service, flags, "wq_test", &mywork);
if (ret) {
? ? printk("cannot request irq %d\n", KEY_IRQ);
? ? return ret;
}
工作隊(duì)列的實(shí)現(xiàn)在“l(fā)inux/workqueue.h”。
————————————————
版權(quán)聲明:本文為CSDN博主「light_in_dark」的原創(chuàng)文章害捕,遵循CC 4.0 BY-SA版權(quán)協(xié)議绿淋,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/light_in_dark/article/details/73372520