LDD3学习-第十章-中断处理

         LDD3-第十章-中断处理<一>        

         中断是指,CPU在执行程序的过程中,出现了某些突发事件时,CPU必须暂停执行当前的程序,转去处理其它突然事件,处理完毕后CPU又返回原来的程序被中断的位置并继续执行。中断分为两种:内部中断,来源于CPU内部(溢出,中断指令,用户态到内核态切换等);外部中断,来源于CPU外部,由外设提出请求(一般为中断引脚的信号)。

        中断信号线是非常珍贵且有限的资源(x86体系, 244个),内核维护了一个中断信号线的注册表。中断信号线的使用遵循注册-撤销的使用模式,并且有时可以与其他设备共享信号线。

中断信号线申请函数:
#include <linux/sched.h>    /* <kernel/irq/Manage.h>  */
int request_irq(unsigned int irq,     /* 中断信号线值 */
                irqreturn_t (*handler)(int, void *, struct pt_regs *), /* 中断处理程序*/
                unsigned long  flags,  /* 与中断管理相关的位掩码 */
                const char *dev_name,  /* 传递给rquest_irq函数的字符串,用以在/proc/interrupts 显示,一般为设备名称 */
                void *dev_id  /* 用于标明此终端号共享。一般指向自己实现的 struct xxx_device结构 */
               );
返回值: 0 成功; -INVAL 中断号无效或者中断处理程序handler为NULL; -EBUSY 中断被其他设备使用且不能共享

中断管理先关的位掩码flags:
    SA_INTERRUPT: 表示handler是一个快速中断的处理例程。快速中断运行在中断被禁用的情况下,即在中断函数运行的过程中会屏蔽所有中断。
    SA_SHIRQ: shared irq, 表示该终端号(线)能够被多个设备共享
    SA_SAMPLE_RANDOM:  表示中断信号的产生时间是真正的无规律的,可以对/dev/random和/dev/urandom设备使用的熵池做出共享。不建议有规律的、可以被控制的设备设置此标志  

取消中断号的使用
void free_irq(unsigned int irq, void *dev_id);

         中断处理例程(handler)可以在驱动程序本身初始化或者设备第一次打开的时候安装。但是,在驱动程序本身初始化时安装中断不是好主意,因为一个驱动模块(module)可能已经插入了内核,但是对应的设备可能很长时间或者从来不使用,那么该驱动始终占据一个中断号但是从来不真正使用,特别是当该驱动独占(即没有设置为SA_SHIRQ)申请到的终端号。因此,在设备第一次打开时申请并设置,最后一次关闭设备时释放中断号,是最佳的方式。缺点是,这样必须记录设备被打开和关闭的引用计数(打开时增加,关闭时减少)用以让驱动知道哪次打开是第一次,哪次关闭是最后一次。


