一、Linux中断处理机制
中断会打破内核进程正常运行的程序的调度,因此对于系统来说中断中执行的程序需要尽量短小。在Linux系统中,为了在中断执行时间和中断处理程序量之间找到一个平衡,引入了Top Half和Bottom Half.即顶半部和底半部。
顶半部:主要完成尽量少的紧急功能,比如紧急处理寄存器的中断状态位
底半部:执行剩余中断中需要完成的事情。
二、Linux中断编程
Linux中的中断编程和MCU不一样,不需要你一个个去配置相关的寄存器,只需要使用Linux内核提供给你的API函数。
1、关于中断编程的API函数
① 申请中断irq
static inline int __must_check request_irq(unsigned int irq,
irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
irq : 要申请的硬件中断号
handler: 向内核注册的中断处理函数,相当于中断处理机制中的顶半部
flags: 中断的触发方式
name: 中断名字
dev: 传递给中断服务函数的私有数据,一般设置为设备结构体,如果flags设置的是共享中断,则用来区分不同的中断
返回值:返回0表示申请成功,-EINVAL表示中断号无效或中断处理函数为NULL,-EBUSY表示中断已经被占用
static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
devname, dev_id);
}
利用此函数申请中断,不需要在出错处理和驱动出口函数中显式的释放中断。
②释放irq
void free_irq(unsigned int irq, void *dev_id)
此函数和request_irq相对应。
irq: 要释放的硬件中断号
dev_id: 如果释放的中断时共享中断的话,则dev_id用来区分具体的中断
③ 使能和屏蔽中断
void disable_irq_nosync(unsigned int irq)
void disable_irq(unsigned int irq)
void enable_irq(unsigned int irq)
从名字就能看出函数的意思,disable_irq_nosyn 屏蔽的中断会立刻返回,而disable_irq 屏蔽的中断会等待中断执行完成再返回。比如在3号中断中执行disable_irq(3)则会造成死锁。
三、 底半部机制
Linux中实现底半部机制的主要由tasklet、workqueue、softirq和线程化irq.
① tasklet
tasklet使用方法简单主要分为3个步骤:
定义一个tasklet ----> 将定义的tasklet和底半部需要执行的函数关联---->在需要调度的地方进行调度。
使用模板:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/*将tasklet和函数关联*/
DECLARE_TASKLET(testtasklet, testtasklet_func, 0;
/* 中断处理函数 */
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);
......
}
②workqueue
workque使用的方法也和tasklet很相似,区别在于workqueue执行的上下文是内核线程,可以调度和睡眠。
定义一个workqueue---->将定义的worqueue和底半部执行函数绑定---->在需要调度的地方进行调度。
使用模板:
/* 定义工作(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);
......
}
③ softirq
软中断softirq和tasklet运行于软中断上下文,是一个原子上下文,因此不允许引起睡眠和调度。
④ 线程化irq
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id)
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id)
通过这两个API函数向内核申请的中断,内核会为相应的中断号分配一个内核线程,handler参数对应的函数会执行在中断上下文,thread_fn参数对应的函数会执行于内核线程