linux中断源码分析 - 概述(一)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/

可编程中断控制器(PIC、APIC)

  为了方便说明,这里我们将PIC和APIC统称为中断控制器。中断控制器是作为中断(IRQ)和CPU核之间的一个桥梁而存在的,每个CPU内部都有一个自己的中断控制器,中断线并不是直接与CPU核相连,而是与CPU内部或外部的中断控制器相连。而为什么叫做可编程中断控制器,是因为其本身有一定的寄存器,CPU可以通过操作设置中断控制器屏蔽某个中断引脚的信号,实现硬件上的中断屏蔽。中断控制器也可以级联提供更多的中断线,具体如下:

  如上图,CPU的INTR与中断控制器的INT相连,INTA与ACK相连,当一个外部中断发生时(比如键盘中断IRQ1),中断控制器与CPU交互操作如下:

  1. IRQ1发生中断,主中断控制器接收到中断信号,检查中断屏蔽寄存器IRQ1是否被屏蔽,如果屏蔽则忽略此中断信号。
  2. 将中断控制器中的中断请求寄存器对应的IRQ1位置位,表示收到IRQ1中断。
  3. 中断控制器拉高INT引脚电平,告知CPU有中断发生。
  4. CPU每执行完一条指令时,都会检查INTR引脚是否被拉高,这里已被拉高。
  5. CPU检查EFLAGS寄存器的中断运行标志位IF是否为1,若为1,表明允许中断,通过INTA向中断控制器发出应答。
  6. 中断控制器接收到应答信号,将IRQ1的中断向量号发到数据总线上,此时CPU会通过数据总线读取IRQ1的中断向量号。
  7. 最后,如果中断控制器需要EOI(End of Interrupt)信号,CPU则会发送,否则中断控制器自动将INT拉低,并清除IRQ1对应的中断请求寄存器位。

  在linux内核中,用struct irq_chip结构体描述一个可编程中断控制器,它的整个结构和调度器中的调度类类似,里面定义了中断控制器的一些操作,如下:

