裸机编程中使用中断比较麻烦,需要配置寄存器、使能IRQ等等。而在Linux驱动编程中,内核提供了完善的终端框架,只需要申请中断,然后注册中断处理函数即可,使用非常方便
1. 内核中断
Linux内核中断分为上半部和下半部,其主要目的就是实现中断处理函数的快进快出,两者的区别如下:
-
上半部:就是中断处理函数,处理过程比较快,不会占用很长时间
-
下半部:若中断处理过程很耗时,就将这些代码提出来,交给下半部去执行,这样中断处理函数就会快进快出
一般情况下,若要处理的内容不希望被其他中断打断、要处理的任务对时间敏感或者要处理的任务与硬件有关,通常放到上半部;其他情况优先考虑放到下半部
2. 中断下半部实现方式
上半部直接编写中断处理函数处理即可,下半部的实现方式主要有如下三种:软中断:内核使用结构体sofTIrq_acTIon表示软中断,定义在文件include/linux/interrupt.h中
struct sofTIrq_acTIon{
void (*action)(struct softirq_action *);
}
//文件 kernel/softirq.c 中共定义了10个软中断
static struct softirq_action softirq_vec[NR_SOFTIRQS];
//NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h中
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU 软中断 */
NR_SOFTIRQS
};
软中断的使用方法如下示:
-
先使用open_softirq函数注册对应的软中断处理函数
-
注册好软中断后,需要通过raise_softirq函数触发
-
软中断一定要在编译的时候静态注册,使用softirq_init函数来静态注册
tasklet:是利用软中断来实现的另一种下半部机制,两者之间,建议使用 tasklet,内核使用tasklet_struct结构体来表示tasklet
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
tasklet 的使用方法如下示:
-
先定义一个tasklet,然后使用tasklet_init函数初始化tasklet
-
也可用宏DECLARE_TASKLET()来一次性完成tasklet的定义和初始化
-
在上半部(即中断处理函数)中调用tasklet_schedule函数使tasklet处理函数在合适的时间运行
tasklet的参考使用示例如下所示:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
工作队列:在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,工作队列允许睡眠或重新调度。因此若你要推后的工作需要睡眠功能,就可选择工作队列,否则就只能选择软中断或tasklet
//内核使用 work_struct 结构体表示一个工作
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
tasklet 的使用方法如下示:-
先定义一个work,然后使用INIT_WORK()宏函数来初始化工作
-
也可以使用DECLARE_WORK()宏函数一次性完成work的创建和初始化
-
在上半部(即中断处理函数)中调用schedule_work函数使work处理函数在合适的时间运行
工作队列的参考使用示例如下所示:
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
3. 中断API函数
每个中断都有一个中断号,通过中断号可区分不同的中断。在Linux内核中使用一个 int 变量表示中断号request_irq函数:申请中断,会激活中断,所以无需手动使能中断
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
//irq:要申请中断的中断号
//handler:中断处理函数,中断发生后会执行此函数
//flags:中断标志,在include/linux/interrupt.h中定义
//name:中断名字,设置后可以在/proc/interrupts中看到对应的中断名字
//dev:若flags设置为IRQF_SHARED的话,dev用来区分不同的中断
//返回值:0表示中断申请成功,其他负值表示中断申请失败,-EBUSY表示中断已被申请
free_irq函数:释放中断,会删除中断处理函数并且禁止中断
void free_irq(unsigned int irq,
void *dev)
//irq:要释放的中断
//dev:若flags设置为IRQF_SHARED的话,dev用来区分不同的中断
// 共享中断只有在释放最后中断处理函数的时候才会被禁止掉
//返回值:无
中断处理函数:申请中断时需要设置中断处理函数,格式如下
irqreturn_t (*irq_handler_t) (int, void *)
//返回值irqreturn_t是一个枚举类型,共有三种返回值:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
//一般中断服务函数返回值使用形式为:
return IRQ_RETVAL(IRQ_HANDLED)
中断使能与禁止函数
void enable_irq(unsigned int irq); //使能指定的中断
void disable_irq(unsigned int irq); //禁止中断,会等当前处理函数执行完才返回
void disable_irq_nosync(unsigned int irq); //禁止中断,不等执行完,立即返回
local_irq_enable(); //使能当前处理器中断系统
local_irq_disable(); //禁止当前处理器中断系统
local_irq_save(flags); //禁止中断,并将中断状态保存在flags中
local_irq_restore(flags); //恢复中断,并将中断状态保存在flags中
4. 内核中断使用模板
以上半部中断为例,介绍内核中断的使用流程:设备树中与中断有关的设备树属性信息有:
#interrupt-cells //指定中断源的信息 cells 个数
interrupt-controller //表示当前节点为中断控制器
interrupts //指定中断号,触发方式等
interrupt-parent //指定父中断,也就是中断控制器
获取中断号函数有两个,如下示:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
//dev:设备节点
//index:索引号,通过索引号来指定要获取的信息
//返回值:中断号
/*****若使用GPIO中断,可以下函数来获取对应中断号*****/
int gpio_to_irq(unsigned int gpio)
//gpio:要获取的GPIO编号
//返回值:GPIO对应的中断号
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)