中断处理

转自:http://blog.chinaunix.net/uid-25445243-id-4052877.html

一、中断申请和释放函数
1.1、申请函数

在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:

int request_irq(unsigned int irq, irq_handler_t handler,
                                     unsigned long irqflags, const char *devname, void *dev_id)

功能:在发生对应于第 1个参数 irq 的中断时,则调用第2个参数handler 为要注册的中断服务函数(也就是把handler()
中断服务函数注册到内核中)。

参数说明:

irq是要申请的硬件中断号。

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。

irqflags指定了快速中断或中断共享等中断处理属性。
            IRQF_DISABLED:表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽。
            IRQF_SHARED:表示多个设备共享中断。

IRQF_SAMPLE_RANDOM:表示对系统熵有贡献,对系统获取随机数有好处。

这几个flag是可以通过或的方式同时使用的。

devname设置中断名称,通常是设备驱动程序的名称  在cat /proc/interrupts中可以看到此名称。

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。建议将设备结构指针作为dev_id参数。中
断共享注册时的注册函数中的dev_id参数是必不可少的,并且dev_id的值必须唯一。
        返回值:

request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用

且不能共享。

中断处理例程限制:

处理例程不能向用户空间发送或者接收数据,因为它不是在任何进程的上下文中执行的,处理例程也不能做任何可能发生

休眠的操作,例如调用wait_event、使用不带GFP_ATOMIC标志的内存分配操作、或者锁住一个信号量等等,最后处理例程不

能调用schdule函数。

中断处理例程的一个典型任务:如果中断通知进程所等待的事件已经发生,比如新数据到达,就会唤醒在该设备上休眠的

进程。比如在读每一个数据之前,read调用都是阻塞的,每当新的数据到达时,中断处理例程就会唤醒此进程。
        中断处理例程的返回值:如果发现其设备的确需要处理,应该返回IRQ_HANDLED,否则,返回值应该是IRQ_NONE。

1.2、注销函数

注销函数定义在Kernel/irq/manage.c中定义:

void free_irq(unsigned int irq, void *dev_id)

返回值:

函数运行正常时返回0,否则返回对应错误的负值。

1.3、共享中断

共享中断的不同设备的iqraction结构体都会添加进该中断号对应的 irq_desc结构体的action成员所指向的irqaction链表

内。当内核发生中断时,它会依次调用该链表内所有的handler函数。因此若驱动程序需要使用共享中断机制,其中断处理函数

必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断flag标志位进行判断。        
        共享中断通过request_irq安装,注意两点:

1、请求中断时,必须指定flags参数中的SA_SHIRQ位。

2、dev_id参数必须是唯一的,任何指向模块地址空间的指针都可以使用,但dev_id不能设置成NULL。

当请求一个共享中断时,如果满足下面条件之一,那么request_irq就会成功:

1、中断信号线空闲。

2、任何已经注册了该中断信号线的处理例程也标识了IRQ是共享的。

1.4、常用宏

        1、中断类型: 在request_irq(irq, handler, flags, devname, dev_id)中使用

#define SA_SHIRQ            共享中断(旧版本的,2.6.19之前的内核)

#define IRQF_SHARED         共享中断(新版本的)

#define SA_INTERRUPT        快速中断(旧版本的)

#define IRQF_DISABLED       快速中断(新版本的)

#define IRQF_SAMPLE_RANDOM  表示本中断源可以用作随机数生成器的熵池

        2、中断的触发类型: 在set_irq_type(irq, type)中使用

#define IRQ_TYPE_NONE           0x00000000     未指明类型

#define IRQ_TYPE_EDGE_RISING    0x00000001     上升沿触发

#define IRQ_TYPE_EDGE_FALLING   0x00000002     下降沿触发
        #define IRQ_TYPE_EDGE_BOTH      (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
        #define IRQ_TYPE_LEVEL_HIGH     0x00000004     高电平触发

#define IRQ_TYPE_LEVEL_LOW      0x00000008     低电平触发

#define IRQ_TYPE_SENSE_MASK     0x0000000f     /* Mask of the above */
        #define IRQ_TYPE_PROBE          0x00000010     /* Probing in progress */
二、顶半部和底半部

定义:Linux通过将中断处理例程分成两个部分来解决这个问题。称为顶半部的部分,是实际响应中断的例程,也就是request_irq

注册的中断例程。而所谓的底半部是一个被顶半部调度,并在稍后更安全的时间内执行的例程。顶半部和底半部处理例程之间的

