LINUX设备驱动程序笔记(五)中断处理

     <一> 中断处理流程如下:

1、发生中断时,CPU执行异常向量vector_irq的代码。

2、在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ。

3、asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。

4、hadnle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等。

5、handle_irq逐个调用用户在action链表中注册的处理函数。

      <二>安装中断处理例程

注册/释放中断的函数:

int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs*), unsigned long flags,

const char *dev_name, void *dev_id);

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

unsigned int irq:申请的中断号

irqreturn_t (*handler)(int, void *, struct pt_regs*):安装的中断处理函数指针

unsigned long flags:中断管理有关的位掩码选项,中断的触发方式,如SA_INTERRUPT、SA_SHIRQ等

const char *dev_name:传递给request_irq的字符串,中断名字

void *dev_id:该指针只用于共享的中断信号线。没有使用共享时,dev_id设置为NULL。

中断处理例程可以在驱动程序初始化时或设备第一次打开时安装。但考虑中断资源有限,调用request_irq的正确位置应该是在设备第一次打开、硬件被告知产生中断之前。调用free_irq的位置是最后一次关闭设备、硬件被告知不用再中断处理器之后。

    <三>实现中断处理例程

中断处理例程其实也是一个C函数程序,但由于发生在中断时间内,所以它有以下特点:

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

2、处理例程不能做任何可能发生休眠的操作,例如调用wait_event,锁住一个信号量等。

3、处理例程不能调用schdule函数。

中断处理例程的功能就是将有关中断接收的信息反馈给设备,并根据正在服务的中断的不同含义对数据进行相应的读或写。它的一个典型任务就是:如果中断通知进程所等待的事件已经发生,比如新的数据到达,就会唤醒在该设备上休眠的进程。

无论是快速还是慢速处理例程,程序员都应该编写执行时间尽可能短的处理例程。如果需要执行一个长时间的计算任务,最好的方法是使用tasklet或者工作队列在更安全的时间内调度计算任务。

中断处理例程irqreturn_t (*handler)(int, void *, struct pt_regs*)的参数及返回值:

int irq:中断号

void *dev_id:一种客户数据类型即驱动程序可用的私有数据,传递给request_irq函数的void *参数会在中断发生时作为参数被传回处理例程。通常,我们会为dev_id传递一个指向自己设备的数据结构指针。例如:

static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs)
{
          struct sample_dev *dev = dev_id;
          ……
}
/*与该处理例程相关联的典型open代码如下所示:*/
static void sample_open(struct inode *inode, struct file *filp)
{
          struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);
          request_irq(dev->irq, sample_interrupt, 0, "sample", (void *)dev);
          ……
          return 0;
}

struct pt_reg *regs:很少使用。

返回值有三种:IRQ_HANDLED;IRQ_NONE;IRQ_RETVAL(handled);

禁用和启用中断:

有时设备驱动程序必须在一个时间内阻塞中断的发生。中断的禁用分为禁用单个中断和禁用所有中断。

禁用单个中断

#include <asm/irq.h>

void disable_irq(int irq);

void disable_irq_nosync(int irq);

void enable_irq(int irq);

禁用所有中断

#include <asm/irq.h>

void local_irq_save(unsigned long flags);

void local_irq_disable(void);

void local_irq_restore(unsigned long flags);

void local_irq_enable(void);

      <四>顶半部和底半部

中断处理的一个主要问题是怎样在处理例程内完成耗时的任务。相应一次设备中断需要完成一定数量的工作,但是中断处理例程需要尽快结束而不能使终端阻塞的时间过长,这两个需求彼此冲突。Linux通过将中断处理例程分成两部分来解决这个问题。称为顶半部的部分,是实际响应中断的例程,也就是用request_irq注册的中断例程;而所谓的底半部是一个被顶半部调度,并在稍后更安全的时间内执行的例程。顶半部处理例程和底半部处理例程之间最大的不同,就是当底半部处理例程执行时,所有的中断都是打开的——这就是所谓的更安全时间内运行。

典型的情况是顶半部保存设备的数据到一个设备特定的缓冲区并调度它的底半部,然后退出,这个操作是非常快的。然后,底半部执行其他必要的工作,例如唤醒进程、启动另外的I/O操作等等。这种方式允许在底半部工作期间,顶半部还可以继续为新的中断服务。

Linux内核有两种不同机制可以用来实现底半部处理:tasklet和工作队列。tasklet通常是底半部处理的优选机制,因为它非常快,但是所有的tasklet代码必须是原子的。工作队列具有更高的延迟,但允许休眠。

tasklet:tasklet是一个可以由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数。

使用宏DECLARE_TASKLET声明tasklet:DECLARE_TASKLET(name, function, data);其中的参数name是给tasklet起的名字,function是执行tasklet时调用的函数(它带有一个unsigned long型的参数并且返回void),data是一个用来传递给tasklet函数的unsigned long类型的值。

函数tasklet_schedule用来调度一个tasklet运行。示例如下:

void example_do_tasklet(unsigned long)
{
   ……
}
DECLARE_TASKLET(example_tasklet, example_do_tasklet, 0);

irqreturn_t example_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
     ……
     tasklet_schedule(&example_tasklet);
     ……
}

工作队列:工作队列会在将来的某个时间、在某个特定的工作者进程上下文中调用一个函数。因为工作队列函数运行在进程上下文中,因此可在必要时休眠。工作队列一般程序编写示例如下:

