linux 中断管理

Posted 古澜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 中断管理相关的知识,希望对你有一定的参考价值。

一、中断作用

Linux 内核需要对连接到计算机上的所有硬件设备进行管理。如果要管理这些设备,首先得和它们互相通信才行。
一般有两种方案可实现这种功能:

  • 轮询(polling) 让内核定期对设备的状态进行查询,然后做出相应的处理;
  • 中断(interrupt) 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

使用轮询的方式会占用CPU比较多的时间,效率极低。例如:要读取一个按键有没有被按下时,一个进程需要不断地查询按键有没有被按下。这样这个任务就占用CPU大量得时间,使得CPU做了大量的无用功。使用中断提供这样的一个机制。当按键没有被按下的时候,挂起当前进程,将控制权转交给其他进程。当按键按下的时候,操作系统把当前进程设为活动的,从而允许该进程继续执行。

二、linux中断管理

linux 内核将所有的中断统一编号,使用一个 irq_desc 结构体数组描述中断。一个数组项对用一个中断(或者是一组中断,它们共用中断号)。
struct irq_desc 结构体记录了,中断的名称、中断状态,底层硬件访问接口(使能中断,屏蔽中断,清除中断),中断处理函数的入口,
通过它可以调用用户注册的中断处理函数。

1、struct irq_desc

struct irq_descinclude\\linux\\irq.h 文件里面定义

struct irq_desc {
	irq_flow_handler_t	handle_irq;    /* 当前中断的处理函数入口 */
	struct irq_chip		*chip;         /* 底层硬件访问 */
        ...
	struct irqaction	*action;	/* 用户注册的中断处理函数链表 */
	unsigned int		status;		/* IRQ状态 */

	...

	const char		*name;         /* 中断函数名 */
} ____cacheline_internodealigned_in_smp;

a. handle_irq

handle_irq 是这个或者是这组的中断的处理函数入口。发生中断时,会调用asm_do_IRQ函数。在这个函数里面根据中断号调用相应irq_desc数组项的handle_irq
handle_irq里面会使用chip成员的接口来使能、屏蔽、清除中断。还会一一调用用户注册在action链表里面的处理函数。

b. struct irq_chip

struct irq_chip 在 include\\linux\\irq.h 文件里面定义

struct irq_chip {
    const char	*name;
    /* 启动中断,如果不设置则缺省为 "enable" */
    unsigned int	(*startup)(unsigned int irq);       
    /* 关闭中断,如果不设置则缺省为 "disable" */
    void		(*shutdown)(unsigned int irq);      
    /* 使能中断,如果不设置则缺省为"unmask" */  
    void		(*enable)(unsigned int irq);
    /* 禁止中断,如果不设置则缺省为"mask" */  
    void		(*disable)(unsigned int irq);
    /* 响应中断,一般是清除当前的中断,使得可以接收下一个中断 */
    void		(*ack)(unsigned int irq);
    /* 屏蔽中断源 */
    void		(*mask)(unsigned int irq);
    /* 屏蔽和响应中断 */
    void		(*mask_ack)(unsigned int irq);
    /* 开启中断 */
    void		(*unmask)(unsigned int irq);
  ....
};

c. struct irqaction

struct irqaction 结构体在 include\\linux\\interrupt.h 文件里面定义。
用户注册的每个中断处理函数都用一个 irqaction 结构体来描述一个中断(例如共享中断)可以有多个处理函数。
它们的irqaction结构以action 为表头链成一个链表

struct irqaction {
    /* 用户注册的中断处理函数 */
    irq_handler_t handler;
    /* 中断的标志,是否是共享中断,中断的触发方式是电平触发,还是边沿触发 */
    unsigned long flags;

    cpumask_t mask;
    /* 用户注册时,给的中断的名字 */
    const char *name;
    /* handler 中断函数的参数,也可以用来区分共享中断 */
    void *dev_id;
    /* 链表的指针 */
    struct irqaction *next;
    /* 中断号 */
    int irq;
    struct proc_dir_entry *dir;
};

2. 小结

对于 struct irq_desc数组 、 struct irq_chip结构体 和 struct irqaction 三者之间的关系,如下图:

三、中断处理初始化

1、中断处理初始化

init\\Main.c 文件的 start_kernel 函数里面调用的init_IRQ() 函数就是中断体系结构的初始化

2、init_IRQ 函数

init_IRQ 用来初始化中断处理体系结构, 在arch\\arm\\kernel\\irq.c文件里面

void __init init_IRQ(void)
{
	int irq;
        /* 初始化irq_desc[] 每一项的中断状态 */
	for (irq = 0; irq < NR_IRQS; irq++)
		irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    ...
        /* 架构相关的中断初始化函数 */
	init_arch_irq();
}

3、 init_arch_irq

init_arch_irq 是一个函数指针,arch\\arm\\kernel\\setup.c 文件 setup_arch() 函数被初始化

void __init setup_arch(char **cmdline_p)
{
    ...
    init_arch_irq = mdesc->init_irq;
    ...
}

