【主要内容】

Linux设备驱动编程中的中断与定时器处理

【正文】

一、基础知识

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() 函数

a. 申请IRQ

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

b. 释放IRQ

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

2、屏蔽和使能中断

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

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

屏蔽:

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

使能:

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

3、底半部机制

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

a. Tasklet

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

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

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

  1. tasklet_schedule(struct tasklet *xxx_tasklet);

tasklet使用模版

  1. /* 定义 tasklet 和底半部函数并关联 */
  2. void xxx_do_tasklet(unsigned long data);
  3. DECLARE_TASKLET(xxx_tasklet, xxx_tasklet_func, data);
  4.  
  5. /* 中断处理底半部 */
  6. void xxx_tasklet_func()
  7. {
  8. /* 中断处理具体操作 */
  9. }
  10.  
  11. /* 中断处理顶半部 */
  12. irqreturn xxx_interrupt(int irq, void *dev_id)
  13. {
  14. //do something
  15. task_schedule(&xxx_tasklet);
  16. //do something
       return IRQ_HANDLED
  17. }
  18.  
  19. /* 设备驱动模块 init */
  20. int __init xxx_init(void)
  21. {
  22. ...
  23. /* 申请设备中断 */
  24. result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL);
  25. ...
  26. return 0;
  27. }
  28. module_init(xxx_init);
  29.  
  30. /* 设备驱动模块exit */
  31. void __exit xxx_exit(void)
  32. {
  33. ...
  34. /* 释放中断 */
  35. free_irq(xxx_irq, NULL);
  36. }
    module_exit(xxx_exit);

b. 工作队列 workqueue

工作队列与tasklet方法非常类似,使用一个结构体定义一个工作队列和一个底半部执行函数:

  1. struct work_struct {
  2.   atomic_long_t data;
  3.   struct list_head entry;
  4.   work_func_t func;
  5. #ifdef CONFIG_LOCKDEP
  6.   struct lockdep_map lockdep_map;
  7. #endif
  8. };
  1. struct work_struct my_wq; /* 定义一个工作队列 */
  2. void my_wq_func(unsigned long); /*定义一个处理函数 */

通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定(一般在模块初始化中使用):

  1. void INIT_WORK(struct work_struct *my_wq, work_func_t);
  2. /* my_wq 工作队列地址
  3. ** work_func_t 处理函数
  4. */

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

  1. schedule_work(&my_wq);

工作队列使用模版

  1. /* 定义工作队列和关联函数 */
  2. struct work_struct xxx_wq;
  3. void xxx_do_work(unsigned long);
  4.  
  5. /* 中断处理底半部 */
  6. void xxx_work(unsigned long)
  7. {
  8.   /* do something */
  9. }
  10.  
  11. /* 中断处理顶半部 */
  12. irqreturn_t xxx_interrupt(int irq, void *dev_id)
  13. {
  14.   ...
  15.   schedule_work(&xxx_wq);
  16.   ...
  17.   return IRQ_HANDLED;
  18. }
  19.  
  20. /* 设备驱动模块 init */
  21. int __init xxx_init(void)
  22. {
  23. ...
  24. /* 申请设备中断 */
  25. result = request_irq(xxx_irq, xxx_interrupt, IRQF_DISABLED, "xxx", NULL);
  26.   /* 初始化工作队列 */
  27.   INIT_WORK(&xxx_wq, xxx_do_work);
  28. ...
  29. return ;
  30. }
  31. module_init(xxx_init);
  32.  
  33. /* 设备驱动模块exit */
  34. void __exit xxx_exit(void)
  35. {
  36. ...
  37. /* 释放中断 */
  38. free_irq(xxx_irq, NULL);
  39. }
  40. module_exit(xxx_exit);

c. 软中断

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

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

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

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