申请中断实例(LDD3, short模块):
if (short_irq >= 0) { /* 已确定用哪个中断号 */
	result = request_irq(short_irq, short_interrupt,/* 将终端号,和中断处理例,设备名字等关联起来 */ 
			SA_INTERRUPT, "short", NULL);
	if (result) {
		printk(KERN_INFO "short: can't get assigned irq %i\n",
				short_irq);
		hort_irq = -1;
	}
	else { /* actually enable it -- assume this *is* a parallel port */
			outb(0x10,short_base+2); /* 启动设备的中断能力, 设备在初始化时是默认关闭中断能力的 */
	}
}

         可以通过/proc/interrupts查看正在使用的终端号的信息,包括终端号: cpu0  cpu1 ... 中断控制器  处理中断的设备(dev_id)。另外,/proc/stat也能显示终端信息,但是是与体系相关的。

        

          检测中断号IRQ

         自动检测中断号irq。在上面的申请中断号的时候,我们必须已经知道了驱动设备所使用的驱动号,即irq>=0。但是,实际情况是我们可能并不预先知道一个设备所使用的中断号。这就要求我们能够通过一定的手段探知设备将要使用的中断号(在模块的初始化过程中探测将要使用的中断号,但是在打开设备的时候申请request_irq并使用此中断号),一般分为几种情况,依据实际的硬件的说明使用:

          1.  通过读取设备配置寄存器的特定位来获知所使用的中断号。 例如:PCI设备,0x300c。

          2. 通过内核辅助函数,探测设备的中断号,例如short模块:

    int count = 0;
    do {
        unsigned long mask;

        mask = probe_irq_on();   /* 启用中断 */
        outb_p(0x10,short_base+2); /* enable reporting */
        outb_p(0x00,short_base);   /* clear the bit */
        outb_p(0xFF,short_base);   /* set the bit: interrupt! */
        outb_p(0x00,short_base+2); /* disable reporting */
        udelay(5);  /* give it some time */
        short_irq = probe_irq_off(mask);  /* 返回0没有找到中断号,负值产生了多次中断 */

        if (short_irq == 0) { /* 没有中断号 */
            printk(KERN_INFO "short: no irq reported by probe\n");
            short_irq = -1;
        }
        /* 在这应该实现对此次产生的中断的处理, 将每次为了探测中断号而产生的中断处理掉 
         * if more than one line has been activated, the result is
         * negative. We should service the interrupt (no need for lpt port)
         * and loop over again. Loop at most five times, then give up
         */
    } while (short_irq < 0 && count++ < 5);  /* 循环探测 5次 */
    if (short_irq < 0)  /* 最后还是没有探测到中断号 */
        printk("short: probe failed %i times, giving up\n", count);

          3.  通过I/O地址分配中断号,例如short模块:

	if (short_irq < 0) /* not yet specified: force the default on */
		switch(short_base) {
		    case 0x378: short_irq = 7; break;
		    case 0x278: short_irq = 2; break;
		    case 0x3bc: short_irq = 5; break;
		}
          4. 自己手动探测中断号。即,将每个中断号(0 ~NR_IRQS -1)都申请一次并且产生中断,然后通过中断处理程序中的第一个参数irq来获得中断号。因为,中断号申请后产生中断,内核会判断该中断产生的中断号,并且在调用中断处理函数的过程中赋值给第一个int irq参数。(具体代码,见LDD3)
       irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)     /* 中断处理程序 */
   

        中断处理程序

        中断处理函数是在中断时间运行的,可以理解为与其他程序并行运行,且不在任何进程的上下文中。因此,中断处理函数不能向用户态发送或者接受数据、不能做任何发生休眠的操作(wait_event,非GPA_ATOMIC的内存分配,锁住信号量等),最后也不能调用内核的schdule函数

      另外,中断处理程序没有自己的栈,相反,它共享被中断进程的内核栈,如果没有正在运行的进程,它就使用idle进程的栈。因为中断程序共享别人的堆栈,所以它们在栈中获取空间时必须非常节省。内核栈在32位体系结构上是8KB,在64位体系结构上是16KB.执行的进程上下文和产生的所有中断都共享内核栈。

      #include <linux/sched.h>
      static irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *regs)     /* 中断处理程序 */
      
      irq: 使用的中断号,内核自己安排并在运行中断处理程序handler时传递给int irq参数。
      dev_id: 用户数据,传递给request_irq函数中的void *dev_id。一般在request_irq函数中将其置为指向用户自己实现的xxx_device结构,这样当共享中断时可以通过它来判断是哪个具体设备产生了中断
      regs: 很少用,保存CPU进入中断处理程序之前的寄存器状态,用来调试。 

        中断处理函数的返回值很重要,如果确实处理了设备的中断(例如,给设备写读等),就返回IRQ_HANDLED ,如果不需要则返回IRQ_NONE。或者使用宏IRQ_RETVAL(value) 。对应的内核定义为,0、1与 value != 0。

       irqrequest_t,可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED.当中断处理程序检测到一个中断时,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回IRQ_HANDLED.C此外,也可以使用宏IRQ_RETVAL(x),如果x非0值,那么该宏返回IRQ_HANDLED,否则,返回IRQ_NONE.利用这个特殊的值,内核可以知道设备发出的是否是一种虚假的(未请求)中断。如果给定中断线上所有中断处理程序返回的都是IRQ_NONE,那么,内核就可以检测到出了问题。最后,需要说明的就是那个static了,中断处理程序通常会标记为static,因为它从来不会被别的文件中的代码直接调用。另外,中断处理程序是无需重入的,当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一个中断上接收另外一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断总是被禁止的。由此可见,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。      

        中断的功能就是将中断相关的信息反馈给设备,并根据情况进行相应的数据的读写。典型的任务是:通知进程等待的事件已经发生,例如数据到达,然后唤醒在该设备上休眠的程序。

          中断的启用和禁止。

           当需要在短时间内禁止中断,或者在持有自旋锁时不得不禁止中断时,内核提供了一下3个函数。不过应该尽量少用此策略,且不用在驱动中的互斥机制中。(......)

#include <asm/irq.h>
void disable_irq(int irq);  /* 禁止给定的中断,并且等待中断处理程序的结束返回。如果调用disable_irq函数的线程持有中断处理函数的资源则可能死锁 */
void disbale_irq_nosync(int irq); /* 禁止给定的中断,立即返回。可能产生竞争 */
void eable_irq(int irq);  /* 恢复中断 */ 
           可以嵌套调用,但是也要嵌套的恢复。 关闭当前处理器(即调用这些函数时运行的CPU)的所有中断:

#include <asm/system.h>
void local_irq_save(unsigned long flags);  /* 保存当前的中断状态到flags之后禁止中断 */ /* 当前中断状态? */
void local_irq_disable(void);  /* 直接关闭 */ 
/* 多个调用链中有个多个函数需要禁止中断,应使用local_irq_save */

void local_irq_restore(unsigned  long flags);
void local_irq__enable(void); 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章