Chinaunix首页 | 论坛 | 博客
  • 博客访问: 164886
  • 博文数量: 38
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2014-10-25 21:26
文章分类

全部博文(38)

文章存档

2015年(10)

2014年(28)

我的朋友

分类: LINUX

2014-12-24 20:48:44

    处理器的速度跟外围硬件设备的速度往往不在一个数量级上,因此,如果内核采取让处理器向硬件发出一个请求,然后专门等待回应的方法,显然差强人意;轮询虽然能够解决这个问题,但会周期性地重复执行。更好的办法是让硬件在需要的时候向内核发出信号(变内核主动为硬件主动),这就是中断机制。

6.1 中断

    硬件设备生成中断的时候并不考虑与处理器的时钟同步——也就是说中断随时可以产生。内核随时可能因为新到来的中断而被打断。从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚。然后由中断控制器向处理器发送相应的信号。

    不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识。这些中断值通常被称为中断请求(IRQ)线。

异常

    异常与中断不同,它在产生时必须考虑与处理器的同步。实际上异常也常常被称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。

    因为许多处理器体系结构处理异常与处理中断的方式类似,因此,内核对它们的处理也很类似。我们已经熟悉一种特殊的异常:系统调用处理程序异常。 中断的工作方式与之类似,其差异只在于中断是由硬件而不是软件引起的。   

6.2 中断处理程序

      在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程(interrupt service routine,ISR)。产生中断的每一个设备都有一个(中断处理程序通常不是和特定设备相关联,而是和特定中断相关联,也就是说,若一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动也就需要准备多个这样的函数。)相应的中断处理程序。一个设备的中断程序是它设备驱动程序的一部分——设备驱动程序是用于对设备进行管理的内核代码。

       在Linux中,中断处理程序看起来就是普普通通的C函数。只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递处理程序的信息。中断处理程序与其它内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。

      上半部与下半部的对比

      通常我们把中断处理切为两个部分或两半。中断处理程序是上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,这些工作都是在所有中断被禁止的情况下完成。能够被允许稍后完成的工作会推迟到下半部去。在合适的时机,下半部会被开中断执行

6.3 注册中断处理程序

    中断处理程序是管理硬件的驱动程序的组成部分。每个设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。驱动程序可以通过下面的函数注册并激活一个中断处理程序。 int request_irq( unsigned int irq,         

                      irqrequest_t(*handler)( int, void *, struct pt_regs *),

                      unsigned long irqflags,

                      const char * devname,

                      void *dev_id)

(1)第一个参数irq表示要分配的中断号

(2)第二个参数handler是一个指针,指向处理这个中断的实际中断处理程序

(3)第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:SA_INTERRUPT:此标志表明给 定的中断处理程序是一个快速中断处理程序。SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对     内核熵池有贡献。SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。

(4)第四个参数devname是与中断相关的设备的ASCII文本表示法(设备名)。这些名字会被/proc/irq和proc/interrupt文件使用,以便与用户通信。

(5)第五个参数 dev_id主要用于共享中断线。用于区分共享中断线上的各个中断处理程序。内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传递驱动程序的设备结构:这个指针是唯一的,而且有可能在中断处理程序内及设备模式中被用到。注意,request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。一个例子:

if(request_irq(irqn, my_interrupt, SA_SHIRQ, "my_device", dev)) 

       printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);                                  return -EIO;                                          

} 该例子中我们用dev_id传递dev结构体(通常情况下均取该值)

释放中断处理程序

卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。调用void free_irq(unsigned int irq, void *dev_id)来释放中断线。如果指定的中断线不是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。不管在那种情况下,如果dev_id非空,它都必须与需要删除的处理程序相匹配。

6.4   编写中断处理程序

 一个典型的中断处理程序声明:                                                                 static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)                  注意,它的类型与request_irq()参数中handler所要求的参数类型相匹配(在前面我没已经看到,handler函数有一个类型为 irqreturn_t的返回值)。dev_id是一个通用的指针,它与在中断注册时传递给request_irq()的参数dev_id必须一致。 dev_id也有可能指向中断处理程序使用的一个数据结构,对每个设备而言,设备结构都是唯一的,而且可能在中断处理程序中也用的到,因此,也通常会把设备结构传递给dev_id。   中断处理程序的返回值是一个特殊类型:irqreturn_t。它可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回IRQ_HANDLED。

6.4.1 共享的中断处理程序

共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下几点:

(1)request_irq()的参数flags必须设置SA_SHIRQ标志

(2)对每个注册的中断处理程序来说,dev_id参数必须唯一。而指向任一设备结构体的指针就可以满足这一要求;通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。

(3)中断处理程序必须能够区分它的设备是否真的产生中断。这既需要硬件支持,也需要处理程序中有相关的处理逻辑。(大多数硬件设备提供状态寄存器或类似机制,以便中断处理程序进行检查)

6.5 中断上下文

    当执行一个中断处理程序或下半部时,内核处于中断上下文中。让我们先回忆一下进程上下文。进程上下文是一种内核所处的模式,此时内核代表进程执行——例如,执行系统调用或运行内核线程。在进程上下文中,可以通过current宏关联当前进程。中断上下文和进程并没有什么瓜葛。与current宏也是不相干的(它此时指向被中断的进程)。中断上下文不可以睡眠。如果一个函数睡眠,就不能在中断处理程序中使用——这是对什么样的函数可以在中断处理程序中使用的限制。

    中断上下文有较为严格的时间限制,因为它打断了其他代码。中断上下文中的代码应当迅速简洁,尽量不要使用循环去处理繁重的工作。请永远牢记:中断处理程序打断了其他的代码(甚至可能是打断了在其他中断线的另一个中断处理程序)。尽量把工作从中断处理程序中分离出来,放在下半部来执行,因为下半部可以在更合适的时间运行。写中断处理程序时不必关心栈如何设置,或者内核栈的大小是多少。总而言之,尽量节约内核栈空间

6.6 中断处理机制的实现

       中断处理系统在Linux中的实现是非常依赖于体系结构的。实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。设备产生中断,通过总线把电信号发送给中断控制器。如果中断是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号,处理器会立即停止它正在做的事情,关闭中断系统,然后跳转到内存中预定义的位置开始执行那里的代码。这个预定以的位置是内核设置的,是中断处理程序的入口点。    在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核。对于每一个中断线,处理器都会跳到对应的一个唯一的位置。至此,内核知道了所接受的中断的IRQ号。初始入口点只是在栈中保存这个号,并存放当前寄存器的值(这些值属于被中断的任务),然后,内核调用函数 do_IRQ()。


6.7 中断控制

Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在中找到。 下面给出一些中断控制方法:

local_irq_disable()          禁止本地中断传送

local_irq_enable()          激活本地中断传送

local_irq_save()      保存本地中断传递的当前状态,然后禁止本地中断传递

local_irq_restore()    恢复本地中断传递到给定的状态

disable_irq()          禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行

disable_irq_nosynoc()   禁止给定中断线

enable_irq()            激活给定中断线

irqs_disabled()         如果本地中断传递被禁止,则返回非0,否则返回0

in_interrupt()          如果在中断上下文中,则返回非0,如果在进程上下文中,则返回0

in_irq()                如果当前正在执行中断处理程序,则返回非0,否则,返回0 

阅读(1621) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~