您的位置:首页 > 运维架构 > Linux

linux内核中断实现

2011-08-19 19:23 141 查看
中断的内核实现
一.中断的使用
Linux 内核需要对连接到计算机上的所有硬件设备进行管理,毫无疑问这是它的份内事。如果要管理这些设备,首先得和它们互相通信才行,一般来说,有下面两种方案可实现这种内核和设备之间的通讯:
(1)轮询(polling) 让内核定期对设备的状态进行查询,然后做出相应的处理;
(2)中断(interrupt) 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。
第一种方案会让内核做不少的无用功,因为轮询总会周期性的重复执行,大量地耗用CPU 时间,因此效率及其低下,所以一般都是采用第二种方案 。
从物理学的角度看,中断是一种电信号,由硬件设备产生,并直接送入中断控制器(如8259A)的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到该信号,便中断自己当前正在处理的工作,转而去处理中断。此后,处理器会通知 操作系统已经产生中断,这样,操作系统 就可以对这个中断进行适当的处理。
不同的设备对应的中断不同,相同的设备也可能暂用多条中断线,而每个中断都通过一个唯一的数字标识,这些值通常被称为中断请求线。
二.中断的分类
中断可以分为同步和一步中断两种:

(1)同步中断:是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用,非法指令等。

(2)异步中断:是指由其他硬件设备,依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断等。这些硬件中断的到来,对于处理器来说不可预知,而且必须及时的响应,所以它会使当前进程停止运行。

下面的内容主要是针对异步中断进行的讨论。

三.中断描述符表

linux内核中断处理模型结构图如下:

下面对这张表中涉及的数据结构进行分析:

(1)Linux内核中定义了名字为irq_desc的中断例程描述符表,这可以在文件include/linux/irq.h中找到:

struct irqdesc irq_desc[NR_IRQS];//NR_IRQS表示中断源的数目。

structirq_desc_t{

init flags;

struct irqaction *action;

};

(2) irq_desc是一个指向irq_desc_t结构的数组,irq_desc_t结构是各个设备中断服务例程的描述符。Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义如下:

/* include/linux/interrupt.h */

struct irqaction {

irq_handler_t handler; /* 指向中断服务程序 */

unsigned long flags; /* 中断标志 */

unsigned long mask; /* 中断掩码 */

const char *name; /* I/O设备名

void *dev_id; /* 设备标识 */

struct irqaction *next; /* 指向下一个描述符 */

int irq; /* IRQ线 */

struct proc_dir_entry *dir; /* 指向IRQn相关的/proc/irq/n目录的描述符 */

};

其中关键的handler成员指向了该设备的中断服务程序,由执行request_irq时建立。

(3) 在驱动程序初始化时,若使用到中断,通常调用函数request_irq()建立该驱动程序对应的irqaction结构体,并把它登记到irq_desc [irq_num]->action链表中。Iqr_num为驱动程序申请的中断号。

request_irq()函数的原型如下:

/* kernel/irq/manage.c */

int request_irq(unsigned int irq,

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

unsigned long irqflags,

const char *devname,

void *dev_id);

参数irq是设备中断求号,在向irq_desc[]数组登记时,它做为数组的下标。把中断号为irq的irqaction结构体的首地址写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。

这样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。流程如上图所示。

四. 关于共享中断

共享中断的不同设备的iqraction结构体都会添加进该中断号对应的irq_desc结构体的action成员所指向的irqaction链表内。当内核发生中断时,它会依次调用该链表内所有的handler函数。因此,若驱动程序需要使用共享中断机制,其中断处理函数必须有能力识别是否是自己的硬件产生了中断。通常是通过读取该硬件设备提供的中断flag标志位进行判断。

当使用内核共享中断时,request_irq必须要提供dev_id参数,并且dev_id的值必须唯一。那么这里提供唯一的dev_id值的究竟是做什么用的?

内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即 irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。

那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那request_irq的dev_id参数究竟是做什么用的?

(1)在中断处理程序释放时使用到dev_id,当调用free_irq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中删除指定的一个。如果没有这个参数,那么kernel不可能知道给定的中断线上到底要删除哪一个处理程序。

(2)将使用该中断处理程序的设备结构体传递给该中断处理程序,首先看两个基础条件:

1) 内核中的各个设备结构体肯定是唯一的,因此满足dev_id唯一这个要求

2) dev_id参数会在发生中断时传递给该中断的服务程序。

典型的中断服务程序定义如下:

static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);

即request_irq的dev_id参数会传递给该中断服务程序的dev_id。因此也可以将驱动程序的设备结构体通过dev_id传递给中断服务程序。这样做作用主要有两个:

1) 中断服务程序可能使用到设备结构体中的信息,因此,我们可以通过将设备结构传递给request_irq的dev_id参数这种机制,在中断处理函数中使用该设备结构体,这是大部分驱动程序通用的一种手法。

2)前面我们讲了若使用共享中断,那么中断处理函数自身需要能识别是否是自己的设备产生了中断。通常这是通过读取该硬件设备提供的中断flag标志位进行判断的。

而往往驱动程序里定义的设备结构体通常包含了该设备的IO地址,加上偏移就可以算出中断状态寄存器及中断flag标志位的IO地址信息。因此将设备结构体通过dev_id传递给设备中断处理程序的另一个作用就是使用共享中断时,可以在中断处理函数内通过读取该设备结构 (dev_id) 中提供的中断Flag标志位地址信息进行判断,是否是该设备产生了中断,然后再进一步判断是否继续往下执行还是跳到下一个 irqaction->handler函数再判断执行。 当然,如果本来就知道该设备中断状态寄存器的IO地址,也可以选择直接readl该地址信息进行判断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: