Linux中断及处理方式
Linux中断
中断
中断的处理
处理方式
- 保护现场
- 中断处理函数
- 恢复现场
arm对中断的处理流程
- 初始化
a.设置中断源,使其可以产生中断
b.设置中断控制器,是否屏蔽,优先级等属性
c.使能中断
- 执行其他程序
- 产生中断
- CPU检查异常,处理中断,不同的中断跳去不同的地址执行(中断也是一种异常,参考异常向量表)
Linux中断相关API
- 中断号
每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中断线。在Linux内核中使用一个int变量表示中断号。
- 申请中断
在 Linux 内核中要想使用某个中断是需要申请的,request_irq函数用于申请中断。
int request_irq(unsigned int irq, /*中断号*/
irq_handler_t handler, /*中断处理函数*/
unsigned long flags, /*中断标志*/
const char *name, /*中断名称*/
void *dev)/*dev用来区分不同的中断,一般情况下将dev 设置为设备结构体*/
常用的几个标志如下表: 更多请查看 /include/linux/interrupt.h 中相关宏定义
标志 | 描述 |
---|---|
IRQF_SHARED | 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq函数的dev参数就是唯一区分他们的标志。 |
IRQF_ONESHOT | 单次中断,中断执行一次就接触 |
IRQF_TRIGGER_NONE | 无触发 |
IRQF_TRIGGER_RISING | 上升沿触发 |
IRQF_TRIGGER_FALLING | 下降沿触发 |
IRQF_TRIGGER_HIGH | 高电平触发 |
IRQF_TRIGGER_LOW | 低电平触发 |
3.释放中断
有申请就有释放
void free_irq(unsigned int irq, /*中断号*/
void *dev) /*使用共享中断号的话,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_HANDLE);
- 中断开关
中断使能与禁止
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
全局中断开关
local_irq_enable();
local_irq_disable();
中断处理的原则
- 中断不得嵌套,嵌套中断会导致保护现场的栈越来越多,最终爆炸
- 中断处理函数时间越短越好,时间太长会导致其他线程不能运行,表先出来就是卡顿
然而,总有一些中断需要很长的时间的去处理,linux内核就引入了上半部和下半部的机制。
上半部:接受中断,它就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。顶半部简单快速,执行时禁止一些或者全部中断。
下半部:上半部执行完毕后会将中断打开,然后随即执行下半部,下半部在先执行过程中是可以响应中断的。
上/下半部划分原则
- 如果一个任务对时间非常敏感,将其放在上半部中执行
- 如果一个任务和硬件有关,将其放在上半部中执行
- 如果一个任务要保证不被其他中断打断,将其放在上半部中执
- 其他所有任务,考虑放置在底半部执行
下半部实现和处理
- 软中断
建议使用tasklet
- tasklet
如果下半部处理的时间不太长,处于可以接受程度,可以使用tasklet处理下半部。tasklet是由软中断实现的。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。
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
void tasklet_init(struct tasklet_struct *t, /*要初始化的tasklet*/
void (*func)(unsigned long),/*tasklet执行的函数*/
unsigned long data) /*func的参数*/
也可以直接使用宏定义 定义并初始化tasklet
DECLARE_TASKLET(name, func, data)
在中断处理函数上半部完成后调度tasklet
void tasklet_schedule(struct tasklet_struct *t);
- 工作队列
当下半部处理的时间相对较长时,可以采用工作队列的方式。中断处理在处理完上半部开放中断以后,下半部会被内核线程接管,参与线程调度。所以使用工作队列处理的下半部中可以有堵塞或者睡眠情况。
内核线程kwork会从工作队列work queue中取出一个个work去执行。 我们只需要将下半部处理函数,封装成work结构体并添加到work queue中即可。
Linux用 work_struct 来表示一个工作
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
创建并初始化一个工作
两种方式
struct work_struct testwork;
INIT_WORK(&testwork, testwork_func_t);
#define DECLARE_WORK(work_struct, func)
和 tasklet 一样,工作也是需要调度才能运行的
bool schedule_work(struct work_struct *work);
- 新技术:threaded_irq
以前用work来线程化的处理内核,一个worker线程只能由一个CPU执行,在多cpu中凭什么让一个cpu处理所有的中断。
threaded_irq 可以为每一个中断创建一个内核进程,参与进程调度。
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)
这个函数允许只提供一个 thread_fn 可以将 handler设置为NULL
当中断触发时,系统会为其创建内核线程,执行thread_fn。
上一篇: 大数据面试题整理 --持续更新中