struct irq_chip {
    /* 中断控制器的名字 */
    const char    *name;
    /* 控制器初始化函数 */
    unsigned int    (*irq_startup)(struct irq_data *data);
    /* 控制器关闭函数 */
    void        (*irq_shutdown)(struct irq_data *data);
    /* 使能irq操作,通常是直接调用irq_unmask(),通过data参数指明irq */
    void        (*irq_enable)(struct irq_data *data);
    /* 禁止irq操作,通常是直接调用irq_mask,严格意义上,他俩其实代表不同的意义,disable表示中断控制器根本就不响应该irq,而mask时,中断控制器可能响应该irq,只是不通知CPU */
    void        (*irq_disable)(struct irq_data *data);
    /* 用于CPU对该irq的回应,通常表示cpu希望要清除该irq的pending状态,准备接受下一个irq请求 */
    void        (*irq_ack)(struct irq_data *data);
    /* 屏蔽irq操作,通过data参数表明指定irq */
    void        (*irq_mask)(struct irq_data *data);
    /* 相当于irq_mask() + irq_ack() */
    void        (*irq_mask_ack)(struct irq_data *data);
    /* 取消屏蔽指定irq操作 */
    void        (*irq_unmask)(struct irq_data *data);
    /* 某些中断控制器需要在cpu处理完该irq后发出eoi信号 */
    void        (*irq_eoi)(struct irq_data *data);
    /*  用于设置该irq和cpu之间的亲和力,就是通知中断控制器,该irq发生时,那些cpu有权响应该irq */
    int        (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
    int        (*irq_retrigger)(struct irq_data *data);
    /* 设置irq的电气触发条件,例如 IRQ_TYPE_LEVEL_HIGH(电平触发) 或 IRQ_TYPE_EDGE_RISING(边缘触发) */
    int        (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
    /* 通知电源管理子系统,该irq是否可以用作系统的唤醒源 */
    int        (*irq_set_wake)(struct irq_data *data, unsigned int on);

    void        (*irq_bus_lock)(struct irq_data *data);
    void        (*irq_bus_sync_unlock)(struct irq_data *data);

    void        (*irq_cpu_online)(struct irq_data *data);
    void        (*irq_cpu_offline)(struct irq_data *data);

    void        (*irq_suspend)(struct irq_data *data);
    void        (*irq_resume)(struct irq_data *data);
    void        (*irq_pm_shutdown)(struct irq_data *data);

    void        (*irq_calc_mask)(struct irq_data *data);

    void        (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
    int        (*irq_request_resources)(struct irq_data *data);
    void        (*irq_release_resources)(struct irq_data *data);

    unsigned long    flags;
};

中断描述符表(IDT)

  在中断系统中有两个名字很相像的结构,就是中断描述符表中断描述符数组。这里我们先说说中断描述符表。  ?一个系统中的中断和异常加起来一共是256个,它们以向量的形式保存在中断描述符表中,每一个向量是8字节(整个表大小就是8 x 256=2048字节),其主要保存着权限位和向量对应的中断或异常处理程序的入口地址。而一般的,linux会将中断描述符表中的0~31用于非屏蔽中断和异常,其他的中断用于32~255之间。CPU把中断描述符表的向量类型分为三种类型:任务门、中断门、陷阱门。CPU为了防止恶意程序访问中断,限制了中断门的权限,而在某些时候,用户程序又必须使用中断,所以Linux把中断描述符的中断向量类型改为了5种:中断门,系统门,系统中断门,陷阱门,任务门

中断门

?用户程序不能访问的CPU中断门(权限字段为0),所有的中断处理程序都是这个,被限定在内核态执行。会清除IF标志,屏蔽可屏蔽中断

系统门

用户程序可以访问的CPU陷阱门(权限字段为3),在中断描述符表中有3个,向量号为4,5,128。我们的系统调用就是通过向量128系统门进入的。

系统中断门

能够被用户进程访问的CPU陷阱门(权限字段为3),只有一个,向量号为3,作为一个特别的异常处理所用。

陷阱门

用户进程不能访问的CPU陷阱门(权限字段为0),大部分异常处理程序入口都为陷阱门

任务门

用户进程不能访问的CPU任务门(权限字段为0),‘‘Double fault"异常处理程序入口。

  当我们发生异常或中断时,系统首先会判断权限字段(安全处理),权限通过则进入指定的处理函数,而所有的中断门的中断处理函数都是同一个,它首先是一段汇编代码,汇编代码操作如下:

  • 执行SAVE_ALL宏,保存中断向量号寄存器上下文至当前运行进程的内核栈或者硬中断请求栈(当内核栈大小为8K时保存在内核栈,若为4K,则保存在硬中断请求栈)。
  • 调用do_IRQ()函数。
  • 跳转到ret_from_intr,这是一段汇编代码,主要用于判断是否需要进行调度。

中断处理

  每个能够产生中断的设备或者模块都会在内核中注册一个中断服务例程(ISR),当产生中断时,中断处理程序会被执行,在中断处理程序中,首先会保存中断向量号和上下文,之后执行中断线对应的中断服务例程。对于CPU来说,中断线是非常宝贵的资源,而由于计算机的发展,外部设备数量和种类越来越多,导致了中断线资源不足的情况,linux为了应对这种情况,实现了两种中断线分配方式,分别是:共享中断线,中断线动态分配

共享中断线

  多个设备共用一条中断线,当此条中断线发生中断时,因为不可能预先知道哪个特定的设备产生了中断,因此,这条中断线上的每个中断服务例程都会被执行,以验证是哪个设备产生的中断(一般的,设备产生中断时,会标记自己的状态寄存器,中断服务例程通过检查每个设备的状态寄存器来查找产生中断的设备)。

中断线动态分配

  一条中断线在可能使用的时刻才与一个设备驱动程序关联起来,这样一来,即使几个硬件设备并不共享中断线,同一个中断向量也可以由这几个设备在不同时刻运行。

  共享中断线的分配方式是比较常见的,一次典型的基于共享中断线的中断处理流程如下:

  由于中断处于中断上下文中,所以在中断处理过程中,会有以下几个特性:

  • 中断处理程序正在运行时,CPU会通知中断控制器屏蔽产生此中断的中断线。此中断线发出的信号被暂时忽略,当中断处理程序结束时恢复此中断线。
  • 在中断服务例程的设计中,原则上是立即处理紧急的操作,将非紧急的操作延后处理(交给软中断进行处理)。
  • 中断处理程序是运行在中断上下文,但是其是代表进程运行的,因此它所代表的进行必须处于TASK_RUNNING状态,否则可能出现僵死情况,因此在中断处理程序中不能执行任何阻塞过程。

中断描述符

  中断描述符用于描述IRQ线的属性与状态,每个IRQ都有它自己的中断描述符,这些中断描述符用一个数组保存, 这个数组就是中断描述符数组,整个中断描述符数组长度为NR_IRQS(通常为224)项。当产生一个中断或者异常时,首先会从中断描述符表中获取到一个中断向量号时(此中断向量号有可能表示中断,也可能表示的是一个异常),如果是一个中断导致的,会执行do_IRQ()函数,而在do_IRQ()函数中,会根据中断向量号,从中断描述符数组中获取对应的中断描述符,如下图:

  整个中断描述符结构如下:

struct irq_desc {
    struct irq_data        irq_data;
    /* irq的统计信息,在proc中可查到 */
    unsigned int __percpu    *kstat_irqs;

    /* 回调函数,当此中断产生中断时,会调用handle_irq,在handle_irq中进行遍历irqaction链表
     * handle_simple_irq  用于简单处理;
     * handle_level_irq  用于电平触发中断的流控处理;
     * handle_edge_irq  用于边沿触发中断的流控处理;
     * handle_fasteoi_irq  用于需要响应eoi的中断控制器;
     * handle_percpu_irq  用于只在单一cpu响应的中断;
     * handle_nested_irq  用于处理使用线程的嵌套中断;
     */
    irq_flow_handler_t    handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t    preflow_handler;
#endif
    /* 中断服务例程链表 */
    struct irqaction    *action;    /* IRQ action list */
    /* 状态 */
    unsigned int        status_use_accessors;
    /* 函数调用中使用,另一个名称为istate */
    unsigned int        core_internal_state__do_not_mess_with_it;
    /* 嵌套深度,中断线被激活显示0,如果为正数,表示被禁止次数 */
    unsigned int        depth;        /* nested irq disables */
    unsigned int        wake_depth;    /* nested wake enables */
    /* 此中断线上发生的中断次数 */
    unsigned int        irq_count;    /* For detecting broken IRQs */
    /* 上次发生未处理中断时的jiffies值 */
    unsigned long        last_unhandled;    /* Aging timer for unhandled count */
    /* 中断线上无法处理的中断次数,如果当第100000次中断发生时,有超过99900次是意外中断,系统会禁止这条中断线 */
    unsigned int        irqs_unhandled;
    atomic_t        threads_handled;
    int            threads_handled_last;
    /* 锁 */
    raw_spinlock_t        lock;
    struct cpumask        *percpu_enabled;
#ifdef CONFIG_SMP
    /* CPU亲和力关系 */
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    /* 用于调整irq在各个cpu之间的平衡 */
    cpumask_var_t        pending_mask;
#endif
#endif
    unsigned long        threads_oneshot;
    atomic_t        threads_active;
    /* 用于synchronize_irq(),等待该irq所有线程完成 */
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
    /* irqaction数量 */
    unsigned int        nr_actions;
    unsigned int        no_suspend_depth;
    unsigned int        force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
    /* 指向与IRQn相关的/proc/irq/n目录的描述符 */
    struct proc_dir_entry    *dir;
#endif
    int            parent_irq;
    struct module        *owner;
    /* 在/proc/interrupts所显示名称 */
    const char        *name;
} ____cacheline_internodealigned_in_smp;

  core_internal_state__do_not_mes_with_it成员是用于记录此中断线状态的,中断线状态有如下几种形式:

    IRQS_AUTODETECT        /* 该IRQ线用来进行硬件设备探测 */
    IRQS_SPURIOUS_DISABLED    /* 该IRQ线被禁止,是由于产生了欺骗性中断 */
    IRQS_POLL_INPROGRESS      /* 该IRQ进行轮询检查是否发生中断 */
    IRQS_ONESHOT        /* 此IRQ没有在主处理函数中进行unmasked处理 */
    IRQS_REPLAY         /* IRQ线已被禁止,但前一个出现的中断还没有被应答 */
    IRQS_WAITING        /* 进行硬件设备探测时,会将所有没有挂载中断服务程序的IRQ线状态设置为IRQS_WAITING,如果该IRQ上有中断产生,就清除这个状态,可以推断哪些引脚产生过中断 */
    IRQS_PENDING        /* IRQ已经被应答(挂起),但是内核还没有进行处理 */
    IRQS_SUSPENDED       /* 此IRQ被延迟 */    

中断服务例程(ISR)

  中断服务例程用于描述一个设备的中断处理(区别与中断处理函数),每个申请了中断的外部设备都会有一个中断服务例程,其作用就是执行对应设备的中断处理。当多个设备共享IRQ线时,内核会将此IRQ线上所有设备的中断服务例程组织成一个链表并保存在中断描述符中,当此IRQ线产生中断时,中断处理函数会依次执行此IRQ线上的中断服务例程。内核使用struct irqaction描述一个中断服务例程:

struct irqaction {
    /* 此中断服务例程的中断处理函数 */
    irq_handler_t        handler;
    /* 设备ID,一般用于指向中断处理时需要的数据结构传入handler */
    void            *dev_id;
    /* 此中断服务例程在CPU上所对应的设备ID */
    void __percpu        *percpu_dev_id;
    /* 链表中下一个中断服务例程 */
    struct irqaction    *next;
    /* 进行中断处理的内核线程执行函数 */
    irq_handler_t        thread_fn;
    /* 一个内核线程,用于执行中断处理 */
    struct task_struct    *thread;
    /* IRQ线,IRQ号 */
    unsigned int        irq;
    unsigned int        flags;
    unsigned long        thread_flags;
    unsigned long        thread_mask;
    const char        *name;
    /* 指向/proc/irq/n目录的描述符 */
    struct proc_dir_entry    *dir;
} ____cacheline_internodealigned_in_smp;

irq_stat数组

  此数组包含NR_CPUS个元素,系统中每个CPU对应数组中的一个元素。每个元素的类型为irq_cpustat_t,其包含几个计数器和内核记录CPU正在做什么的标志。

typedef struct {
    unsigned int __softirq_pending;        /* 表示挂起的软中断,每一位表示一个软中断,为1表示挂起 */
    long idle_timestamp;                    /* CPU变为空闲的时间点 */

    /* 硬中断统计. */
    unsigned int irq_timer_count;            /* 定时器中断统计 */
    unsigned int irq_syscall_count;            /* 系统调用中断统计 */
    unsigned int irq_resched_count;
    unsigned int irq_hv_flush_count;
    unsigned int irq_call_count;
    unsigned int irq_hv_msg_count;
    unsigned int irq_dev_intr_count;

} ____cacheline_aligned irq_cpustat_t;

数据结构总结

  到此,在中断处理中所涉及的几个重要的数据结构已经说明,其最主要的数据结构为:中断描述符(struct irq_desc),中断控制器描述符(struct irq_chip),中断服务例程(struct irqaction)。它们的组织形式如下:

时间: 2024-10-10 11:16:52

linux中断源码分析 - 概述(一)的相关文章

linux中断源码分析 - 中断发生(三)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 回顾 上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始化期间系统关闭了中断(通过设置CPU的EFLAGS寄存器的IF标志位为0),当整个中断和异常的初始化完成后,系统会开启中断(设置CPU的EFLAGS寄存器的IF标志位为1),此时整个系统的中断已经开始可以使用了.本篇文章我们具体研究一次典型中断发生时的运行流程. 中断产生 我们需要先明确一下,中断控

linux中断源码分析 - 初始化(二)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 本篇文章主要讲述源码中是如何对中断进行一系列的初始化的. 回顾 在上一篇概述中,介绍了几个对于中断来说非常重要的数据结构,分别是:中断描述符表,中断描述符数组,中断描述符,中断控制器描述符,中断服务例程.可以说这几个结构组成了整个内核中断框架主体,所以内核对整个中断的初始化工作大多集中在了这几个结构上. 在系统中,当一个中断产生时,首先CPU会从中断描述符表中获取相应的中断向量,并根据中断向量的权限位判断是否

linux中断源码分析 - 软中断(四)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 在上一篇文章中,我们看到中断实际分为了两个部分,俗称就是一部分是硬中断,一部分是软中断.软中断是专门用于处理中断过程中费时费力的操作,而为什么系统要分硬中断和软中断呢?问得明白点就是为什么需要软中断.我们可以试着想想,如果只有硬中断的情况下,我们需要在中断处理过程中执行一些耗时的操作,比如浮点数运算,复杂算法的运算时,其他的外部中断就不能得到及时的响应,因为在硬中断过程中中断是关闭着的,甚至一些很紧急的中断会

linux内存源码分析 - SLUB分配器概述

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ SLUB和SLAB的区别 首先为什么要说slub分配器,内核里小内存分配一共有三种,SLAB/SLUB/SLOB,slub分配器是slab分配器的进化版,而slob是一种精简的小内存分配算法,主要用于嵌入式系统.慢慢的slab分配器或许会被slub取代,所以对slub的了解是十分有必要的. 我们先说说slab分配器的弊端,我们知道slab分配器中每个node结点有三个链表,分别是空闲slab链表,部分空sla

linux内存源码分析 - SLAB分配器概述

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请者,而且我们知道也可页框大小为4K(也可设置为4M),这时候就会有个问题,如果我只需要1KB大小的内存,页框分配器也不得不分配一个4KB的页框给申请者,这样就会有3KB被白白浪费掉了.为了应对这种情况,在页框分配器上一层又做了一层SLAB层,SLAB分配器的作用就是从页框分配器中拿出一些页框,专门把

Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938396.html 在基本分析完内核启动流程的之后,还有一个比较重要的初始化函数没有分析,那就是do_basic_setup.在内核init线程中调用了do_basic_setup,这个函数也做了很多内核和驱动的初始化工作,详解

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

linux内存源码分析 - 内存压缩(同步关系)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也那么复杂吧,带着好奇心就把页面迁移的源码都大致看了一遍,还好,不复杂,也容易理解,这里我们就说说在页面迁移过程中是如何进行同步的.不过首先可能没看过的朋友需要先看看linux内存源码分析 - 内存压缩(一),因为会涉及里面的一些知识. 其实一句话可以概括页面迁移时是如何进行同步的,就是:我要开始对这

ARMv8 Linux内核源码分析:__flush_dcache_all()

1.1 /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY(__flush_dcache_all) //保证之前的访存指令的顺序 dsb sy //读cache level id register mrs x0, clidr_el1           // read clidr //取bits[26:24](Level of Coherency f