最大不同,就是当底半部处理例程执行时,所有的中断都是打开的----这就是所谓的在更安全时间内运行。

典型的情况是顶半部保存设备的数据到一个设备特定的缓冲区并调度它的底半部,然后退出。然后底半部执行其他的必要工

作,例如唤醒进程、启动另外的I/O操作等等。这种方式允许在底半部工作期间,顶半部还可以继续为新的中断服务。

Linux内核有两种不同的机制可以用来实现底半部处理,tasklet通常是底半部处理的优选机制,因为这种机制非常快,但是

所有的tasklet代码必须是原子的。除了tasklet之外,还可以选择工作队列,它可以具有更高的延迟,但允许休眠。

2.1、tasklet

在编写设备驱动时, tasklet 机制是一种比较常见的机制。
        tasklet是一个可以在由系统决定的安全时刻,在软件中断上下文被调度运行的特殊函数。通常用于减少中断处理的时间,将
本应该是在中断服务程序中完成的任务转化成软中断完成。为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们
需要把一些在中断处理中不是非常紧急的任务放在后面执行,而让中断处理程序尽快返回。它们可以被多次调度运行,但tasklet
的调度并不会累积,也就是说,实际只会运行一次。
        但是tasklet可以与其他的tasklet并行地运行在对称多处理器(SMP)系统上,这样,如果驱动程序有多个tasklet,它们必须
使用某种锁机制来避免彼此间的冲突。tasklet可确保和第一次调度它们的函数运行在同样的CPU上。tasklet和中断处理例程之
间的锁是必须的。

tasklet的使用比较简单,只需要定义tasklet及其处理函数并将两者关联即可,在定义时可以采用两种形式。

例:
        struct tasklet_struct my_tasklet;

Void my_tasklet_func(unsigned long);
        第一种:

DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)

代码DECLARE_TASKLET实现了定义名称为my_tasklet的tasklet并将其与my_tasklet_func这个函数绑定,而传入这个函
数的参数为data。
        第二种:
        tasklet_init(&my_tasklet, my_tasklet_func, data);

需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度,如下所示

tasklet_schedule(&my_tasklet)

下面给出驱动模板

点击(此处)折叠或打开

  1. void xxx_do_tasklet(unsigned long);
  2. DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
  3. void xxx_do_tasklet(unsigned long)
  4. {
  5. ……
  6. }
  7. irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
  8. {
  9. ……
  10. tasklet_schedule(&xxx_tasklet);
  11. ……
  12. }
  13. int _init xxx_init(void)
  14. {
  15. ……
  16. result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL)
  17. ……
  18. }
  19. void _exit xxx_exit(void)
  20. {
  21. ……
  22. free_irq(xxx_irq,xxx_irq_interrupt);
  23. ……
  24. }

tasklet函数详解

点击(此处)折叠或打开

  1. #include <linux/interrupt.h>
  2. struct tasklet_struct {
  3. struct tasklet_struct *next;
  4. unsigned long state;
  5. atomic_t count;
  6. void (*func)(unsigned long);
  7. unsigned long data;
  8. };
  9. void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
  10. #define DECLARE_TASKLET(name, func, data) \ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
  11. #define DECLARE_TASKLET_DISABLED(name, func, data) \ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

1) void tasklet_disable(struct tasklet_struct *t);

函数暂时禁止给定的 tasklet被 tasklet_schedule 调度,直到这个 tasklet 再次被enable;若这个 tasklet 当前在运行,

这个函数忙等待直到这个tasklet退出。

2) void tasklet_disable_nosync(struct tasklet_struct *t);

和tasklet_disable类似,但是tasklet可能仍然运行在另一个 CPU。

3) void tasklet_enable(struct tasklet_struct *t);

使能一个之前被disable的 tasklet。若这个 tasklet 已经被调度, 它会很快运行。 tasklet_enable和tasklet_disable必须

匹配调用, 因为内核跟踪每个 tasklet 的"禁止次数"。

4) void tasklet_schedule(struct tasklet_struct *t);

调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行; 这保证了在其他事件被处理当中发生的事件受到应

有的注意. 这个做法也允许一个 tasklet 重新调度它自己。

5) void tasklet_hi_schedule(struct tasklet_struct *t);

和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet。在其他软中断之前,只

有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期。

6) void tasklet_kill(struct tasklet_struct *t);

确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet正在运行,这个函数

等待直到它执行完毕。若tasklet重新调度它自己,则必须阻止在调用 tasklet_kill前它重新调度它自己,如同使用 del_timer_sync。

