Linux 中断学习总结

中断这个概念并不陌生,在单片机编程里就用的很多,最常见的如定时器中断、按键中断等。
在单片机里面使用中断,大致的步骤如下:

  • 1.初始化配置(中断寄存器等)
  • 2.编写中断处理函数

中断处理过程,大致如下:

  • 1.触发中断
  • 2.跳转至相应中断处理函数
  • 3.中断处理完成后恢复至进入中断前的运行位置继续运行

中断与中断处理函数的绑定关系是通过中断向量表确定的。

在linux 系统中,中断的使用相对复杂些,不过大同小异。

1.中断注册与注销

函数说明
request_irq()在给定的中断线(由参数irq指定)上注册一个给定的中断处理函数
free_irq()注销给定中断线的中断处理函数
  • 1.1 request_irq 函数

request_irq 函数声明如下(include/linux/interrupt.h):

request_irq(unsigned int irq,
	 irq_handler_t handler, 
	 unsigned long flags,
	 const char *name, void *dev)

第一个参数:irq 是要申请的硬件中断号。对于某些设备,如系统时钟、键盘,这个值是预先制定的,而对于大多数设备来说,这个值是通过探测获取,或者编程动态确定。
第二个参数:handler 是一个指针,指向这个中断的实际处理函数,中断发生时,系统调用这个函数。
第三个参数:flags 可以为0,或者是一个或多个标志的位掩码,如:IRQF_DISABLED、IRQF_TIMER、IRQF_SHARED等。
第四个参数:name 与中断相关的设备的ASCII文本表示,如"keyboard", 这些名字会被/proc/irq ,/proc/interrupts 文件使用。
第五个参数:dev 一般用它来传递程序的驱动结构

  • 1.2 中断释放

free_irq

void free_irq(unsigned int irq, void *dev_id)

第一个参数:irq 是要申请的硬件中断号
第二个参数:dev_id 一般与 注册时传的dev相同

2.中断处理函数

声明:
irq_handler_t 定义如下(include/linux/interrupt.h):

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

一个简单的中断程序模板:

/*中断处理函数*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
	//...
 	do_something;
 	//...
}

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

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

3.中断控制

函数说明
local_irq_disable()禁用本地(仅仅是当前处理器)中断
local_irq_enable()激活本地中断
local_irq_save()保存本地中断的当前状态,然后禁用本地中断
local_irq_restore()恢复本地中断到指定状态
disable_irq()禁用给定中断线,并确保函数返回前没有在该中断线上没有处理程序在运行
disable_irq_nosync()禁用给定中断线,不等待处理程序运行完
enable_irq()激活给定中断
irqs_disabled()如果本地中断被禁止,返回非0,否则返回0
in_interrupt()如果在中断上下文中,返回非0,否则返回0
in_irq()如果当前正在执行中断处理程序,返回非0,否则返回0

4.tasklet 与 workque

  • 4.1 中断处理的上半部与下半部

       设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。
       为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部 。上半部(Top half)与下半部(Bottom half)。
       所谓的上半部是实际响应中断的函数 – 你使用 request_irq 注册的那个. 下半部是由上半部调度来延后执行的函数。

    在这里插入图片描述

    就是当一个中断处理程序比较耗时间时(毕竟你的几毫秒对于CPU来说都是几个世纪),使用上半部与下半部机制对其进行优化,上半部只处理必要的硬件交互,耗时的工作放到下半部去处理。这样来提高性能。

    底半部的实现方式,现在主要有两种,分别是tasklet 与 workqueue.

  • 4.2 tasklet 的使用
    • 创建一个tasklet
    DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, data)
    //或者
    tasklet_init(&xxx_tasklet,xxx_do_tasklet,data)
    
    • 编写tasklet处理程序
    void xxx_do_tasklet(unsigned long)
    {
    	//...
    }
    
    • 调度tasklet
    tasklet_schedule(&xxx_tasklet)
    
    • 一个使用tasklet的中断处理程序模板
    /*定义 tasklet 和底半部函数并关联*/
    void xxx_do_tasklet(unsigned long);
    DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
    
    
    /*中断处理上半部*/
    void xxx_do_tasklet(unsigned long data)
    {
    	//...
    }
    
    /*中断处理下半部*/
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
    	//...
     	tasklet_schedule(&xxx_tasklet);
     	//...
    }
    
    /*设备驱动模块加载函数*/
    int __init xxx_init(void)
    {
    	//...
    	/*申请中断*/
    	result = request_irq(xxx_irq, xxx_interrupt,IRQF_SHARED, "xxx", dev_id);
    	//...
    }
    
    /*设备驱动模块卸载函数*/
    void __exit xxx_exit(void)
    {
    	//...
    	/*释放中断*/
    	free_irq(xxx_irq, dev_id);
    }
    
  • 4.3 workqueue的使用
    • 创建一个work
     DECLARE_WORK(xxx_wq, xxx_do_work)
     //或者
     INIT_WORK(&xxx_wq,xxx_do_work)
    
    • workque 处理函数
    void xxx_do_work(unsigned long data)
    {
    	//...
    }
    
    • work 调度
     schedule_work(&xxx_wq)
    
    • 一个使用workqueue 的中断处理程序模板
    tasklet_schedule(&xxx_tasklet)
    
    • 一个使用tasklet的中断处理程序模板
    /*定义 work 和底半部函数并关联*/
    void xxx_do_work(unsigned long);
    DECLARE_WORK(xxx_wq, xxx_do_work);
    
    
    /*中断处理上半部*/
    void xxx_do_work(unsigned long data)
    {
    	//...
    }
    
    /*中断处理下半部*/
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
    	//...
     	schedule_work(&xxx_wq);
     	//...
    }
    
    /*设备驱动模块加载函数*/
    int __init xxx_init(void)
    {
    	//...
    	/*申请中断*/
    	result = request_irq(xxx_irq, xxx_interrupt,IRQF_SHARED, "xxx", dev_id);
    	//...
    }
    
    /*设备驱动模块卸载函数*/
    void __exit xxx_exit(void)
    {
    	//...
    	/*释放中断*/
    	free_irq(xxx_irq, dev_id);
    }
    

    代码上看其实大同小异,不过实际上有很大tasklet 与workqueue有很大差别

  • 5.4 tasklet 与 workqueue 的区别
    • tasklet 基于软中断实现,workqueue 基于内核线程实现
    • tasklet 不可以睡眠,workqueue 可以睡眠
    • tasklet 运行于中断上下文,worqueue 运行于进程上下文
      如何选择
      1.如果有休眠的需要,worqueue 是唯一的选择 ,如果不需要休眠,那么用 tasklet

最后:不论是裸机编程还是Linux编程,中断的处理函数都要求尽可能快的退出,不占用太长的CPU时间,以保证性能。Linux中断的上半部底半部思想其实也可以应用的到单片机编程里面。

参考书籍:
《Linux 内核设计与实现》
《Linux 设备驱动开发详解(宋宝华)》
《Linux 设备驱动程序》
《深入理解Linux 内核》
以上书籍各有侧重点,我觉得《Linux 内核设计与实现》最为亲民。而《Linux 设备驱动程序》则是由于翻译的问题,读起来十分费劲,有时需要对照英文原版进行理解。当然这几本书结合起来看最好。我现在也还没把这些书读完,嗯,继续前行!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值