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

Linux中断处理体系结构分析(二)

2017-03-29 13:39 543 查看
1.中断处理的体系结构

   我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分。在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top
half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的
情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》的第七章的内容。
    Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
   通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h
中定义,
struct irq_desc
{

    unsigned int        irq;

    struct timer_rand_state
*timer_rand_state;

    unsigned int
*kstat_irqs;
#ifdef CONFIG_INTR_REMAP

    struct irq_2_iommu
*irq_2_iommu;
#endif

    irq_flow_handler_t    handle_irq; // 当前中断的处理函数入口

    struct irq_chip        *chip;
//低层的硬件访问

    struct msi_desc        *msi_desc;

    void            *handler_data;

    void            *chip_data;

    struct irqaction    *action;    // 用户提供的中断处理函数链表

    unsigned int        status;        //IRQ状态

                ........

    const char        *name;
//中断的名称

} ____cacheline_internodealigned_in_smp;

   handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中handle_irq.handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。

   irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。
struct irq_chip
{

    const char    *name;

   
12f3e
 unsigned int    (*startup)(unsigned
int irq);//启动中断,如果不设置,缺省为“enable

    void        (*shutdown)(unsigned
int irq);/*关闭中断,如果不设置,缺省为"disable"*/

    void        (*enable)(unsigned
int irq);// 使用中断,如果不设置,缺省为"unmask"

    void        (*disable)(unsigned
int irq);//禁止中断,如果不设置,缺省为“mask”

    void        (*ack)(unsigned
int irq);/*响应中断,通常是清除当前中断使得可以接收下一个中断*/

    void        (*mask)(unsigned
int irq);
//屏蔽中断源

    void        (*mask_ack)(unsigned
int irq);//屏蔽和响应中断

    void        (*unmask)(unsigned
int irq);//开启中断源

    void        (*eoi)(unsigned
int irq);

    ........

    const char    *typename;
};

irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断
处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结
构链接成一个链表,以action为表头。irqation结构定义如下:
struct irqaction
{

    irq_handler_t handler;
//用户注册的中断处理函数

    unsigned long flags;
//中断标志,比如是否共享中断,电平触发还是边沿触发

    const char
*name; //用户注册的中断名字

    void *dev_id;
//用户传给上面的handler的参数,还可以用来区分共享中断

    struct irqaction *next;
//指向下一个用户注册函数的指针

    int irq;
//中断号

    struct proc_dir_entry
*dir;

    irq_handler_t thread_fn;

    struct task_struct
*thread;

    unsigned long thread_flags;
};

   irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:



中断处理流程如下

(1)发生中断时,CPU执行异常向量vector_irq的代码

