Linux 中断学习之小试牛刀篇

Linux中断内核编程

前言

在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程

一.内核中断程序

我们还是来看一看成程序:

在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法。如果不熟悉的话请大家看,module_param()的学习Linux内核模块编程 ,在此不作解释。

1.程序interrupt.c

2.Makefile的编写

(程序的调试,加载和运行,在此不进行说明)

3.首先我们来分析下内核加载模块

在内核加载模块中最重要的的action就是注册中断处理程序。很明显,这一动作是通过request_irq()函数来完成的。

int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags, const char *devname, void *dev_id)

A.先来分析形参:

第一个参数irq: 表示要分配的中断号。对于一些设备(系统时钟或键盘)它的值是预先固定的,而对于大多数设备来说,这个值是动态确定的。

第二个参数 handler: 表示要挂入到中断请求对列中的中断服务例程, 这个中断服务函数的原型是static irqreturn_t handler(int , void *);

中断处理程序的前缀为static,因为它从来不会被别的文件中的代码直接调用。

第三个参数flags:为标志位。可以取IRQF_DISABLED、IRQF_SHARED和IRQF_SAMPLE_RANDOM之一。在本实例程序中取 IRQF_SHARED,该标志表示多个中断处理程序共享irq中断线。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取 IRQF_DISABLED标志,则在执行该中断服务程序时会屏蔽所有其他的中断。取IRQF_SAMPLE_RANDOM则表示设备可以被看做是事件随见的发生源。

以下是官方解释:


第四个参数devname:是请求中断的设备的名称。当你加载模块成功后可以在/proc/interrupts中查看到具体设备的名称,与此同时也可以看到这个设备对应的中断号以及请求次数。

第五个参数dev_id:为一个指针型变量。注意该参数为void型,也就是说通过强制转换可以转换为任意类型。dev_id主要用于共享中断线,对每个注册的中断处理程序来说,( Dev_id must be globally unique. Normally the address of the device data structure is used as the cookie.)dev_id参数必须唯一(指向任一设备结构的指针就可以满足此要求,选择设备结构因为它是唯一的,而且中断处理程序可能会用到它)如果无需共享中断线,则将该参数赋值为NULL。

B:函数返回值

requset_irq()函数成功执行后返回0。如果返回非0值,就表示错误发生。此时,指定的中断处理程序不会被注册。

这里面有几个疑问:

为什么要注册中断函数

共享中断线的概念,参数dev_id的作用是什么

看一个图进行说明 :


Linux 中断学习之小试牛刀篇

1>由图可知:有16个中断线。要使用中断线,就要进行中断线的 申请 ,也常把申请一条中断线称为申请一个中断号,这就 与request_irq()函数中的第一个形参 irq 有关系

2>Linux有256个中断向量,而外部中中断向量只有16个(32~47)。由于硬件上的限制,很多外部设备不得不共享中断线。

(例如:一些PC机所用的网卡和图形卡可以把它们分配到一条中断线上)

让每个中断源独自占用一条中断线是不实现的。

3>共享中断线的话虽然解决了中断资源的问题,但是,此时引出了另一个问题( 任何事物都有其两面性 ),此时仅仅用中断描述符并不能提供中断产生的所有信息。为了解决这个问题,内核必须对中断线给出近一步的描述,所以在Linux设计中,为每个中断请求IRQ设置了一个专用队列(中断请求队列)
4>中断服例程序和中断处理程序的区别:
a.中断服务例程(interrupt service routine):

Linux中,15条中断线对应15个中断处理程序,依次命名是IRQ0x00_interrupt(),IRQ0x01_interrupt()..... IRQ0X1f_interrupt().

中断处理程序相当于某个中断向量的总处理程序。
eg:IRQ0X05_interupt()是5号中断(向量为37)的总处理程序。

