Linux中断处理机制

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

一:中断概述


中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。


中断类型:


同步中断由CPU本身产生,又称为内部中断。这里同步是指中断请求信号与代码指令之间的同步执行,在一条指令执行完毕后,CPU才能进行中断,不能在执行期间。所以也称为异常(exception)。


异步中断是由外部硬件设备产生,又称为外部中断,与同步中断相反,异步中断可在任何时间产生,包括指令执行期间,所以也被称为中断(interrupt)。


异常又可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。而中断可分为故障(fault)、陷阱(trap)、终止(abort)三类。


从广义上讲,中断又可分为四类:中断、故障、陷阱、终止。


20200305203601427.png


二:中断处理流程


1. 中断处理流程


 当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把中断号irq传进来。根据中断号,找到中断号对应的irq_desc结构(irq_desc结构为内核中中断的描述结构,内核中有一个irq_desc结构的数组irq_desc_ptrs[NR_IRQS]),然后调用irq_desc中的handle_irq函数,即中断入口函数。我们编写中断的驱动,即填充并注册irq_desc结构。


 2. 中断处理数据结构:irq_desc


 Linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断源(也可能是一组中断源),记录中断入口函数、中断标记,并提供了中断的底层硬件访问函数(中断清除、屏蔽、使能)。另外通过这个结构体数组项中的action,能够找到用户注册的中断处理函数。


struct irq_desc {
    unsigned int        irq;
    irq_flow_handler_t    handle_irq;
    struct irq_chip        *chip;
    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;        /* IRQ status */
    unsigned int        depth;        /* nested irq disables */
    unsigned int        wake_depth;    /* nested wake enables */
    unsigned int        irq_count;    /* For detecting broken IRQs */
    unsigned long        last_unhandled;    /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;
    spinlock_t        lock;
    const char        *name;
} ____cacheline_internodealigned_in_smp;


(1)handle_irq:中断的入口函数

(2)chip:包含这个中断的清除、屏蔽、使能等底层函数


struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);
    void        (*shutdown)(unsigned int irq);
    void        (*enable)(unsigned int irq);
    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);
    void        (*eoi)(unsigned int irq);
    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq,
                    const struct cpumask *dest);
    int        (*retrigger)(unsigned int irq);
    int        (*set_type)(unsigned int irq, unsigned int flow_type);
    int        (*set_wake)(unsigned int irq, unsigned int on);
    /* Currently used only by UML, might disappear one day.*/
  #ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
  #endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */
    const char    *typename;
};


(3)action:记录用户注册的中断处理函数、中断标志等内容


struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;
    const char *name;
    void *dev_id;
    struct irqaction *next;
    int irq;
    struct proc_dir_entry *dir;
};


三. 中断处理流程总结


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

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

(3)asm_do_IRQ()根据中断号调用irq_des[NR_IRQS]数组中的对应数组项中的handle_irq();

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

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


 可见,中断体系结构的初始化,就是构造irq_desc[NR_IRQS]这个数据结构;用户注册中断就是构造action链表;用户卸载中断就是从action链表中去除对应的项。


四. Linux操作系统中断初始化


(1)init_IRQ()函数用来初始化中断体系结构,代码位于arch/arm/kernel/irq.c


void __init init_IRQ(void)
{
    int irq;
        for (irq = 0; irq < NR_IRQS; irq++)
        irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
  #ifdef CONFIG_SMP
    bad_irq_desc.affinity = CPU_MASK_ALL;
    bad_irq_desc.cpu = smp_processor_id();
    #endif
    init_arch_irq();
}


(2)init_arch_irq()函数,就是用来初始化irq_desc[NR_IRQS]的,与硬件平台紧密相关。init_arch_irq其实是一个函数指针,我们移植Linux内核时,以S3C2440平台为例,把init_arch_irq指向函数s3c24xx_init_irq()。


(3)s3c24xx_init_irq()函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有的中断设置了芯片相关的数据结构irq_desc[irq].chip,设置了处理函数入口irq_desc[irq].handle_irq。


(4)以外部中断EINT0为例:


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);
}


① set_irq_chip()的作用就是"irq_desc[irqno].chip = &s3c_irq_eint0t4",s3c_irq_eint0t4为系统提供了一套操作EINT0~EINT4的中断底层函数集,内容如下


static struct irq_chip s3c_irq_eint0t4 = {
    .name        = "s3c-ext0",
    .ack        = s3c_irq_ack,
    .mask        = s3c_irq_mask,
    .unmask        = s3c_irq_unmask,
    .set_wake    = s3c_irq_wake,
    .set_type    = s3c_irqext_type,
};


② set_irq_handler()函数的作用就是“irq_desc[irqno].handle_irq = handle_edge_irq”。发生中断后,asm_do_IRQ()函数会调用中断入口函数handle_edge_irq(),而handle_edge_irq()函数会调用用户注册的处理函数(即irq_desc[irqno].action)。


