Linux的中断使用
一、响应API函数
1.中断号
每个中断都有一个中断号,通过中断号区分不同的中断,有的资料也把中断号叫做中断线。在Linux内核中使用一个int变量表示中断号。
2.request_irq函数
在 Linux内核中要想使用某个中断是需要申请的, request_irq函数用于申请中断, request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq函数。 request_irq函数会激活 (使能 )中断,所以不需要我们手动去使能中断,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用来区分不同的中断,一般情况下将dev设置为设备结构体, dev会传递给中断处理函数 irq_handler_t的第二个参数。
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回 -EBUSY的话表示中断已经被申请了。
3.free_irq函数
使用中断的时候需要通过 request_irq函数申请,使用完成以后就要通过 free_irq函数释放掉相应的中断。如果中断不是共享的,那么 free_irq会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:
void free_irq(unsigned int irq, void *dev)
函数参数和返回值含义如下:
irq 要释放的中断。
dev:如果中断设置为共享 (IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无。
4.中断处理函数
使用 request_irq函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void的指针,也就是个通用指针,需要与 request_irq函数的 dev参数保持一致。用于区分共享中断的不同设备,dev也可以指向设备数据结构。
是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)
5.中断使能与禁止函数
常用的中断使用和禁止函数如下所示:
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
enable_irq和 disable_irq用于使能和禁止指定的中断, irq就是要禁止的中断号。 disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:
void disable_irq_nosync(unsigned int irq)
disable_irq_nosync函数调用以后立即返回,不会等待当前中断处理程序执行完毕。上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32的时候常说的关闭全局中断,这个时候可以使用如下两个函数:
local_irq_enable()
local_irq_disable()
local_irq_enable用于使能当前处理器中断系统, local_irq_disable用于禁止当前处理器中断系统。假如 A任务调用 local_irq_disable关闭全局中断 10S,当关闭了 2S的时候 B任务开始运行, B任务也调用 local_irq_disable关闭全局中断 3S 3秒以后 B任务调用 local_irq_enable函数将全局中断打开了。此时才过去 2+3=5秒的时间,然后全局中断就被打开了,此时 A任务要关闭 10S全局中断的愿望就破灭了,然后 A任务就“生气了”,结果很严重,可能系统都要被A任务整崩溃。为了解决这个问题, B任务不能直接简单粗暴的通过 local_irq_enable函数来打开全局中断,而是将中断状态恢复到以前的状态,要考 虑到别的任务的感受,此时就要用到下面两个函数:
local_irq_save(flags)
local_irq_restore(flags)
这两个函数是一对, local_irq_save函数用于禁止中断,并且将中断状态保存在 flags中。local_irq_restore用于恢复中断,将中断到 flags状态。
二、中断的上半部和下半部
1、中断的上半部和下半部是什么
在有些资料中也将上半部和下半部称为顶半部和底半部,都是一个意思。我们在使用request_irq申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发 ,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。比如电容触摸屏通过中断通知 SOC有触摸事件发生, SOC响应中断,然后通过 IIC接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC的速度最高也只有400Kbit/S,所以在中断中通过 IIC读取数据就会浪费时间。我们可以将通过 IIC读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处 理过程就分为了两部分:
-
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
-
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
因此, Linux内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度 快 的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。至于哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,这个就很考验驱动编写人员的功底了。这里有一些可以借鉴的参考点:
-
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
-
②、如果要处理的任务对时间敏感,可以放到上半部。
-
③、如果要处理的任务与硬件有关,可以放到上半部
-
④、除了上述三点以外的其他任务,优先考虑放到下半部。
上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢? Linux内核提供了多种下半部机制,接下来我们来学习一下这些下半部机制。
2、下半部处理机制
2.1软中断
一开始 Linux内核提供了“ bottom half”机制来实现下半部,简称 BH”。后面引入了软中断和 tasklet来替代“ BH”机制,完全可以使用软中断和 tasklet来替代 BH,从 2.5版本的 Linux内核开始 BH已经被抛弃了。 Linux内核 使用结构体 softirq_action表示软中断,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
};
可以看出,一共有 10个软中断,因此 NR_SOFTIRQS为 10,因此数组 softirq_vec有 10个元素。 softirq_action结构体中的 action成员变量就是软中断的服务函数,数组 softirq_vec是个全局数组,因此所有的 CPU(对于 SMP系统而言 )都可以访问到,每个 CPU都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU所执行的软中断服务函数确是相同的,都是数组 softirq_vec中定义的 action函数。要使用软中断,必须先使用 open_softirq函数注册对应的软中断处理函数, open_softirq函数原型如下:
void open_softirq(int nr, void (*action)(struct softirq_action *))
函数参数和返回值含义如下:
nr:要开启的软中断,在示例代码 51.1.2.3中选择一个。
action:软中断对应的处理函数。
返回值: 没有返回值。
注册好软中断以后需要通过 raise_softirq函数触发, raise_softirq函数原型如下:
void raise_softirq(unsigned int nr)
函数参数和返回值含义如下:
nr:要触发的软中断,在示例代码 51.1.2.3中选择一个。
返回值: 没有返回值。
软中断必须在编译的时候静态注册! Linux内核使用 softirq_init函数初始化软中断,softirq_init函数定义在 kernel/softirq.c文件里面,函数内容如下:
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
可以看出, softirq_init函数默认会打开 TASKLET_SOFTIRQ和HI_SOFTIRQ。
2.2tasklet
tasklet是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet之间,建议大家使用 tasklet。 Linux内核使用 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的参数 */
};
func函数就是 tasklet要执行的处理函数,用户定义函数内容,相当于中断处理函数。如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init函数初始化 tasklettaskled_init函数原型如下:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
函数参数和返回值含义如下:
t:要初始化的 tasklet func tasklet的处理函数。
data 要传递给 func函数的参数
返回值: 没有返回值。
也可以使用宏 DECLARE_TASKLET来一次性完成 tasklet的定义和初始化,DECLARE_TASKLET定义在 include/linux/interrupt.h文件中,定义如下 :
DECLARE_TASKLET(name, func, data)
其中 name为要定义的 tasklet名字,这个名字就是一个 tasklet_struct类型的时候变量, func就是 tasklet的处理函数, data是传递给 func函数的参数。
在上半部,也就是中断处理函数中调用 tasklet_schedule函数就能使 tasklet在合适的时间运行, tasklet_schedule函数原型如下:
void tasklet_schedule(struct tasklet_struct *t)
函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET宏里面的 name。
返回值: 没有返回值。
关于 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)
{
....