2.2、工作队列

定义:工作队列会在将来的某个时间、在某个特殊的工作者进程上下文中调用一个函数。因为工作队列函数运行在进程上下文中,
因此可在必要时休眠。但是不能从工作队列向用户空间复制数据,工作者进程无法访问其他任何进程的地址空间。

Linux中的Workqueue机制就是为了简化内核线程的创建。通过调用workqueue的接口就能创建内核线程。并且可以根据当前

系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化。workqueue是内核中实现简单而有效的机制,他显然简化了内核

daemon的创建,方便了用户的编程。

2.2.1、Workqueue机制的实现

Workqueue机制中定义了两个重要的数据结构,分析如下:

1、cpu_workqueue_struct 结构。
        该结构将CPU和内核线程进行了绑定。在创建workqueue的过程中,Linux根据当前系统CPU的个数创建 cpu_workqueue_struct。
该结构主要维护了一个任务队列,以及内核线程需要睡眠的等待队列,另外还维护了一个任务上下文,即 task_struct。

2、work_struct结构是对任务的抽象。在该结构中需要维护具体的任务方法,需要处理的数据,以及任务处理的时间。该结构定义

如下:

点击(此处)折叠或打开

  1. struct work_struct {
  2. unsigned long pending;
  3. struct list_head entry;
  4. void (*func)(void *);
  5. void *data;
  6. void *wq_data;
  7. strut timer_list timer;
  8. };

当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对 workqueue队列进行初始化

时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后 Linux根据当前CPU的情况,为

workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个 cpu_workqueue_struct对象都会存在一条任务队列。紧接

着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化

接口将workqueue初始化完毕,返回workqueue的指针。

在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct

中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等

待队列上睡眠,直到有人唤醒daemon去处理任务队列。

Workqueue初始化完毕之后,将任务运行的上下文环境构建起来了,但是具体还没有可执行的任务,所以,需要定义具体的work_struct
对象。然后将work_struct加入到任务队列中,Linux会唤醒daemon去处理任务。

上述描述的workqueue内核实现原理可以描述如下:

在Workqueue机制中,提供了一个系统默认的workqueue队列——keventd_wq,这个队列是Linux系统在初始化的时候就创建的。
用户可以直接初始化一个work_struct对象,然后在该队列中进行调度,使用更加方便。

2.2.2、Workqueue编程接口


序号


接口函数


说明


1


create_workqueue


用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数:

@name:workqueue的名称


2


create_singlethread_workqueue


用于创建workqueue,只创建一个内核线程。输入参数:

@name:workqueue名称


3


destroy_workqueue


释放workqueue队列。输入参数:

@ workqueue_struct:需要释放的workqueue队列指针


4


schedule_work


调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数:

@ work_struct:具体任务对象指针


5


schedule_delayed_work


延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数:

@work_struct:具体任务对象指针

@delay:延迟时间


6


queue_work


调度执行一个指定workqueue中的任务。输入参数:

@ workqueue_struct:指定的workqueue指针

@work_struct:具体任务对象指针


7


queue_delayed_work


延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。

例子:
        定义一个工作队列:

struct work_struct my_wq;

void my_wq_func(unsigned long);

通过INIT_WORK可以初始化这个工作队列并将工作队列与处理函数绑定

INIT_WORK(&my_wq, my_wq_func):

调度工作队列:

schedule_work(&my_wq)

2.3、Workqueue与tasklet的联系

工作队列类似 tasklets,允许内核代码请求在将来某个时间调用一个函数,不同在于:

(1)tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更
多的灵活性,且能够休眠。

(2)tasklet只能在最初被提交的处理器上运行,这只是工作队列默认工作方式。

(3)内核代码可以请求工作队列函数被延后一个给定的时间间隔。

(4)tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。

时间: 2024-09-27 04:37:11

中断处理的相关文章

Linux 中断处理浅析