五. 用户注册中断时带来的中断初始化


(1)用户(驱动程序)通过request_irq()函数向内核注册中断处理函数,request_irq()函数根据中断号找到数组irq_desc[irqno]对应的数组项,然后在它的action链表中添加一个action表项。该函数定义于:kernel/irq/manage.c


(2) request_irq()函数首先使用4个参数构造一个irqaction结构,然后调用__setup_irq函数将它链入链表中,


(3) __setup_irq()函数主要完成功能如下


① 将新建的irqaciton结构链入irq_desc[irq]结构体的action链表中

 * 如果action链表为空,则直接链入

 * 如果非空,则要判断新建的irqaciton结构和链表中的irqaciton结构所表示的中断类型是否一致:即是都声明为“可共享的”,是否都是用相同的触发方式,如果一致,则将新建的irqaciton结构链入


② 设置中断的触发方式;


③ 启动中断


六:卸载中断


卸载中断使用函数free_irq()函数,该函数定义在kernel/irq/manage.c中,需要用到的两个参数irq、dev_id。通过参数irq可以定位到action链表,再使用dev_id在链表中找到要卸载的表项(共享中断的情况)。如果它是唯一表项,那么删除中断,还需要调用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()来关闭中断


七: Linux中断处理流程分析

① 中断总入口函数:asm_do_IRQ() (定义在:arch/arm/kernel/irq.c)


② generic_handle_irq()会调用相应中断号描述结构的handle_irq,等价于irq_desc[irq].handle_irq(irq, desc)


③ 普通中断流程(以EINT0为例)


(1)irq_desc[IRQ_EINT0].handle_irq函数指针指向handle_edge_irq()(定义在:kernel/irq/chip.c),用来处理边沿触发的中断


void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    kstat_cpu(cpu).irqs[irq]++;
    /* Start handling the irq */
    desc->chip->ack(irq);
    /* Mark the IRQ currently in progress.*/
    desc->status |= IRQ_INPROGRESS;
    action_ret = handle_IRQ_event(irq, action);
}


(2)通过函数调用desc->chip->ack(irq)来响应中断,实际上就是清除中断以使得可以接受下一个中断,有了之前数据结构初始化的前提了解,可以知道实际上执行的就是s3c_irq_eint0t4.ack函数


(3)handle_IRQ_event函数逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义,关键代码如下:


irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    do {
        ret = action->handler(irq, action->dev_id);
         if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
            action = action->next;
    } while (action);
}


(4)用户通过函数request_irq()函数注册中断处理函数时候,传入参数irq和dev_id,在这里这两个参数被用户注册的中断处理函数action->handler()所使用。可见用户可以在注册中断处理函数的时候,指定参数dev_id,然后将来再由注册的中断处理函数使用这个参数。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
4天前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
3月前
|
存储 Linux C语言
Linux:冯·诺依曼结构 & OS管理机制
Linux:冯·诺依曼结构 & OS管理机制
138 0
|
1月前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。
|
1月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
2月前
|
存储 缓存 Linux
Linux VFS机制详解
Linux VFS机制详解
48 1
|
2月前
|
Linux
Linux异步io机制 io_uring
Linux异步io机制 io_uring
33 1
|
1月前
|
NoSQL 算法 Linux
【内附完整redis配置文件】linux服务器命令设置redis最大限制内存大小,设置redis内存回收机制,redis有哪些回收机制
【内附完整redis配置文件】linux服务器命令设置redis最大限制内存大小,设置redis内存回收机制,redis有哪些回收机制
37 0
|
3月前
|
Linux 数据库
linux守护进程介绍 | Linux的热拔插UDEV机制
linux守护进程介绍 | Linux的热拔插UDEV机制
linux守护进程介绍 | Linux的热拔插UDEV机制
|
3月前
|
安全 Linux 调度
xenomai+linux双内核下的时钟管理机制
clock是操作系统正常运行的发动机,系统利用时钟中断维持系统时间、促使任务调度,以保证所有进程共享CPU资源;可以说,“时钟中断”是整个操作系统的脉搏。那你是否好奇xenomai cobalt内核和Linux内核双内核共存的情况下,时间子系统是如何工作的?一个硬件时钟如何为两个操作系统提供服务的?本文将揭开xenomai双核系统时间机制
78 0
xenomai+linux双内核下的时钟管理机制
|
3月前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【5月更文挑战第29天】 在现代操作系统中,尤其是类Unix系统如Linux中,进程调度机制是保证多任务高效运行的核心。本文将深入探讨Linux操作系统内核的进程调度器——负责管理CPU资源分配的关键组件。我们会详细分析其调度策略、调度器的演进及其在多核处理器环境下的表现。通过剖析进程调度器的工作原理和设计哲学,旨在为读者提供一个清晰的视角来理解这一复杂的系统功能。
100 0