b.中断服务例程是针对一个具体设备的中断。
5>.注册中断服务例程:
在IDT表完成初始化时,每个中断服务队列还为空。此时即使打开中断且某个外设的中断真的发生了,也得不到实际的服务。因为CPU虽然通过中断门进入了某个中断向量的总处理程序。但是,具体的中断服务例程还没有挂入中断请求队列。所以,在设备驱动程序的初始化阶段,必须通过request_irq()函数将响应的中断服务例程挂入中断请求队列,也就是进行注册。

6>分析一下中断服务程序,即request_irq()函数中第二个参数所对应的函数

static irqreturn_t myirq_handler(int irq,void *dev_id)
{

printk("ISR is Working/n");
return IRQ_HANDLED;

}

中断服务例程的形参:

a.int irq :中断号。
b.void *dev_id :与request_irq()的参数dev_id一致,可以根据这个设备id号得到相应设备的数据结构,进而得到相应设备的信息和相关数据。
c.返回值:中断程序的返回值是一个特殊类型 rqreturn_t。但是中断程序的返回值却只有两个值IRQ_NONE和IRQ_HANDLED。
IRQ_NONE:中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号。
IRQ_HANDLED:接收到了准确的中断信号,并且作了相应正确的处理。

一般 中断处理程序要做什么service,主要取决于产生的设备和该设备为什么要发送中断。

John哥说明:

1.当一个给定的中断处理程序正在执行时,这条中断线上的其它中断都会被屏蔽。but,所有其他中断线上的中断都是打开的。因此这些不同中断线上的其他中断都能被处理。

2.request_irq()函数可能会睡眠,所以,不能在中断上下文或其它不允许阻塞的代码中调用该函数。

4.在深入分析request_irq()函数之前,先来看几个重要的数据结构。

A.irqaction的数据结构(用irqaction结构体来描述一个具体的中断服务例程)

1>handler:指向具体的一个中断服务例程。

2>flags:表示中断标志位,对应于request_irq()函数中所传递的第三个参数,可取IRQF_DISABLED、IRQF_SAMPLE_RANDOM和IRQF_SHARED其中之一。

3>name:请求中断的设备名称,对应request_irq()函数中所传递的第四个参数

4>dev_id: 共享中断时有用。 对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。

5>strct irqaction *next:指向irqaction描述符的下一个元素。用一条链表将共享同一条中断线上的中断服务例程链接起来。

6>irq:所申请的中断号

7>dir:指向proc/irq/NN/name entry

8>thread_fn:指向具体的一个线程化的中断。

9>thread:指向线程中断的指针。

10>thread_flags:线程中断的标志。

B.irq_desc的数据结构体

每个中断向量都有它自己的irq_desc 描述符。即用irq_desc来描述中断向量。所有的这些中断描述符组织在一起就形成了irq_desc irq_desc[NR_IRQS]数组

1>irq:表示这个描述符所对应的中断号。

