您的位置:首页 > 移动开发 > Android开发

Android深度探索:HAL与驱动开发学习笔记--中断

2017-09-28 10:15 393 查看


1、中断

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


2、中断的分类

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

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

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


二、Linux中断处理程序架构

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


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

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


Linux中断处理机制

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

a. 底半部可以被新的中断事件打断,这是和顶半部最大的不同,顶半部通常被设计成不可被打断

b. 底半部相对来说不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。

c. 如果中断要处理的工作本身很少,所有的工作可在顶半部全部完成


三、中断编程


1、申请和释放中断

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

request_threaded_irq()是Linux kernel 2.6.30 之后新加的irq handler API 

底半的处理方式主要有soft_irq, tasklet, workqueue三种,他们在使用方式和适用情况上各有不同。soft_irq用在对底半执行时间要求比较紧急或者非常重要的场合,主要为一些subsystem用,一般driver基本上用不上。 tasklet和work queue在普通的driver里用的相对较多,主要区别是tasklet是在中断上下文执行,而work queue是在process上下文,因此可以执行可能sleep的操作。


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 */



 


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)
======》
request_threaded_irq 两个完全相同的irq_handler_t类型的irq action : handler 和 thread_fn。

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);
}
=====》
request_irq是request_threaded_irq的一个wrapper,只是将其中的thread_fn置为空。


和request_irq非常类似,irq是中断号, handler是在发生中断时,首先要执行的code,非常类似于顶半,该函数最后会return IRQ_WAKE_THREAD来唤醒中断线程,

一般设为NULL,用系统提供的默认处理。thread_fn,是要在线程里执行的handler,非常类似于底半。 后三个参数基本和request_irq相同。irqsflags新增加了一

个标志,IRQF_ONESHOT,用来标明是在中断线程执行完后在打开该中断,该标志非常有用,否则中断有可能一直在顶半执行,而不能处理中断线程。例如对于

gpio level中断,如果不设置该位,在顶半执行完成后,会打开中断,此时由于电平没有变化,马上有执行中断,永远没有机会处理线程。


thread_fn:中断线程化,如果传递的是NULL。NULL表示没有中断线程化。

    在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。but,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。 




2、屏蔽和使能中断

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);



3、底半部机制

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底半部机制的函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: