linux 中断管理(一)

这一生的挚爱 提交于 2020-01-25 16:46:02

一、中断作用

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链表头,用户注册的处理函数都链接到这个链表里面,发生中断的时候,就会从之里面调用用户注册进来的中断服务函数
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!