2>handle_irq:指向该IRQ线的公共服务程序(即该IRQ所对应的中断处理程序。

3>chip:它是一个struct irq_chip类型的指针,是中断控制器的描述符 。在2.6以前的版本中它是hw_irq_controller。
4>handler_data:是handler_irq的参数。
5>chip_data:是指向irq_chip的指针。
6>atcion:一个struct irqaction类型的指针,它指向一个单链表。该链表是由该中断线上所有中断服务例程链接起来的。
7>status:表示中断线当前的状态。
8>depth:中断线被激活时,值为0;当值为正数时,表示被禁止的次数。
9>irq_count:表示该中断线上发生中断的次数
10>irqs_unhandled:该IRQ线上未处理中断发生的次数
11>name:申请中断设备的名字。

C.struct irq_chip结构体:

struct irq_chip是一个中断控制器的描述符。Linux支持N种可编程中断控制器PIC(中断控制器),通常不同的体系结构就有一套自己的中断处理方式。内核为了统一的处理中断,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。

struct irq_chip的具体代码如下:


name:中断控制器的名字;
Startup:启动中断线;
Shutdown:关闭中断线;
Enable:允许中断;
Disable:禁止中断;

分析了struct irq_desc,struct irq_chip和irqaction的数据结构之后我们来看看他们之间的关系

Linux 中断学习之小试牛刀篇

现在深入分析request_irq()内部是如何实现的。

可以看到request_irq()函数里面有封装了request_threaded_irq(irq, handler, NULL, flags, name, dev)函数。

先看一下官方的解释

5.首先分析request_threaded_irq()函数中的各个形参
1>:irq:表示申请的中断号。
2>:handler:表示中断服务例程
3.> thread_fn:中断线程化,此处传递的是NULL。NULL表示没有中断线程化。
此参数是最新版本中才出现的。为什么要提出中断线程化?
在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断
处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不
到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以
有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍
有实时性保证。but,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器
等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当
被线程化。

4>.irqflags:表示中断标志位。
5>.devname:表示请求中断的设备的名称。

6>.dev_id: 对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。 共享中断时所用。

现在继续迭代深入 request_threaded_irq()内部是如何实现的。

程序的第一行和第二行分别定义了:

(1) struct irqaction *action;

(2)2struct irq_desc *desc;

两个指针action和desc,它们分别指向了结构体irqaction和 irq_desc。

(3) if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;

作用是:判断中断标志位,如果是共享中断的话就必须要有一个唯一的dev_id,否则返回一个错误。

(4) desc = irq_to_desc(irq);

irq_to_desc(irq):根据中断号irq在 irq_desc[NR_IRQS]数组中 返回一个具体的irq_desc。即根据irq找到它的中断处理程序。

(5) if (!desc)

return -EINVAL;

当返回一个空值时返回一个错误。说明申请中断号失败。

(6)if (desc->status & IRQ_NOREQUEST)
return -EINVAL;

判断中断线的状态,若为IRQ_NOREQUEST时( IRQ_NOREQUEST表示 IRQ 不能被申请)

(7) if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}

判断中断服务例程是否为空,如果handler为空,则判断线程中断服务例程,若线程中断服务例程也为空,则返回一个错误值。否则中断服务例程指向: rq_default_primary_handler。

(8)

1079 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1080 if (!action)
1081 return -ENOMEM;
1082
1083 action->handler = handler;
1084 action->thread_fn = thread_fn;
1085 action->flags = irqflags;
1086 action->name = devname;
1087 action->dev_id = dev_id;

从1079~1087:根据requst_irq()函数中传递的参数生成一个irqaction.

1097 if (!retval && (irqflags & IRQF_SHARED)) {
1098 /*
1099 * It's a shared IRQ -- the driver ought to be prepared for it
1100 * to happen immediately, so let's make sure....
1101 * We disable the irq to make sure that a 'real' IRQ doesn't
1102 * run in parallel with our fake.
1103 */
1104 unsigned long flags;
1105
1106 disable_irq(irq);
1107 local_irq_save(flags);
1108
1109 handler(irq, dev_id);
1110
1111 local_irq_restore(flags);
1112 enable_irq(irq);
1113 }

1097~1113:如果为共享中断的话,在执行中断服务例程之前,要先把这条中断线上的中断屏蔽,让后在执行,执行完之后打开中断。

6.有注册中断服务函数,那必然有相应的释放中断函数。

可以调用void free_irq(unsigned int irq, void *dev_id)来释放我们申请的中断线。

函数形参:

1>unsigned int riq:表示申请的中断号与request_irq()函数中的第一个形参对应。

2>void *dev_id:与request_irq()函数中的最后一个形参含义和用法相同,在此不再说明。

函数功能:

如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本省只有在删除了最后一个处理程序时才会被禁止。

切记:This function must not be called from interrupt context

freee_irq()函数不能在中断上下文中被调用。

3>深入分析下free_irq()函数内部是如何实现的

可以看到free_irq()函数了封装了_free_irq(irq,dev_id)函数。

free_irq()调用_free_irq()把每一个具体的中断服务例程()释放。

你可能感兴趣的:(linux)