Linux设备驱动中断机制

1、基础知识

一、中断

所谓中断是指CPU在执行程序的过程当中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回程序被中断的位置并继续执行。编程

二、中断的分类

  1)根据中断来源分为:内部中断和外部中断。内部中断来源于CPU内部(软中断指令、溢出、语法错误等),外部中断来自CPU外部,由设备提出请求。架构

  2)根据是否可被屏蔽分为:可屏蔽中断和不可屏蔽中断(NMI),被屏蔽的中断将不会获得响应。函数

  3)根据中断入口跳转方法分为:向量中断和非向量中断。向量中断为不一样的中断分配不一样的中断号,非向量中断多个中断共享一个中断号,在软件中判断具体是哪一个中断(非向量中断由软件提供中断服务程序入口地址)。atom

2、Linux中断处理程序架构

设备的中断会打断内核中正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量的短小(时间短),可是在大多数实际使用中,要完成的工做都是复杂的,它可能须要进行大量的耗时工做。spa

一、Linux中断处理中的顶半部和底半部机制

因为中断服务程序的执行并不存在于进程上下文,所以,要求中断服务程序的时间尽量的短。 为了在中断执行事件尽量短和中断处理需完成大量耗时工做之间找到一个平衡点,Linux将中断处理分为两个部分:顶半部(top half)和底半部(bottom half)。设计

Linux中断处理机制指针

顶半部完成尽量少的比较紧急的功能,它每每只是简单地读取寄存器中的中断状态并清除中断标志后进行“登记中断”的工做。“登记”意味着将底半部的处理程序挂载到该设备的底半部指向队列中去。底半部做为工做重心,完成中断事件的绝大多数任务。rest

a. 底半部能够被新的中断事件打断,这是和顶半部最大的不一样,顶半部一般被设计成不可被打断code

b. 底半部相对来讲不是很是紧急的,并且相对比较耗时,不在硬件中断服务程序中执行。blog

c. 若是中断要处理的工做自己不多,全部的工做可在顶半部所有完成

3、中断编程

一、申请和释放中断

在Linux设备驱动中,使用中断的设备须要申请和释放相对应的中断,分别使用内核提供的 request_irq() 和 free_irq() 函数

a. 申请IRQ

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);

 

复制代码
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) /* 参数: ** irq:要申请的硬件中断号 ** handler:中断处理函数(顶半部) ** irqflags:触发方式及工做方式 **      触发:IRQF_TRIGGER_RISING  上升沿触发 **      IRQF_TRIGGER_FALLING  降低沿触发 **      IRQF_TRIGGER_HIGH  高电平触发 **      IRQF_TRIGGER_LOW  低电平触发 **      工做:不写:快速中断(一个设备占用,且中断例程回调过程当中会屏蔽中断) **      IRQF_SHARED:共享中断 ** dev_id:在共享中断时会用到(中断注销与中断注册的此参数应保持一致) ** 返回值:成功返回 - 0      失败返回 - 负值(绝对值为错误码) */
复制代码

b. 释放IRQ

void free_irq(unsigned int irq, void *dev_id); /* 参数参见申请IRQ */

二、屏蔽和使能中断

void disable_irq(int irq);  //屏蔽中短、当即返回 void disable_irq_nosync(int irq);  //屏蔽中断、等待当前中断处理结束后返回 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void enable_irq(int irq);  //使能中断

 全局中断使能和屏蔽函数(或宏)

屏蔽:

#define local_irq_save(flags) ...
void local irq_disable(void );

 使能:

#define local_irq_restore(flags) ...
void local_irq_enable(void);

三、底半部机制

Linux实现底半部机制的的主要方式有 Tasklet、工做队列和软中断

a. Tasklet

Tasklet使用简单,只须要定义tasklet及其处理函数并将两者关联便可,例如:

void my_tasklet_func(unsigned long);  /* 定义一个处理函数 */ DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
/* 定义一个名为 my_tasklet 的 struct tasklet 并将其与 my_tasklet_func 绑定,data为传入 my_tasklet_func的参数 */

只须要在顶半部中电泳 tasklet_schedule()函数就能使系统在适当的时候进行调度运行

tasklet_schedule(struct tasklet *xxx_tasklet);

tasklet使用模版

复制代码
/* 定义 tasklet 和底半部函数并关联 */ void xxx_do_tasklet(unsigned long data); DECLARE_TASKLET(xxx_tasklet, xxx_tasklet_func, data); /* 中断处理底半部 */ void xxx_tasklet_func() { /* 中断处理具体操做 */ } /* 中断处理顶半部 */ irqreturn xxx_interrupt(int irq, void *dev_id) { //do something task_schedule(&xxx_tasklet); //do something
   return IRQ_HANDLED; } /* 设备驱动模块 init */ int __init xxx_init(void) { ... /* 申请设备中断 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL); ... return 0; } module_init(xxx_init); /* 设备驱动模块exit */ void __exit xxx_exit(void) { ... /* 释放中断 */ free_irq(xxx_irq, NULL); }
module_exit(xxx_exit);
复制代码

b. 工做队列 workqueue

工做队列与tasklet方法很是相似,使用一个结构体定义一个工做队列和一个底半部执行函数:

复制代码
struct work_struct {   atomic_long_t data;   struct list_head entry;   work_func_t func; #ifdef CONFIG_LOCKDEP   struct lockdep_map lockdep_map; #endif };
复制代码
struct work_struct my_wq; /* 定义一个工做队列 */ void my_wq_func(unsigned long); /*定义一个处理函数 */

经过INIT_WORK()能够初始化这个工做队列并将工做队列与处理函数绑定(通常在模块初始化中使用):

void INIT_WORK(struct work_struct *my_wq, work_func_t); /* my_wq 工做队列地址 ** work_func_t 处理函数 */

与tasklet_schedule_work ()对应的用于调度工做队列执行的函数为schedule_work()

schedule_work(&my_wq);

工做队列使用模版

复制代码
/* 定义工做队列和关联函数 */ struct work_struct xxx_wq; void xxx_do_work(unsigned long); /* 中断处理底半部 */ void xxx_work(unsigned long) {   /* do something */ } /* 中断处理顶半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id) {   ...   schedule_work(&xxx_wq);   ...   return IRQ_HANDLED; } /* 设备驱动模块 init */ int __init xxx_init(void) { ... /* 申请设备中断 */ result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL);   /* 初始化工做队列 */   INIT_WORK(&xxx_wq, xxx_do_work); ... return 0; } module_init(xxx_init); /* 设备驱动模块exit */ void __exit xxx_exit(void) { ... /* 释放中断 */ free_irq(xxx_irq, NULL); } module_exit(xxx_exit);
复制代码

c. 软中断

软中断(softirq)也是一种传统的底半部处理机制,它的执行时机一般是顶半部返回的时候,tasklet的基于软中断实现的,所以也运行于软中断上下文。

在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体中包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数能够注册软中断对应的处理函数,而raise_softirq()函数能够触发一个软中断。

struct softirq_action {   void (*action)(struct softirq_action *); };
void open_softirq(int nr, void (*action)(struct softirq_action *));  /* 注册软中断 */ void raise_softirq(unsigned int nr);  /* 触发软中断 */

 local_bh_disable() 和 local_bh_enable() 是内核中用于禁止和使能软中断和tasklet底半部机制的函数。

软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工做队列则运行于进程上下文,所以,软中断和tasklet处理函数中不能睡眠,而工做队列处理函数中容许睡眠

相关文章
相关标签/搜索