中断是设备驱动中非常重要的一个概念,所以这里首先讲述中断概念,然后讲述中断请求过程、中断处理程序以及中断相关的内核函数。
1中断基本概念
中断一词的字面意思是中间发生阻隔、停顿或故障而断开,但在计算机术语中的定义是是指CPU在正常运行程序时,由于内部/外部事件或由程 序预先安排的事件引起CPU暂时停止正在运行的程序,转到为该内部/外部事件或预先安排的事件服务的程序中去,服务完毕再返回去继续运行被暂时中断的程 序,这个过程称为中断。Linux的中断通常分为两种:软中断和硬中断。注意,这里的软和硬的意思是指和软件相关以及和硬件相关,而不是软件实现的中断或 硬件实现的中断。软中断就是通过“信号机制”实现的中断。Linux通过信号来产生对进程的各种中断操作,就目前所知的信号共有31个,这里就不一一列 举。硬中断就是通常意义上的“中断处理程序”,它是直接处理由硬件发过来的中断信号的。当硬中断收到它应当处理的中断信号以后,就回去自己驱动的设备上去 看看设备的状态寄存器以了解发生了什么事情,并进行相应的操作。对于软中断,主要是进程调度要做的事情,通常设备驱动的中断是指硬中断,所以我们这里讨论 硬中断,即和硬件相关的中断。
2中断请求过程
发生中断,是因为外设需要通知操作系统它那里发生了一些事情,但是中断的功能仅仅是一个设备报警灯,当灯亮的时候中断处理程序只知道有事情发生了,但发生 了什么事情还要亲自到设备那里去看才行。也就是说,当中断处理程序得知设备发生了一个中断的时候,它并不知道设备发生了什么事情,只有当它访问了设备上的 一些状态寄存器以后,才能知道具体发生了什么,要怎么去处理。设备通过中断线向中断控制器发送高电平告诉操作系统它产生了一个中断,而操作系统会从中断控 制器的状态位知道是哪条中断线上产生了中断。并不是每个设备都可以向中断线上发中断信号的,只有对某一条确定的中断线拥有了控制权,才可以向这条中断线上 发送信号。由于计算机的外部设备越来越多,通常中断线是非常宝贵的资源。要使用中断线,就得进行中断线的申请,即IRQ(Interrupt Requirement,中断请求),我们也常把申请一条中断线称为申请一个IRQ或者是申请一个中断号。IRQ是非常宝贵的,所以我们建议只有当设备需 要中断的时候才申请占用一个IRQ,或者是在申请IRQ时采用共享中断的方式,这样可以让更多的设备使用中断。无论对IRQ的使用方式是独占还是共享,申 请IRQ的过程都是一样的,分为3步:第1步,将所有的中断线探测一遍,看看哪些中断还没有被占用。从这些还没有被占用的中断中选一个作为该设备的 IRQ。第2步,通过中断申请函数申请选定的IRQ,这是要指定申请的方式是独占还是共享。第3步,根据中断申请函数的返回值决定怎么做:如果成功了就可 以正常工作,如果没成功或者重新申请或者放弃申请并返回错误。
3中断处理程序
Linux中的中断处理程序很有特色,它的一个中断处理程序通常分为两个部分:上半部(top half)和下半部(bottom half)。之所以会有上半部和下半部之分,完全是考虑中断处理的效率。上半部的功能是“登记中断”,当一个中断发生时,它就把设备驱动程序中中断例程的 下半部挂到该设备的下半部执行队列中去,然后就等待新的中断的到来。这样一来,上半部执行的速度就会很快,它就可以接受更多它负责的设备产生的中断了。上 半部之所以要快,是因为它是完全屏蔽中断的,如果它不执行完,其它的中断就不能被及时的处理,只能等到这个中断处理程序执行完毕以后。所以要尽可能多的对 设备产生的中断进行服务和处理,中断处理程序就一定要快。但是,有些中断事件的处理是比较复杂的,所以中断处理程序必须多花一点时间才能够把事情做完。可 怎样化解在短时间内完成复杂处理的矛盾呢,这时候Linux引入了下半部的概念。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的。下 半部几乎做了中断处理程序所有的事情,因为上半部只是将下半部排到了他们所负责设备的中断处理队列中去,然后就什么都不管了。下半部一般所负责的工作是察 看设备以获得产生中断的事件信息,并根据这些信息(一般通过读设备上的寄存器得来)进行相应的处理。由于下半部是可中断的,所以在它运行期间,如果其它的 设备产生了中断,这个下半部可以暂时的中断掉,等到那个设备的上半部运行完了,再回头来运行它。但是有一点一定要注意,那就是如果一个设备中断处理程序正 在运行,无论她是运行上半部还是运行下半部,只要中断处理程序还没有处理完毕,在这期间设备产生的新的中断都将被忽略掉。因为中断处理程序是不可重入的, 同一个中断处理程序是不能并行的。
4中断相关函数
与Linux设备驱动程序中断处理相关的函数有申请和释放IRQ(中断请求)函数,即request_irq和free_irq,这两个函数在实际中非常 重要,其函数原型如下,在头文件文件中声明。
int request_irq(unsigned int irq, irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *regs),unsigned long flags, const char *dev_name, void *dev_id);
该函数的作用是注册一个IRQ,其中参数irq是要申请的硬件中断号,参数handler是向系统登记的中断处理函数,是一个回调函数,中断发生时系统调 用这个函数,参数dev_id是设备的ID,参数flags是中断处理的属性,若设置为SA_INTERRUPT,表明中断处理程序是FRQ(快速中断请 求),FRQ程序被调用时屏蔽所有中断,而IRQ程序被调用时不屏蔽FRQ。若设为SA_SHIRQ,则多个设备共享中断,dev_id在中断共享时会用 到。参数dev_name是定义传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者。
void free_irq(unsigned int irq, void *dev_id);
该函数的作用是释放一个IRQ,一般是在退出设备或关闭设备时调用。
void enable_irq(unsigned int irq);
该函数的作用是打开一个IRQ,允许该IRQ产生中断。
void disable_irq(unsigned int irq);
该函数的作用是关闭一个IRQ,禁止该IRQ产生中断。 |