11.2.5 中断处理

   设备一般都比CPU慢得多。因此一般情况下,当一个进程通过设备驱动程序向设备发出读写请求后,CPU并不等待I/O操作的完成,而是让正在执行的进程去睡眠,CPU自己做别的事情,例如唤醒另一个进程执行。当设备完成I/O操作需要通知CPU时,会向CPU发出一个中断请求;然后CPU根据中断请求来决定调用相应的设备驱动程序。

    当设备执行某个命令时,如“将读取磁头移动到软盘的第42扇区上”,设备驱动程序可以从查询方式和中断方式中选择一种来判断设备是否已经完成此命令。

    查询方式意味着需要经常读取设备的状态,一直到设备状态表明请求已经完成为止。如果设备驱动程序被连接进内核,这时使用查询方式将会带来灾难性后果:内核将在此过程中无所事事,直到设备完成目前的请求。有一种方法可以有效的改善这一弊端,就是通过使用系统定时器,使内核周期性调用设备驱动程序中的某个例程来检查设备状态。 使用定时器是查询方式中最好的一种,但更有效的方法是使用中断。

    基于中断的设备驱动程序,指的是在硬件设备需要服务时向 CPU 发一个中断信号,引发中断服务子程序执行 。这样就大大地提高了系统资源的利用率,使内核不必一直等到设备执行完任务后才开始有事可干,而是在设备工作期间内核就可以转去处理其它的事务,收到中断请求信号时再回头响应设备。

1. Linux 对中断的管理

     Linux 内核为了将来自硬件设备的中断传递到相应的设备驱动程序,在驱动程序初始化的时候就将其对应的中断程序进行了登记,即通过调用函数 request_irq (  ) 将其中断信息添加到结构为irqaction的数组中,从而使中断号和中断服务程序联系起来。请参见第四章。

request_irq (  )函数原形如下:

 

    int request_irq(unsigned int irq,              /*  中断请求号  */

    void (*handler)(int, void *, struct pt_regs *),    /*  指向中断服务子程序 */

        unsigned long irqflags,        /*  中断类型 */

         const char * devname,         /*  设备的名字 */

         void *dev_id)

 

另外,irqaction的数据结构如下,其图示如图11.5                         

    struct irqaction {

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

        unsigned long flags;              

        unsigned long mask;       

        const char *name; 

        void *dev_id;

       struct irqaction *next;

    };

static struct irqaction *irq_action[NR_IRQS+1]

                 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 11.5 irqaction的数据结构

 

     根据设备的中断号可以在数组 irq_action 检索到设备的中断信息。对中断资源的请求在驱动程序初始化时就已经完成。

    在传统的 PC 体系结构中 ,有些中断已经被固定下来。软盘设备正是这种情况,它的中断号总为6。有时设备驱动程序可能不知道设备使用的中断号,对PCI设备来说这不是什么大问题,它们总是可以通过设备配置头知道其中断号。但对于ISA设备则没有取得中断号的方便方式,Linux通过让设备驱动程序检测它们的中断号来解决这个问题。

    让我们来看一下对ISA设备中断号的检测过程。设备驱动程序首先迫使 ISA 设备引起一个中断,系统中所有未被分配的中断都被打开。此时设备引发的中断可以通过可编程中断控制器来发送出去,在它接受到 CPU 的响应信号以后将中断号放置在数据线上,Linux 读取此数据并将其内容返回给设备驱动程序。非 0 结果则表示在此次检测中有中断发生,设备驱动程序然后将关闭检测并将所有未分配中断屏蔽掉, 这样ISA设备驱动程序就成功的找到了设备的IRQ号。

    基于PCI系统比基于ISA系统有更多的动态性。ISA设备使用的中断引脚通常是通过硬件设备上的跳线来设置的。而每个 PCI 设备都对应一个配置头, PCI设备在系统启动与初始化PCI时由PCI BIOS PCI子系统来分配中断,将其放入配置头中,故而驱动程序可以方便的获得PCI设备使用的中断号。

    系统中可能存在许多PCI中断源,比如在使用PCI-PCI桥接器时。这些中断源的个数可能将超出系统可编程中断控制器的引脚数。此时PCI设备必须共享中断号,中断控制器上的一个引脚可能被多个PCI设备同时使用。Linux让中断的第一个请求者申明此中断是否可以共享,中断的共享将导致 irq_action 数组中的一个入口同时指向几个irqaction数据结构,如图11.5所示。当共享中断发生时Linux将调用对应此中断源的所有中断处理过程。

 

2. Linux  对中断的处理

    Linux中断处理子系统的一个基本任务是将中断正确联系到中断处理代码中的正确位置。这些代码必须了解系统的中断拓扑结构。例如在中断控制器上引脚6上发生的软盘控制器中断必须被辨认出的确来自软盘并同系统的软盘设备驱动的中断服务子程序联系起来。

    中断发生时, Linux 首先读取系统可编程中断控制器中中断状态寄存器,判断出中断源,将其转换成irq_action 数组中偏移值(例如来自软盘控制器引脚6的中断将被转换成对应于irq_action数组中的第7个指针),然后调用其相应的中断处理程序。

    Linux 内核调用设备驱动程序的中断服务子程序时,必须找出中断产生的原因以及相应的解决办法,这是通过读取设备上的状态寄存器的内容来完成的。

    下面我们结合输入/输出系统的层次结构来看一下中断在驱动程序工作的过程中的作用:

    1)用户发出某种输入/输出请求;

    2)调用驱动程序的 read() 函数或 request()  函数,将完成的输入/输出的指令送给设备控制器,现在设备驱动程序等待操作的发生。

    3小段时间以后,硬设备准备好完成指令的操作,并产生中断信号标志事件的发生。

    4)中断信号导致调用驱动程序的中断服务子程序,它将所要的数据从硬设备复制到设备驱动程序的缓冲区中,并通知正在等待的read() 函数和request()函数,现在数据可供使用。

    5)在数据可供使用时,read()request()函数现在可将数据提供给用户进程。

上述过程是经过了简化了的,但却反映了中断的主要过程的主要方面。