static struct work_struct example_workqueue;
INIT_WORK(&example_workqueue, (void (*)(void *))example_do_tasklet, NULL);
irqreturn_t example_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
     ……
     schedule_work(&example_workqueue);
     ……
}

      <五>中断共享

安装共享的处理例程:就像普通非共享的中断一样,共享的中断也是通过request_irq安装的,但有两处不同:

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

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

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

1、中断信号线空闲

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

使用共享处理例程的驱动程序需要小心一件事情:不能使用enable_irq和disable_irq。

时间: 2024-10-12 17:22:48

LINUX设备驱动程序笔记(五)中断处理的相关文章

LINUX设备驱动程序笔记(二)构造和运行模块

         <一>:设置测试系统 首先准备好一个内核源码树,构造一个新内核,然后安装到自己的系统中.           <二>:HelloWorld模块 #include <linux/init.h> //定义了驱动的初始化和退出相关的函数 #include <linux/module.h> //定义了内核模块相关的函数.变量及宏 MODULE_LICENSE("Dual BSD/GPL"); //该宏告诉内核,该模块采用自由许可

LINUX设备驱动程序笔记(一)设备驱动程序简介

<一>:设备驱动程序的作用 从一个角度看,设备驱动程序的作用在于提供机制,而不是策略.在编写驱动程序时,程序员应该特别注意下面这个基本概念:编写访问硬件的内核代码时,不要给用户强加任何特定策略.因为不同的用户有不同的需求,驱动程序应该处理如何使硬件可用的问题,而将怎样使用硬件的问题留给上层应用程序. 从另一个角度来看驱动程序,它还可以看作是应用程序和实际设备之间的一个软件层. 总的来说,驱动程序设计主要还是综合考虑下面三个方面的因素:提供给用户尽量多的选项.编写驱动程序要占用的时间以及尽量保持

LINUX设备驱动程序笔记(三)字符设备驱动程序

      <一>.主设备号和次设备号        对字符设备的访问时通过文件系统内的设备名称进行的.那些设备名称简单称之为文件系统树的节点,它们通常位于/dev目录.字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的'c'来识别.块设备同样位于/dev下,由字符'b'标识 crw-rw----  1 root root    253,   0 2013-09-11 20:33 usbmon0 crw-rw----  1 root root    253,   1 2013-09

LINUX设备驱动程序笔记(四)并发和竞态

       <一>.并发及其管理 大部分竞态可通过使用内核的并发控制原语,并应用几个基本的原理来避免.第一个规则是,只要可能,就应该避免资源的共享,这种思想的明显应用就是避免使用全局变量.但硬件资源本质上就是共享的,软件资源经常需要对其他执行线程可用.全局变量并不是共享数据的唯一途径,只要我们的代码将一个指针传递给了内核的其他部分,一个新的共享就可能建立.在单个执行线程之外共享硬件或软件资源的任何时候,因为另外一个线程可能产生对该资源的不一致观察,因此必须显示地管理对该资源的访问.访问管理的

LINUX设备驱动程序笔记(一)设备驱动程序简单介绍

<一>:设备驱动程序的作用 从一个角度看,设备驱动程序的作用在于提供机制,而不是策略. 在编写驱动程序时,程序猿应该特别注意以下这个基本概念:编写訪问硬件的内核代码时,不要给用户强加不论什么特定策略.由于不同的用户有不同的需求,驱动程序应该处理如何使硬件可用的问题.而将如何使用硬件的问题留给上层应用程序. 从还有一个角度来看驱动程序.它还能够看作是应用程序和实际设备之间的一个软件层. 总的来说,驱动程序设计主要还是综合考虑以下三个方面的因素:提供给用户尽量多的选项.编写驱动程序要占用的时间以及

Linux设备驱动程序学习笔记(一)

1.设备驱动程序扮演的角色:       设备程序是一个独立的“黑盒子”,使其某个特定硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节.用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序.将这些调用映射到作用于实际硬件的设备特有操作上,则是设备驱动程序的任务.2.驱动程序的作用:        驱动程序应该处理如何使用硬件可用的问题,而将怎样使用硬件的问题留给上层应用.(提供机制,而不是策略)3.内核功能划分:        进程管理    内存管理    文

linux设备驱动程序中的阻塞、IO多路复用与异步通知机制

一.阻塞与非阻塞 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回. 函数是否处于阻塞模式和驱动对应函数中的实现机制是直接相关的,但并不是一一对应的,例如我们在应用层设置为阻塞模式,如果驱动中没有实现阻塞,函数仍然没有阻塞功能. 二.等待队列 在linux设备驱动程序中,阻塞进程可以使用等待队列来实现. 在内核中,

linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件.#include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件.#include <mach/***.h> 是在linux-2.6.29/arch/ar

转:《Linux设备驱动程序3》源码目录结构和源码分析经典链接

转自:http://blog.csdn.net/geng823/article/details/37567557 [原创][专栏]<Linux设备驱动程序>--- LDD3源码目录结构和源码分析经典链接 [专栏]Linux设备驱动程序学习(总目录) [专栏]LDD3源码分析链接(总目录) 1. LDD3源码分析之hello.c与Makefile模板 2. LDD3源码分析之字符设备驱动程序 其他错误: 我的Linux内核为 3.2.0-65-generic-pae,在scull目录下make时