Linux中断机制

一、中断上半部与底半部

       所谓中断是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行。Linux系统中实现底半部机制主要有:tasklet,工作队列和软中断。软中断和tasklet运行于中断上下文,而工作队列则运行于进程上下文。因此,软中断和tasklet处理函数中不能睡眠,而工作队列处理函数中允许睡眠。中断注册函数和中断注销函数:

注册中断:

int request_irq(unsigned int irq, irqreturn_t (* handler) (int, void* ,struct pt_regs* ), unsigned long flags, const char * dev_name, void * dev_id);

参数意义依次是:中断号,中断处理函数,中断管理有关的掩码,中断请求设备名,中断信号线。

过程是:dev_name设备请求中断->cpu分配中断号->设置中断管理的掩码->分配中断信号线->处理中断函数->完成之后再根据设置情况返回原处理程序处继续处理程序。

注销中断;

void free_irq(unsigned int irq, void *dev_id);

释放中断和中断信号线

      中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:前半部分(top half)和后半部分(bottom half),前半部分内核立即执行,而后半部分留着稍后处理。

      首先,一个快速的“前半部分”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常地,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。

然后,就让一些与中断处理相关的有限个函数作为“后半部分”来运行,允许一个普通的内核函数,而不仅仅是服务于中断的一个函数,能以后半部分的身份来运行。允许几个内核函数合在一起作为一个后半部分来运行。后半部分运行时是允许中断请求的,而前半部分运行时是关中断的,这是二者之间的主要区别。

顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。

现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。

尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。

 

中断的底半部机制

实现机制主要有tasklet, 工作队列 和 软中断。

(1)tasklet

      void my_tasklet_func (unsigned long);

      DECLARE_TASKLET (my_tasklet, my_tasklet_func, data);

      /*定义一个tasklet结构my_tasklet, 与my_tasklet_func(data)函数相关联*/

      tasklet_schedule (&my_tasklet);

      /*使系统在适当的时候调度tasklet注册的函数*/

(2)工作队列

      struct work_struct my_wq;

      void my_wq_func (unsigned long);

      INIT_WORK (&my_wq, (void(*)(void *))my_wq_func,NULL);

      /*初始化工作队列并将其与处理函数绑定*/

      schedule_work (&my_wq); /*调度工作队列执行*/

(3)软中断(与通常说的软中断(软件指令引发的中断),比如arm的swi是完全不同的概念)

      在linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该

      函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一

      个软中断。

      

      软中断和tasklet 运行与软中断上下文,仍属于原子上下文的一种,而工作队列则运行与进程上下文。因此,软中断和tasklet处理函数中不能睡眠,而工作队列处理函数中允许睡眠。local_bh_disable() 和 local_bh_enable()是内核中用于禁止和使能软中断和tasklet底半部机制的函数。


      Linux实现底半部的机制,tasklet的使用较简单,我们只需要定义tasklet及其处理函数并将两者关联,例如:

void my_tasklet_func(unsigned long);   /*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);/*定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联 */

        代码DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)实现了定义名称为my_tasklet的tasklet并将其与 my_tasklet_func()这个函数绑定,而传入这个函数的参数为data。
        在需要调度tasklet 的时候引用一个 tasklet_schedule()函数就能使系统在适当的时候进行调度运行:tasklet_schedule(&my_tasklet); 使用 tasklet 作为底半部处理中断的设备驱动程序模板如代码所示:

/*定义 tasklet 和底半部函数并关联*/
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);

/*中断处理底半部*/
void xxx_do_tasklet(unsigned long)
{
  ...
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
  ...
  tasklet_schedule(&xxx_tasklet);
  ...
  return IRQ_HANDLED;
}

/*设备驱动模块加载函数*/
int _ _init xxx_init(void)
{
  ...
  /*申请中断*/
  result = request_irq(xxx_irq, xxx_interrupt,IRQF_DISABLED, "xxx", NULL);
  ...
}

/*设备驱动模块卸载函数*/
void _ _exit xxx_exit(void)
{
  ...
  /*释放中断*/
  free_irq(xxx_irq, xxx_interrupt);
  ...
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值