最近在研究异步消息处理, 突然想起linux内核的中断处理, 里面由始至终都贯穿着"重要的事马上做, 不重要的事推后做"的异步处理思想. 于是整理一下~ 第一阶段--获取中断号 每个CPU都有响应中断的能力, 每个CPU响应中断时都走相同的流程. 这个流程就是内核提供的中断服务程序. 在进入中断服务程序时, CPU已经自动禁止了本CPU上的中断响应, 因为CPU不能假定中断服务程序是可重入的. 中断处理程序的第一步要做两件事情: 1. 将中断号压入栈中; (不同中断号的中断对应不同的中

实验5 :分析system_call中断处理过程

分析system_call中断处理过程 上周我们使用gcc内嵌汇编调用系统调用,这次我们具体分析下过程. 将getpid嵌入menuos 代码从github下载,步骤如下: 1. 增加一个函数,getpid 2. 在main中添加MenuConfig("getpid","Show Pid", Getpid); 3. 重新编译 make roofs 4. 此时启动 执行getpid就可以看到打印出pid为1   menuos的原理 其实这个很简单,在上上周我们分析过l

实验五:分析system_call中断处理过程

原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 如果我写的不好或者有误的地方请留言 题目自拟,内容围绕系统调用system_call的处理过程进行: 博客内容中需要仔细分析system_call对应的汇编代码的工作过程,特别注意系统调用返回iret之前的进程调度时机等. 总结部分需要阐明自己对“系统调用处理过程”的理解,进一步推广到一般的中断处理过程. 实验报告: 1.将myfork()和

Linux中断处理驱动程序编写

本章节我们一起来探讨一下Linux中的中断: http://blog.csdn.net/gotosola/article/details/7422072 中断处理 http://www.cnblogs.com/tianshuai11/archive/2012/04/20/2477168.html 里边有自动探测中断号: http://blog.csdn.net/en_wang/article/details/6726421 当目标设备有能力告知驱动它要使用的中断号时,自动探测中断号只是意味着探测

驱动笔记 - 中断处理

中断注册int request_irq(unsigned int irq, irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *regs), unsigned long flags, const char *devname, void *dev_id) //返回0成功,或者返回错误码 irq:中断号handler:中断处理函数flags:与中断管理有关的各种选项devname:设备名dev_id共享中断时使用 (参数必须唯一

linux驱动之中断处理过程汇编部分

linux系统下驱动中,中断异常的处理过程,与裸机开发中断处理过程非常类似.通过简单的回顾裸机开发中断处理部分,来参考学习linux系统下中断处理流程. 一.ARM裸机开发中断处理过程 以S3C2440的裸机开发启动文件中,有关irq中断部分代码为例进行说明: .extern main .text .global _start _start: b Reset HandleUndef: b HandleUndef HandleSWI: b HandleSWI HandlePrefetchAbort

Linux设备驱动程序:中断处理之顶半部和底半部

http://blog.csdn.net/yuesichiu/article/details/8286469 设备的中断会打断内核中进程的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍.但是,这个良好的愿望往往与现实并不吻合.在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理. 为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top  half)和底半

中断处理 I/O内存

回顾:内核竞态与并发 什么情况下会产生竞态 1)SMP 2)单CPU支持任务抢占 3)中断和进程之间 4)中断和中断之间 解决竞态的方法 1)中断屏蔽 2)原子操作 位原子操作 整形原子操作 atomic{     int....} 3)自旋锁 优点:一旦可以获取锁,立即获取 缺点:长时间获取锁不成功,会消耗CPU资源 它所保护的临界资源(代码段)通常比较短 4)信号量 down(...)会导致睡眠 等等看前一章中 自旋锁只允许一个持有者,信号量可以有多个持有者 信号量保护的临界资源(代码段)通

《Linux内核设计与实现》读书笔记(七)- 中断处理【转】

转自:http://www.cnblogs.com/wang_yb/archive/2013/04/19/3030345.html 中断处理一般不是纯软件来实现的,需要硬件的支持.通过对中断的学习有助于更深入的了解系统的一些底层原理,特别是驱动程序的开发. 主要内容: 什么是中断 中断类型 中断相关函数 中断处理机制 中断控制方法 总结 1. 什么是中断 为了提高CPU和外围硬件(硬盘,键盘,鼠标等等)之间协同工作的性能,引入了中断的机制. 没有中断的话,CPU和外围设备之间协同工作可能只有轮询

Linux内核中断处理体系分析

前一篇博文中:linux内核初始化阶段通过early_trap_init()函数完成了把异常向量拷贝到0xFFFF0000开始的地方,这些异常向量大部分指向通过vector_stub宏定义的那段代码,这段代码完成的主要工作是计算异常返回地址.保存现场.切换到svc模式.跳转执行汇编异常处理函数,汇编异常处理函数工作在svc模式,先接管上一异常模式保存的现场,然后调用C处理函数,C函数返回后执行一段汇编代码完成异常返回工作.这一系列的工作就是基于arm9处理器的内核异常处理的体系架构. linux