mdesc->init_irq 指向的是 arch\\arm\\plat-s3c24xx\\irq.c 文件的s3c24xx_init_irq()函数。
MACHINE_START(S3C2440, "SMDK2440") 是一个宏,用来定义 struct machine_desc 结构体
结构体在 arch\\arm\\mach-s3c2440\\Mach-smdk2440.c文件里面定义并且初始化 init_irq指向s3c24xx_init_irq()函数

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <ben@fluff.org> */
	.phys_io	= S3C2410_PA_UART,
	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S3C2410_SDRAM_PA + 0x100,
         /* init_irq成员在这里初始化 */
	.init_irq	= s3c24xx_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END

4、s3c24xx_init_irq函数

s3c24xx_init_irq()函数在arch\\arm\\plat-s3c24xx\\irq.c定义(部分代码如下面的代码块),他为所有的所有与芯片操作相关的数据结构(irq_desc[irq].chip),,并且初始化了处理函数入口(irq_desc[irq].handle_irq)。
IRQ_EINT0IRQ_EINT3 为例,set_irq_chip函数就是将irq_desc[irqno].chip = &s3c_irq_eint0t4,以后就可以通过irq_desc[irqno].chip结构的函数指针来设置触方式,使能中断、屏蔽中断了
set_irq_handler函数就是设置中断处理函数入口 将irq_desc[irqno].handle_irq = handle_edge_irq 发生中断时,handle_edge_irq会调用用户具体注册的处理函数
irq_desc[irqno].falgs设置为IRQF_VALID

void __init s3c24xx_init_irq(void)
{
    /* 中断号是组的初始化 */
    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
    ...
    /* external interrupts */
    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
        irqdbf("registering irq %d (extended s3c irq)\\n", irqno);
        set_irq_chip(irqno, &s3c_irqext_chip);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    ...
    
}

5、set_irq_chip函数

int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
	struct irq_desc *desc;
	unsigned long flags;
        /* 判断是否超过最大的中断号 */
	if (irq >= NR_IRQS) {
		printk(KERN_ERR "Trying to install chip for IRQ%d\\n", irq);
		WARN_ON(1);
		return -EINVAL;
	}
         
	if (!chip)
		chip = &no_irq_chip;
        /* 通过中断号找到irq_desc数组对应的数组项 */
	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);
        /* 判断 chip 的成员即&s3c_irq_eint0t4的成员是否为空,如果为空就设置为默认的操作函数 */
	irq_chip_set_defaults(chip);
        /* 设置irq_desc[].chip成员 */
	desc->chip = chip;
	spin_unlock_irqrestore(&desc->lock, flags);
	return 0;

5、set_irq_handler函数

set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__set_irq_handler(irq, handle, 0, NULL);
}


/***************************************************************************************/


__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
    struct irq_desc *desc;
    unsigned long flags;
    /* 通过中断号找到irq_desc数组对应的数组项 */
    desc = irq_desc + irq;
   
   ...
    /*  中间还会做一些判断 */
   ...
    /* 设置中断处理函数,名字*/
    desc->handle_irq = handle;
    desc->name = name;
    /* 设置中断的状态,开启中断 */
    if (handle != handle_bad_irq && is_chained) {
	desc->status &= ~IRQ_DISABLED;
	desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
	desc->depth = 0;
	desc->chip->unmask(irq);
    }

}

6、set_irq_flags函数

void set_irq_flags(unsigned int irq, unsigned int iflags)
{
	struct irq_desc *desc;
	unsigned long flags;

	if (irq >= NR_IRQS) {
		printk(KERN_ERR "Trying to set irq flags for IRQ%d\\n", irq);
		return;
	}
        /* 找到数组项 */
	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);

       /* 设置中断状态 */
	desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
	if (iflags & IRQF_VALID)
		desc->status &= ~IRQ_NOREQUEST;
	if (iflags & IRQF_PROBE)
		desc->status &= ~IRQ_NOPROBE;
	if (!(iflags & IRQF_NOAUTOEN))
		desc->status &= ~IRQ_NOAUTOEN;
	spin_unlock_irqrestore(&desc->lock, flags);
}

四、总结

中断处理体系结构的初始化的过程其实就是对irq_desc[]数组的每一项初始化进行初始化.
一个中断或者一组中断通过irq_desc[]的一个数组项来管理。
数组项里面的 handle_irq chip action 三个重要的结构体成员。

  • handle_irq是当前的中断处理函数
  • chip底层硬件相关的处理函数(设置中断的触发方式,屏蔽中断,使能中断)
  • action链表头,用户注册的处理函数都链接到这个链表里面,发生中断的时候,就会从之里面调用用户注册进来的中断服务函数

以上是关于linux 中断管理的主要内容,如果未能解决你的问题,请参考以下文章

linux 中断管理

linux 中断管理

Linux 内核Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )

Linux内存管理 (10)缺页中断处理

Linux的中断

linux设备树-中断控制器驱动