Linux设备驱动中断机制的更多相关文章

  1. linux 设备驱动概述

    linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和 ...

  2. 浅谈Android系统移植、Linux设备驱动

    一.Android系统架构 第一层:Linux内核 包括驱动程序,管理内存.进程.电源等资源的程序 第二层:C/C++代码库 包括Linux的.so文件以及嵌入到APK程序中的NDK代码 第三层:An ...

  3. linux设备驱动概述,王明学learn

    linux设备驱动学习-1 本章节主要学习有操作系统的设备驱动和无操作系统设备驱动的区别,以及对操作系统和设备驱动关系的认识. 一.设备驱动的作用 对设备驱动最通俗的解释就是“驱使硬件设备行动” .设 ...

  4. Linux设备驱动工程师之路——内核链表的使用【转】

    本文转载自:http://blog.csdn.net/forever_key/article/details/6798685 Linux设备驱动工程师之路——内核链表的使用 K-Style 转载请注明 ...

  5. linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-119723.html linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟 xxxxxxxxxx ...

  6. linux设备驱动归纳总结(十二):简单的数码相框【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...

  7. linux设备驱动归纳总结(十一):写个简单的看门狗驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-112879.html linux设备驱动归纳总结(十一):写个简单的看门狗驱动 xxxxxxxxxxx ...

  8. linux设备驱动归纳总结(十):1.udev&misc【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-111839.html linux设备驱动归纳总结(十):1.udev&misc xxxxxxx ...

  9. linux设备驱动归纳总结(九):1.platform总线的设备和驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-111745.html linux设备驱动归纳总结(九):1.platform总线的设备和驱动 xxxx ...

随机推荐

  1. 【USACO 2.3.2】奶牛家谱

    [题目描述] 农民约翰准备购买一群新奶牛.在这个新的奶牛群中,每一个母亲奶牛都生两小奶牛.这些奶牛间的关系可以用二叉树来表示.这些二叉树总共有N个节点(3 <= N < 200).这些二叉 ...

  2. BroadcastReceiver监听电量变化

    用BroadcastReceiver监听电量的变化,可以实现BroadcastReceiver接收电量变化的广播,然后获取电量百分比信息. BatteryChangedReceiver.java pu ...

  3. [xfire]使用xfire开发webservice的简单示例

    目前项目上有用到xfire,所以临时看了些xfire的资料和示例,自己照着写了一个简单示例. xfire在2007年后已经停止更新,正式更名为apache cxf,也可以说是xfire2.0. xfi ...

  4. python -- 函数传参

    一.参数传入规则 可变参数允许传入0个或任意个参数,在函数调用时自动组装成一个tuple: 关键字参数允许传入0个或任意个参数,在函数调用时自动组装成一个dict: 1. 传入可变参数: def ca ...

  5. uva 10382 - Watering Grass(区域覆盖问题)

    Sample Input 8 20 2 5 3 4 1 1 2 7 2 10 2 13 3 16 2 19 4 3 10 1 3 5 9 3 6 1 3 10 1 5 3 1 1 9 1 Sample ...

  6. c# 重新认识 Double 浮点型

    double test1 = 0; for (int i = 0; i < 100000000; i++) { test1 += 0.0001; } 请问 test1 的值是几? 答案是:999 ...

  7. linux 路由表设置 之 route 指令详解

    使用下面的 route 命令可以查看 Linux 内核路由表. # route Destination     Gateway         Genmask Flags Metric Ref     ...

  8. asp.net WebForm 多语言的实现

    1.这里介绍的是实现中文和英文的切换.首先多语言的实现是采用资源文件的形式,建立2个多语言的资源文件.Resource.resx和Resource.zh-CN.resx. 2.将多语言这个属性放到用户 ...

  9. 转:Mysql在大型网站的应用架构演变

    原文来自于:http://www.cnblogs.com/Creator/p/3776110.html 原创文章,转载请注明: 转载自http://www.cnblogs.com/Creator/本文 ...

  10. java Future 模式

    考慮這樣一個情況,使用者可能快速翻頁瀏覽文件中,而圖片檔案很大,如此在瀏覽到有圖片的頁數時,就會導致圖片的載入,因而造成使用者瀏覽文件時會有停頓 的現象,所以我們希望在文件開啟之後,仍有一個背景作業持 ...