(2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ

(3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。

(4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等

(5)handle_irq逐个调用用户在aciton链表中注册的处理函数

   中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。
2.中断处理体系结构的初始化
init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中
153
void __init init_IRQ(void)

154 {

155 int irq;

156

157 for (irq
= 0; irq < NR_IRQS; irq++)

158 irq_desc[irq].status
|= IRQ_NOREQUEST
| IRQ_NOPROBE;

159

160 init_arch_irq();

161 }

157~~158行 初始化irq_desc结构数组中每一项的中断状态

第160行调用架构相关的中断初始化函数。对于S3C2440开发板,这个函数就是s3c24xx_init_irq,移植machine_desc结构中的init_irq成员就指向这个函数s3c24xx_init_irq函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。以外部中断EINT4-EINT23为例,用来设置它们的代码如下:
void __init s3c24xx_init_irq(void)

534 {

535 unsigned long pend;

536 unsigned long last;

537 int irqno;

538 int i;

........
637
for (irqno = IRQ_EINT4; irqno
<= IRQ_EINT23; irqno++)
{

638 irqdbf("registering irq %d (extended s3c irq)\n", irqno);

639 set_irq_chip(irqno,
&s3c_irqext_chip);

640 set_irq_handler(irqno, handle_edge_irq);

641 set_irq_flags(irqno, IRQF_VALID);

...............
655 for
(irqno = IRQ_S3CUART_RX1; irqno
<= IRQ_S3CUART_ERR1; irqno++)
{

656 irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);

657 set_irq_chip(irqno,
&s3c_irq_uart1);

658 set_irq_handler(irqno, handle_level_irq);

659 set_irq_flags(irqno, IRQF_VALID);

660 }

..........

676 irqdbf("s3c2410: registered interrupt handlers\n");

677 }

678

    在639行set_irq_chip函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。

   在640行设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数; 在641行设置中断标志为“IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员都被设置        

  2.2 用户注册中断处理函数的过程
    用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。原先的内核中requset_irq函数在kernel/irq/manage.c中定义,而现在2.6.32版本中,进行了改变,可以看这篇文章http://eeek.borgchat.net/lists/newbies/msg39146.html ,这里解释了,在2.6.32内核中我们可以看到找不到了request_irq函数的实现,而是用request_threaded_irq()函数给替换了。我们可以在inclue/linux/interrupt.h中找到这个函数的原型。

110
#ifdef CONFIG_GENERIC_HARDIRQS

111 extern int __must_check

112 request_threaded_irq(unsigned
int irq, irq_handler_t handler,

113 irq_handler_t thread_fn,

114 unsigned long flags,
const char
*name, void
*dev);

115

116 static inline
int __must_check

117 request_irq(unsigned
int irq, irq_handler_t handler,
unsigned long flags,

118 const char
*name, void
*dev)

119 {

120 return request_threaded_irq(irq, handler,
NULL, flags, name, dev);

121 }

123 extern void exit_irq_thread(void);

124 #else

126 extern int __must_check

127 request_irq(unsigned
int irq, irq_handler_t handler,
unsigned long flags,

128 const char
*name, void
*dev);

136 static inline
int __must_check

137 request_threaded_irq(unsigned
int irq, irq_handler_t handler,

138 irq_handler_t thread_fn,

139 unsigned long flags,
const char
*name, void
*dev)

140 {

141 return request_irq(irq, handler, flags, name, dev);

142 }

143

144 static inline
void exit_irq_thread(void)
{ }

145 #endif

其实具体的实现在request_threaded_irq函数中,也是在/kernel/irq/manage.c中定义,requset_threaded_irq函数首先使用这4个参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表中,
1003
int request_threaded_irq(unsigned
int irq, irq_handler_t handler,

1004 irq_handler_t thread_fn,
unsigned long irqflags,
1005                         
const char *devname, void *dev_id)

.............

1056 action->handler
= handler;

1057 action->thread_fn
= thread_fn;

1058 action->flags
= irqflags;

1059 action->name
= devname;

1060 action->dev_id
= dev_id;

1061

1062 chip_bus_lock(irq, desc);

1084 local_irq_restore(flags);

1085 enable_irq(irq);

...........

1088 return retval;

1089 }

1090 EXPORT_SYMBOL(request_threaded_irq);

setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能

(1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。

如果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入

(2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数

chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义

296
void irq_chip_set_defaults(struct irq_chip
*chip)

297 {

298 if (!chip->enable)

299 chip->enable
= default_enable;//调用chip->unmask

300 if (!chip->disable)

301 chip->disable
= default_disable;//此函数为空

302 if (!chip->startup)

303 chip->startup
= default_startup;//调用chip->enable

310 if (!chip->shutdown)

311 chip->shutdown
= chip->disable
!= default_disable
?

312 chip->disable
: default_shutdown;

313 if (!chip->name)

314 chip->name
= chip->typename;

315 if (!chip->end)

316 chip->end
= dummy_irq_chip.end;

317 }

(4)启动中断

如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。

总结一下request_irq函数注册

(1)irq_des[irq]结构中的action链表中已经链入了用户注册的中断处理函数

(2)中断的触发方式已经被设好

(3)中断已经被使能
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: