精华内容
下载资源
问答
  • 内核中断号分配
    千次阅读
    2015-06-29 14:14:54
    首先说明,答案是否定的,内核中断号可以与硬件中断号不一致,但是这是个无聊的问题。。实用价值不大。但是却可以引起对内核软件中断号与硬件中断号关系的思考。
    两者的关系我觉得可以从中断的初始化和分发过程来一探究竟。
    这里就从ARM PPC MIPS 3款主流嵌入式处理器架构的内核代码框架中来分析下他们中断的初始化和分发过程。

    一 中断的初始化
    对于中断初始化,在系统启动过程中,这3款处理器架构的内核软件框架中都会有相应的中断初始化函数.

    内核启动函数start_kernel中会调用init_IRQ来进行中断初始化,该函数在不同处理器平台代码有不同实现。实现在arch/xxx/kernel/irq.c中。
    对于arm处理器,init_IRQ调用对应设备描述符machine_desc的init_irq。
    对于ppc处理器,init_IRQ调用对应设备描述符machdep_calls的init_IRQ。
    对于mips处理器,init_IRQ调用arch_init_irq。
    最终调用的中断初始化函数是在板级支持包中实现,因为中断控制器属于处理器核的外设。

    所以可以看出,中断初始化的调用关系:
    通用函数start_kernel -----> 处理器平台级函数init_IRQ -----> 板级中断初始化函数init_irq等


    板级中断初始化函数完成2件事情,其一,中断控制器初始化。其二,中断描述符irq_desc的初始化。
    中断控制器初始化就不细说了。这里我们主要关心软件上中断描述符的处理。

    内核对于中断的管理,最关键的数据结构就是irq_desc,在kernel/irq/irqdesc.c中:

    struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
        [0 ... NR_IRQS-1] = {
            .handle_irq = handle_bad_irq,
            .depth      = 1,
            .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
        }
    };


    NR_IRQS由不同处理器平台的板级支持包来定义,irq_desc数组成员代表每一个中断号和相应的处理函数。
    所以irq_desc[NR_IRQS]就是硬件中断控制器中断号表在内核的表征。

    有个前提,这里是在不配置CONFIG_SPARSE_IRQ内核选项的情况下,irq_desc[NR_IRQS]用数组形式静态分配中断描述符表,编译时即确定irq_desc数组大小。
    如果配置CONFIG_SPARSE_IRQ则动态分配irq_desc以节省内存。这是另一套机制,这里就不细说了。

    板级中断初始化函数中完成其所使用所有中断号对应的irq_desc的初始化,主要是设置中断的一级处理函数。一般是handle_level_irq。函数实现在kernel/irq/chip.c中。
    handle_level_irq遍历该irq_desc的action链表,依次执行其action->handler。

    各个driver中调用request_irq注册的中断处理函数就是irq_desc各个action的handler成员。这里我们也就明白了内核下共享中断的实现机制了。

    内核中断的初始化就是这样。从这里可以看出,irq_desc[NR_IRQS]内核中断号表与硬件中断号表对应。

    那么问题来了,我不让他们一一对应,硬件中断号35的处理函数,我想放在irq_desc数组的30号可不可以?
    从中断的初始化来看这样的修改是没什么问题,就是数组成员内容换一下。
    但是我们要想到中断是来干什么的,中断的初始化以及注册,都是为了能够正确的响应中断进行处理。
    所以这个问题的关键在于,这样修改,当产生35号中断时,内核能不能正常找到在30号irq-desc上的处理函数呢。
    也就是在中断分发过程中内核如何确定软件中断号,由硬件35号中断找到30号irq_desc。

    这个我们就需要来看下内核的中断分发过程了。


    二 中断的分发
    中断是处理器核异常的一种,所以处理器设计中,外设中断引起处理器异常,处理器跳转到异常向量表的相应异常入口取指执行。
    处理器的异常向量表也是软硬件结合很有意思的东西,有时间专门写一篇来记录,这里不详说了。


    我们主要来看产生中断后,处理器跳转到中断异常入口后的执行流程。


    (1)对于arm处理器,执行流程如下:

    vector_irq ---> irq_handler ---> arch_irq_handler_default ---> asm_do_IRQ ---> handle_IRQ
    在arch_irq_handler_default中调用get_irqnr_and_base获取中断号,传给asm_do_IRQ作为参数。
    get_irqnr_and_base由板级支持包实现。


    (2)对于ppc处理器,执行流程如下:

    do_IRQ ---> ppc_md.get_irq ---> handle_one_irq
    ppc_md.get_irq是由板级支持包中实现的设备描述符的获取中断号函数。


    (3)对于mips处理器,执行流程如下:

    handle_int ---> plat_irq_dispatch ---> do_IRQ
    plat_irq_dispatch由班级支持包中实现。


    上述的流程表示由异常入口函数开始,到调用handle_level_irq结束。
    对于3款处理器平台,内核在中断分发上没有像中断初始化那样由通用函数到处理器平台函数最后到板级支持函数,而是每种处理器平台都不一样。上述函数的实现都在arch/xxx/kernel下,具体实现可以参考代码。

    根据上面的分析可以看出,ARM MIPS PPC在中断分发中中断号的获取都是留给板级支持包来实现的。板级支持包中会读取中断控制器中相关寄存器来获取当前产生的中断情况,进而返回中断号。


    所以结合中断初始化部分提出的问题,不管哪款处理器平台,如果我们想将35号中断的中断处理函数在注册时放在30号irq_desc中(方法是request_irq时中断号写30)。

    那么在中断分发时,获取中断号函数中我们也需要进行修改,查询到中断控制器寄存器状态是产生35号中断,我们返回的中断号应该是30号!

    但是,这样做并没有实际的应用意义,因为在实际开发中还是要尽量保证内核下irq_desc数组与硬件中断号表一一对应,这样驱动开发者在操作中断时就不需要关心内核中断号和硬件中断号的关系,而是直接使用硬件中断号来注册就可以了。

    如果内核中断号和硬件中断号不一一对应,驱动开发者在编写驱动时还需要查找硬件中断号和内核中断号的映射表,增大了开发难度。

    无论如何,借这个无聊的问题,还是搞清了内核中断的初始化和分发过程,也是很值得的


    但求好事,莫问前程!

    更多相关内容
  • Linux内核中的中断

    2021-01-20 14:51:16
    中断处理程序是被内核调用来响应中断的,它运行在中断上下文,中断处理程序是上半部,当接收到一个中断,它立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被...
  • 细说内核中断机制

    千次阅读 2018-05-01 20:25:58
     中断通常分为同步中断和异步中断:2 同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后CPU才会发出中断。 ◎ 异步中断是由其他硬件设备依照CPU时钟信号随机产生的。...
    一、什么是中断?

            中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。这样的事件与CPU芯片内外部硬件电路产生的电信号相对应。

            中断通常分为同步中断和异步中断:

    2  同步中断是当指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后CPU才会发出中断。

            ◎ 异步中断是由其他硬件设备依照CPU时钟信号随机产生的。

            在Intel微处理器手册中:

            ◎ 把同步中断称为异常(exception

            ◎ 把异步中断称为中断(interrupt

            这两类中断的共同特点是什么?如果CPU当前不处于核心态,则发起从用户态到核心态的切换。接下来,在内核中执行一个专门的例程,称为中断服务例程(interrupt service routine)。或中断处理程序(interrupthandler)

            另一方面,异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的。第一种情况下,内核通过发送一个每个Unix/Linux程序员都熟悉的信号来处理异常。第二种情况下,内核执行恢复异常需要的所有步骤,例如缺页异常等。

    二、中断信号的作用

            中断信号提供了一种特殊的方式,使处理器转而去运行正常控制流之外的代码。当一个中断信号达到时,CPU必须停止它当前正在做的事情,并且切换到一个新的活动。为了这做到这一点,就要在内核态堆栈保存程序计数器的当前值(即EIP和CS寄存器的内容),并把与中断类型相关的一个地址放进程序计数器。

            这可能会让我们想起系统调度的进程切换,发生在内核用一个进程替换另一个进程时。但是中断处理与进程切换有一个明显的差异:由中断或异常处理程序执行的代码不是一个进程。更准确的说,它是一个内核控制路径,代表中断发生时正在运行的进程执行。作为一个内核控制路径,中断处理程序比一个进程要“轻”(中断的上下文很少,建立或终止中断处理需要的时间也很少)

            中断处理是由内核执行的最敏感的任务之一,因为它必须满足下列约束:

            ◎ 当内核正打算去完成一些别的事情时,中断随时会到来。因此,内核的目标就是让中断尽可能快地处理完,尽其所能把更多的处理向后推迟。因此,内核响应中断后需要进行的操作分为两部分:关键而紧急的部分,内核立即执行;其余推迟的部分,内核随后执行。

            ◎ 因为中断随时会到来,所以内核可能正在处理其中的一个中断时,另一个不同类型的中断又发生了。内核应该尽可能地允许这种情况发生,因为这能维持更多的I/O设备得到处理的机会。因此,中断处理程序必须编写成使相应的内核控制路径能以嵌套的方式执行。当最后一个内核控制路径终止时,内核必须能恢复被中断进程的执行,或者,如果中断信号已导致了重新调度,内核也应能切换到另外的进程。

            ◎ 尽管内核在处理前一个中断时可以接受一个新的中断,但在内核代码中还是存在一些临界区,在临界区中,中断必须被禁止。必须尽可能地限制这样的临界区,因为根据以前的要求,内核,尤其是中断处理程序,应该在大部分时间内以开中断的方式运行。

    三、IRQ和中断

            中断这个名词使用得并不是很谨慎,为什么?由于中断是用来表示由CPU和外部硬件发出的信号所产生的。但是中断不能由处理器外部的外设直接产生,而必须借助于一个称为可编程中断控制器(programmable interrupt controller)的标准组件来请求,该组件存在于每个系统中。

            外部设备,会有电路连接到用于向中断控制器发送中断请求的组件。控制器在执行了各种电工任务之后,将中断请求转发到CPU的中断输入中。因为外部设备不能直接发出中断,而必须通过中断控制器的标准组件来请求中断,所以这种请求更正确的叫法是IRQ,或中断请求(Interrupt Request)。

    每个能够发出中断请求的硬件设备控制器都有这么一条名为IRQ的输出线。所有现有的IRQ线都会与这个中断控制器(PIC)的硬件电路的输入引脚相连。下面来看看这种中断控制器执行下列动作:

            1)    监视IRQ线,检查产生的信号 。如果有一条或两条以上的IRQ线上产生信号,就选择引脚编号较小的IRQ线。

            2)    如果一个引发信号出现在IRQ线上:

                    a)    把接收到的引发信号转换成对应的向量(索引)。

                    b)    把这个向量存放在中断控器的一个I/O端口,从而允许CPU通过数据总线读取此向量。

                    c)    把引发信号发送到处理器的INTR引脚,即产生一个中断。

                    d)    等待,直到CPU通过把这个中断信号写进可编程中断控制器的一个I/O端口来确认它;当这种情况发生时,清INTR线。

            3)    返回到第一步。

            IRQ线是从0开始顺序编号的,因此,第一条IRQ线通常表示成IRQ0。与IRQn关联的Intel缺省向量是n+32。如前所述,通过向中断控制器端口发布合适的指令,就可以修改IRQ和向量之间的映射。

            可以有选择地禁止每条IRQ线。因此,可以对PIC编程从而禁止IRQ,也就是说,可以告诉PIC停止对给定的IRQ线发布中断,或者激活它们,禁止的中断是丢失不了的,它们一旦激活,PIC就又把它们发送到CPU这个特点被大多数中断处理程序使用,因为这允许中断处理程序逐次地处理同一类型的IRQ。

    (一)处理中断

            在CPU得知发生中断后,它将进一步的处理委托给一个软件例程,该例程可能会修复故障、提供专门的处理或将外部事件通知用户进程。由于每个中断和异常都有唯一的编号,内核使用一个数组项是指向处理程序函数的指针。相关的中断号根据数组项在数组中位置判断。

            如下图所示:


    1、进入和退出任务

            如下图,中断处理划分为3部分。首先,必须建立一个适当的环境,使得处理程序函数能够在其中执行,接下来调用处理程序自身,最后将系统复原到中断之前的状态。调用中断处理程序前后的两部分,分别称为进入路径和退出路径。

            进入和退出任务还负责确保处理器从用户态切换到核心态。进入路径的一个关键任务是,从用户态栈切换到核心态栈。但是这一点还不够。因为内核还要使用CPU资源执行其代码,进入路径必须保存用户应用程序当前的寄存器,以便 在中断活动结束后恢复。这与进程调度间用上下文切换的机制是相同的。在进入核心态时,只保存整个寄存器集合的一部分。内核并不使用全部寄存器。(如内核代码中不使用浮点操作,因而不保存浮点寄存器)。平台相关的数据结构pt_regs列出了核心态可能修改的所有寄存器,它的定义考虑到了不同的CPU之间的差别。

            在退出路径中,内核会检查下列事项。

            ◎  调度器是否应该选择一个新进程代替旧的进程。

            ◎  是否有信号必须投递到原进程

            从中断返回之后,只有确认了这两个问题,内核才能完成其常规任务,即还原寄存器集合、切换到用户态栈、切换到适用于用户应用程序的适当的处理器状态,或切换到一个不同的保护环。

            术语中断处理程序的使用可能引起岐义。因为它是用于指代CPU对ISR(中断服务程序)的调用,包括了进入/退出路径和ISR本身。当然,如果只指代在进入路径和退出路径之间进行由C语言实现的例程,将更为准确。

    2、数据结构

            中断技术上的实现有两方面:

            1)    汇编语言代码:与处理器高度相关,用于处理特定平台上相关的底层细节;

            2)    抽象接口:是设备驱动程序及其他内核代码安装和管理IRQ处理程序所需的。

            描述汇编语言部分的功能会涉及无数细节,可以参考处理器体系方面的手册。

            为响应外部设备的IRQ,内核必须为每个潜在的IRQ提供一个函数。该函数必须能够动态注册和注销。静态表组织方式是不够的,因为可能为设备编写模块,而且设备可能与系统的其他部分通过中断进行交互。

            IRQ相关信息管理的关键点是一个全局数组,每个数组项对应一个IRQ编号。因为数组位置和中断号是相同的,很容易定位与特定的IRQ相关的数组项:IRQ0在位置0,IRQ15在位置15,等等,IRQ最终映射到哪个处理器中断,在这里不相关的。

            尽管各个数组项使用的是一个体系结构无关的数据类型,但IRQ的最大可能数目是通过一个平台相关的常数NR_IRQS指定的,大多数体系结构一,该常数定义在处理器相关的头文件irq.h中。不同处理器间及同一处理器家庭内,该常数的值变化都很大,主要取决于辅助CPU管理IRQ的辅助芯片。

            与IRQ的最大数目相比,我们对各数组项的数据类型更感兴趣。在了解细节之前,需要概述内核的IRQ处理子系统。

            之前的一些版本包含了大量平台代码来处理IRQ,在许多地方是相同的。因而,在内核版本2.6开发期间,引入了一个新的通用的IRQ子系统。它能够以统一的方式处理不同的中断控制器和不同类型的中断。基本上它由3个抽象层组成。如下图:

            ◎  高层ISR(high-level interrupt service routines):针对设备驱动程序端的中断,执行由此引起的所有必要的工作。

            ◎  中断电流处理(interrupt flow handling):处理不同的中断电流类型之间的各种差别,如边沿触发和电平触发。

            边沿触发:意味着硬件通过感知线路上的电位差来检测中断。

    电平触发:根据特定的电势值检测中断,与电势是否改变无关。

            ◎  芯片级硬件封装(chip-level hardware encapsulation):需要与电子学层次上产生中断的底层硬件直接通信。该抽象可以视为中断控制器的某种“设备驱动程序”。

    用于表示irq的结构如下:(摘自Linux kernel 3.5)

    [cpp]  view plain copy
    1. /** 
    2.  *struct irq_desc - interrupt descriptor 
    3.  *@irq_data:      per irq and chip datapassed down to chip functions 
    4.  *@timer_rand_state: pointer to timer randstate struct 
    5.  *@kstat_irqs:    irq stats per cpu 
    6.  *@handle_irq:    highlevel irq-eventshandler 
    7.  *@preflow_handler:  handler called beforethe flow handler (currently used by sparc) 
    8.  *@action:     the irq action chain 
    9.  *@status:     status information 
    10.  *@core_internal_state__do_not_mess_with_it: core internal status information 
    11.  *@depth:      disable-depth, for nestedirq_disable() calls 
    12.  *@wake_depth:    enable depth, for multipleirq_set_irq_wake() callers 
    13.  *@irq_count:     stats field to detectstalled irqs 
    14.  *@last_unhandled:   aging timer forunhandled count 
    15.  *@irqs_unhandled:   stats field forspurious unhandled interrupts 
    16.  *@lock:    locking for SMP 
    17.  *@affinity_hint: hint to user space forpreferred irq affinity 
    18.  *@affinity_notify:  context fornotification of affinity changes 
    19.  *@pending_mask:  pending rebalancedinterrupts 
    20.  *@threads_oneshot:  bitfield to handleshared oneshot threads 
    21.  *@threads_active:   number of irqactionthreads currently running 
    22.  *@wait_for_threads: wait queue for sync_irqto wait for threaded handlers 
    23.  *@dir:     /proc/irq/ procfs entry 
    24.  *@name:    flow handler name for/proc/interrupts output 
    25.  */  
    26. struct irq_desc  
    27. {  
    28.     struct irq_data             irq_data;  
    29.     struct timer_rand_state *timer_rand_state;  
    30.     unsigned int __percpu     *kstat_irqs;  
    31.    irq_flow_handler_t        handle_irq;  
    32. #ifdef CONFIG_IRQ_PREFLOW_FASTEOI  
    33.    irq_preflow_handler_t      preflow_handler;  
    34. #endif  
    35.     struct irqaction           *action;           /* IRQ action list*/  
    36.     unsigned int    status_use_accessors;  
    37.     unsigned int    core_internal_state__do_not_mess_with_it;  
    38.     unsigned int   depth;            /* nested irqdisables */  
    39.     unsigned int    wake_depth;       /* nested wake enables */  
    40.     unsigned int    irq_count;       /* For detectingbroken IRQs */  
    41.     unsigned long  last_unhandled; /* Aging timer for unhandled count */  
    42.     unsigned int    irqs_unhandled;  
    43.    raw_spinlock_t  lock;  
    44.     struct cpumask  *percpu_enabled;  
    45. #ifdef CONFIG_SMP  
    46.     const struct cpumask         *affinity_hint;  
    47.     struct irq_affinity_notify *affinity_notify;  
    48. #ifdef CONFIG_GENERIC_PENDING_IRQ  
    49.    cpumask_var_t   pending_mask;  
    50. #endif  
    51. #endif  
    52.     unsigned long   threads_oneshot;  
    53.    atomic_t        threads_active;  
    54.    wait_queue_head_t       wait_for_threads;  
    55. #ifdef CONFIG_PROC_FS  
    56.     struct proc_dir_entry     *dir;  
    57. #endif  
    58.     struct module           *owner;  
    59.     const char                  *name;  
    60. } ____cacheline_internodealigned_in_smp;  

            在上面的代码中,注释部分基本上简单的介绍了下各个字段的意义,下面会针对一些特殊的字段进行说明。

    在介绍下面的主要字段之前,先说下这个结构中的irq_data字段,该字段保存了一些在中断处理过程各个中断处理阶段都会用到的数据,该结构如下所示:(摘自Linux kernel 3.5)

    [cpp]  view plain copy
    1. /** 
    2.  *struct irq_data - per irq and irq chip data passed down to chip functions 
    3.  *@irq:        interrupt number 
    4.  *@hwirq:      hardware interrupt number,local to the interrupt domain 
    5.  *@node:       node index useful forbalancing 
    6.  *@state_use_accessors: status information for irq chip functions. 
    7.  *         Use accessor functions to deal with it 
    8.  *@chip:       low level interrupt hardwareaccess 
    9.  *@domain:     Interrupt translationdomain; responsible for mapping 
    10.  *         between hwirq number and linux irq number. 
    11.  *@handler_data:   per-IRQ data for theirq_chip methods 
    12.  *@chip_data:      platform-specificper-chip private data for the chip 
    13.  *         methods, to allow shared chip implementations 
    14.  *@msi_desc:       MSI descriptor 
    15.  *@affinity:       IRQ affinity on SMP 
    16.  * 
    17.  *The fields here need to overlay the ones in irq_desc until we 
    18.  *cleaned up the direct references and switched everything over to 
    19.  *irq_data. 
    20.  */  
    21. struct irq_data  
    22. {  
    23.     unsigned int        irq;  
    24.     unsigned long       hwirq;  
    25.     unsigned int        node;  
    26.     unsigned int        state_use_accessors;  
    27.     struct irq_chip    *chip;  
    28.     struct irq_domain    *domain;  
    29.     void                *handler_data;  
    30.     void                *chip_data;  
    31.     struct msi_desc   *msi_desc;  
    32. #ifdef CONFIG_SMP  
    33.    cpumask_var_t       affinity;  
    34. #endif  
    35. };  

            大概的意思在注释中可以看到,下面将结合该结构和irq_desc结构针对一些比较有用的字段做以介绍。

    从内核中高层代码的角度来看,每个IRQ都可以由该结构完全描述。上面介绍的3个抽象层在该结构中表示如下:

            ◎  电流层ISR由handle_irq提供。irq_data结构中的handler_data可以指向任意数据,该数据可以是特定于IRQ或处理程序。每当发生中断时,特定于体系结构的代码都会调用handle_irq。该函数负责使用chip中提供的特定于控制器的方法,进行处理中断所必需的一些底层操作。用于不同中断类型的默认函数由内核提供。

            ◎  action提供了一个操作链,需要在中断发生时执行。由中断通知的设备驱动程序,可以将与之相关的处理程序函数放置在此处。有一个专门的数据结构用于表示这些操作。

            ◎  电流处理和芯片相关操作被封装在ird_data结构中的chip中。为此引入了一个专门的数据结构,irq_chip。该结构相关的东西之后介绍。

            ◎  name指定了电流层处理程序的名称,将显示在/proc/interrupts中。对边沿触发是“edge”,对电平触发中断,通常是“level”。

            一些其它的字段意义如下:

            ◎  depth有两个任务。它可用于确定IRQ电路是启用的还是禁用的,正值表示禁用的,而0表示启用的。为什么用正值表示禁用的IRQ呢?因为这使得内核能够区分启用和禁用的IRQ电路,以及重复禁用同一中断的情形。这个值相当于一个计数器,内核其余部分的代码每次禁用某个中断,则将对应的计数器加1;每次 断被再次启用,则将计数器减1 。在depth归0时,硬件才能再次使用对应的IRQ。这各方法能够支持对嵌套禁用中断的正确处理。

            ◎  IRQ不仅可以在处理程序安装期间改变其状态,而且可以在运行时改变:status描述了IRQ的当前状态。<irq.h>中定义了各种常数,可用于描述IRQ电路当前的状态。每个常数表示位串中一个置位的标志位,只要不相互冲突,几个标志可以同时设置。

            根据status当前的值,内核很容易获知某个IRQ的状态,而无需了解底层实现的硬件相关特性。当然,只设置对应的标志位是不会产生预期效果的。如:通过设置IRQ_DISABLED标志来禁用中断是不可能的,还必须将新状态通知底层硬件。因而,该标准只能通过特定于控制器的函数设置,这些函数同时还负责将设置信息同步到底层硬件。

    (1)IRQ控制器抽象结构

            刚刚提到过,电流处理和芯片相关操作被封装在ird_data结构中的chip中。为此还引入了一个专门的数据结构,下面来详细说下这个结构。这个结构是一个操作的集合,它提供的函数用于改变IRQ的状态,这也是它们还负责设置irq_desc结构中的status字段的原因。该结构如下:(摘自Linux kernel 3.5)

    [cpp]  view plain copy
    1. /** 
    2.  *struct irq_chip - hardware interrupt chip descriptor 
    3.  * 
    4.  *@name:       name for /proc/interrupts 
    5.  *@irq_startup:    start up the interrupt(defaults to ->enable if NULL) 
    6.  *@irq_shutdown:   shut down the interrupt(defaults to ->disable if NULL) 
    7.  *@irq_enable:     enable the interrupt(defaults to chip->unmask if NULL) 
    8.  *@irq_disable:    disable the interrupt 
    9.  *@irq_ack:        start of a new interrupt 
    10.  *@irq_mask:       mask an interrupt source 
    11.  *@irq_mask_ack:   ack and mask aninterrupt source 
    12.  *@irq_unmask:     unmask an interruptsource 
    13.  *@irq_eoi:        end of interrupt 
    14.  *@irq_set_affinity:   set the CPU affinityon SMP machines 
    15.  *@irq_retrigger:  resend an IRQ to the CPU 
    16.  *@irq_set_type:   set the flow type(IRQ_TYPE_LEVEL/etc.) of an IRQ 
    17.  *@irq_set_wake:   enable/disablepower-management wake-on of an IRQ 
    18.  *@irq_bus_lock:   function to lock accessto slow bus (i2c) chips 
    19.  *@irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips 
    20.  *@irq_cpu_online: configure an interrupt source for a secondary CPU 
    21.  *@irq_cpu_offline:    un-configure aninterrupt source for a secondary CPU 
    22.  *@irq_suspend:    function called fromcore code on suspend once per chip 
    23.  *@irq_resume:     function called fromcore code on resume once per chip 
    24.  *@irq_pm_shutdown:    function called fromcore code on shutdown once per chip 
    25.  *@irq_print_chip: optional to print special chip info in show_interrupts 
    26.  *@flags:      chip specific flags 
    27.  * 
    28.  *@release:        release function solelyused by UML 
    29.  */  
    30. struct irq_chip  
    31. {  
    32.     const char   *name;  
    33.     unsigned int    (*irq_startup)(struct irq_data *data);  
    34.     void  (*irq_shutdown)(struct irq_data *data);  
    35.     void  (*irq_enable)(struct irq_data *data);  
    36.     void  (*irq_disable)(struct irq_data *data);  
    37.     void  (*irq_ack)(struct irq_data *data);  
    38.     void  (*irq_mask)(struct irq_data *data);  
    39.     void  (*irq_mask_ack)(struct irq_data *data);  
    40.     void  (*irq_unmask)(struct irq_data *data);  
    41.     void  (*irq_eoi)(struct irq_data *data);  
    42. int      (*irq_set_affinity)(struct irq_data *data,  
    43. const struct cpumask *dest, bool force);  
    44.     int      (*irq_retrigger)(struct irq_data *data);  
    45. int   (*irq_set_type)(struct irq_data *data,  
    46. unsigned int flow_type);  
    47.     int   (*irq_set_wake)(struct irq_data *data, unsigned int on);  
    48.     void  (*irq_bus_lock)(struct irq_data *data);  
    49.     void  (*irq_bus_sync_unlock)(struct irq_data *data);  
    50.     void  (*irq_cpu_online)(struct irq_data *data);  
    51.     void  (*irq_cpu_offline)(struct irq_data *data);  
    52.     void  (*irq_suspend)(struct irq_data *data);  
    53.     void  (*irq_resume)(struct irq_data *data);  
    54.     void  (*irq_pm_shutdown)(struct irq_data *data);  
    55.     void  (*irq_print_chip)(struct irq_data *data, struct seq_file *p);  
    56.     unsigned long   flags;  
    57.    
    58.     /* Currently used only by UML, mightdisappear one day.*/  
    59. #ifdef CONFIG_IRQ_RELEASE_METHOD  
    60.     void       (*release)(unsigned int irq, void *dev_id);  
    61. #endif  
    62. };  

            该结构需要考虑内核中出现的各个IRQ实现的所有特性。因而,一个该结构的特定实例,通常只定义所有可能方法的一个子集。

            name包含一个短的字符串,用于标识硬件控制器。

            各个函数指针的语义如下:

            irq_startup指向一个函数,用于第一次初始化一个IRQ。大多数情况下,初始化工作仅限于启用该IRQ。因而, irq_startup函数实际上就工作转给enable。

            irq_enable激活一个IRQ。换句话说,它执行IRQ由禁用状态到启用状态的转换。为此。必须向I/O内存或I/O端口中硬件相关的位置写入特定于硬件的数值。

            irq_disable与enable相对应,用于禁用IRQ。而shutdown完全关闭一个中断源。如果不支持该特性,那么这个函数实际上是disable的别名。

    irq_ack与中断控制器的硬件密切相关。在某些模型中,IRQ请求的到达必须显式确认,后续的请求才能进行处理。如果芯片组没有这样的要求,该指针可以指向一个空函数,或NULL指针。irq_mask_ack确认一个中断,并在接下来屏蔽该中断。

            在现代的中断控制器不需要内核进行太多的电流控制,控制器几乎可以管理所有事务。在处理中断时需要一个到硬件的回调,由irq_eoi提供。eoi表示endof interrupt,即中断结束。

            在多处理器系统中,可使用irq_set_affinity指定用哪个CPU来处理特定的IRQ。这使得可以将IRQ分配给某些CPU。该方法在单处理器系统上没用,可以设置为NULL。

            set_type设置IRQ的电流类型。该方法主要使用在ARM、PowerPC和SuperH机器上,其他系统不需要该方法,可以将set_type设置为NULL。

    (2)处理程序函数的表示

            还记得在在刚才在“中断处理子系统的各部分交互方式”的图中看到的,刚才介绍了下IRQ控制器的抽象对象,当内核获取到相应的中断请求后,是如何执行对应的中断处理函数。这里又要引出一个新的结构,irqaction结构。每个处理程序函数都对应该结构的一个实例:该结构如下:(摘自LinuxKernel 3.5)

    [cpp]  view plain copy
    1. span style="font-size:16px;">typedef irqreturn_t (*irq_handler_t)(intvoid *);  
    2.    
    3. /** 
    4.  *struct irqaction - per interrupt action descriptor 
    5.  *@handler:    interrupt handler function 
    6.  *@flags:  flags (see IRQF_* above) 
    7.  *@name:   name of the device 
    8.  *@dev_id: cookie to identify the device 
    9.  *@percpu_dev_id:  cookie to identify thedevice 
    10.  *@next:   pointer to the next irqactionfor shared interrupts 
    11.  *@irq:    interrupt number 
    12.  *@dir:    pointer to the proc/irq/NN/nameentry 
    13.  *@thread_fn:  interrupt handler functionfor threaded interrupts 
    14.  *@thread: thread pointer for threaded interrupts 
    15.  *@thread_flags:   flags related to @thread 
    16.  * @thread_mask:    bitmask for keeping track of @threadactivity 
    17.  */  
    18. struct irqaction  
    19. {  
    20.    irq_handler_t       handler;  
    21.     unsigned long       flags;  
    22.     void                *dev_id;  
    23.     void __percpu       *percpu_dev_id;  
    24.     struct irqaction   *next;  
    25.     int                 irq;  
    26.    irq_handler_t       thread_fn;  
    27.     struct task_struct *thread;  
    28.     unsigned long       thread_flags;  
    29.     unsigned long       thread_mask;  
    30.     const char          *name;  
    31.     struct proc_dir_entry   *dir;  
    32. } ____cacheline_internodealigned_in_smp;  

            该结构中最重要的成员是处理程序函数本身,即handler成员,这是一个函数指针,位于结构的起始处。在设备请求一个系统中断,而中断控制器通过引用发中断将该请求转发到处理器的时候,内核将调用该处理程序函数。在考虑如何注册处理程序函数时,我们再仔细考察其参数的语义。但请注意,处理程序的类型为irq_handler_t,与电流处理程序的类型irq_flow_handler_t显然是不同的。

            name和dev_id唯一地标识一个中断处理程序。name是一个短字符串,用于标识设备,而dev_id是一个指针,指向在所有内核数据结构中唯一标识了该设备的数据结构实例。

    如果几个设备共享一个IRQ,那么IRQ编号自身不能标识该设备,此时,在删除处理程序函数时,将需要上述信息。

            flag是一个标志变量,通过位图描述了IRQ的一些特性,位图中各个标志位照例可通过预定义的常数。<interrupt.h>中定义了下列常数。

    ◎  对共享的IRQ设置IRQF_SHARED,表示有多于一个设备使用该IRQ电路。

    ◎  如果IRQ对内核熵池有贡献,将设置IRQF_SAMPLE_RANDOM。

    ◎  IRQF_DISABLED表示IRQ的处理程序必须在禁用中断的情况下执行。

    ◎  IRQF_TIMER表示时钟中断

            next用于实现共享的IRQ处理程序。几个irqaction实例聚集到一个链表中。链表的所有元素都必须处理同一个IRQ编号。在一个链表中的中断都属于可共享的IRQ,这在后面会有一些介绍。

            下图给出了所描述各数据结构的一个概览,说明其彼此交互的方式。因为通常在一个系统上只有一种类型的中断控制器会占据支配地位,所以在一般情况下所有的irq_desc的handler成员都指向kirq_chip的同一个实例。

    3、中断电流处理

            在这里将介绍下电流处理是如何实现的。在内核版本2.6重写中断逻辑之前,此领域中的现状令人感到相当痛苦,在电流处理中会涉及大量体系结构相关的代码。幸好,情况现在有了很大的改善,有一个通用框架几乎可用于所有硬件,仅有少量例外。

    (1)设置控制器硬件

            首先,需要提到内核提供的一些标准函数,用于注册irq_chip和设置电流处理程序:

          

    [cpp]  view plain copy
    1. int irq_set_chip(unsigned int irq, struct irq_chip *chip);  

            该函数将一个IRQ芯片以irq_chip实例的形式关联到某个特定的中断上。除了从irq_desc选取适当的成员并设置chip之外,如果没有提供特定于芯片的实现,该函数还将设置默认的处理程序。如果chip指针为NULL,将使用通用的“无控制器”irq_chip实例no_irq_chip,该实现只提供了空操作。

     

    [cpp]  view plain copy
    1. void irq_set_handler(unsigned int irq, irq_flow_handler_t handle);  
    2. void irq_set_chained_handler(unsigned int irq, irq_flow_handler_thandle);  


            这两个函数为某个给定的IRQ编号设置电流处理程序。第二种变体表示,处理程序必须处理共享的中断。这会置位irq_desc[irq]->status中的标志位IRQ_NOREQUEST和IRQ_NOPROBE:设置第一标志,是因为共享中断是不能独占使用的,设置第二个标志,是因为在有多个设备的IRQ电路上,使用中断探测显然是个坏主意。

    两个函数在内部都使用了__irq_set_handler,该函数执行一些合理性的检查,然后设置irq_desc[irq]->handle_irq。

     

    [cpp]  view plain copy
    1. void irq_set_chip_and_handler(unsigned int irq,struct irq_chip *chip,irq_flow_handler_thandle);  
    2. void irq_set_chip_and_handler_name(unsigned int irq,struct irq_chip *chip,irq_flow_handler_thandle,const char *name);  


            该两个函数是一种快捷方式,它相当于连续调用上述两个函数。_name的函数变体工作方式相同,但可以为电流处理程序指定一个名称,保存在irq_desc[irq]->name中。

    (2)电流处理

            在讨论电流处理程序实现方式之前,需要介绍处理程序所用的类型。irq_flow_handler_t指定了IRQ电流处理程序函数的原型:

    [cpp]  view plain copy
    1. typedef void (*irq_flow_handler_t)(unsigned int irq,struct irq_desc *desc);  

            电流处理程序的参数包括IRQ编号和一个指向负责该中断的irq_handler指针,该信息接下来可用于实现正确的电流处理。

            在前面说过,不同的硬件需要不同的电流处理,例如,边沿触发和电平触发就需要不同的处理。内核对各种类型提供了几个默认的电流处理程序。它们有一个共同点:每个电流处理程序在其工作结束后,都要负责调用高层ISR。handle_IRQ_event负责激活高层的处理程序,这将在后面讨论。现在主要讲如何处理电流处理。

            ◎  边沿触发

            现在的硬件大部分采用的是边沿触发中断,因此首先讲述这一类型。默认处理程序实现在handle_edge_irq中。其代码流程图如下图:

            在处理边沿触发的IRQ时无须屏蔽,这与电平触发IRQ是相反的。这对SMP系统有一个重要的含义:当在一个CPU上处理一个IRQ时,另一个同样编号的IRQ可以出现在另一个CPU上,称为第二个CPU。这意味着,当电流处理处理程序在由第一个IRQ触发的CPU上运行时,还可能被再次调用。但为什么应该有两个CPU同时运行同一个IRQ处理程序呢?内核想要避免这种情况:处理程序只应在一个CPU上运行。handle_edge_irq的开始部分必须处理这种情况。如果在irq_desc实例中的irq_data字段内的state_use_accessors字段被设置了IRQD_IRQ_INPROGRESS标志。则IRQ在另一个CPU上已经处于处理过程中。通过设置IRQD_IRQ_INPROGRESS标志,内核能够记录还有另一个IRQ需要在稍后处理。在屏蔽该IRQ并通过mask_ack_irq向控制器发送一个确认后,处理过程可以放。因而第二个CPU可以恢复正常的工作,而第一个CPU将在稍后处理该IRQ。

            在IRQ被禁用,或没有可用的ISR处理程序,都会放弃处理。

            现在,开始IRQ处理本身所涉及的工作。在用芯片相关的函数chip->irq_ack向中断控制器发送一个确认。然后调用handle_irq_event函数,该函数先将IRQS_PENDING的标志清除,然后设置IRQD_IRQ_INPROGRESS标志。这表示IRQ正在处理过程中,可用于避免同一处理程序在多个CPU上执行。

            假定只有一个IRQ需要处理。在这种情况下,这时handle_irq_event函数会激活高层ISR处理程序,然后可以清除IRQD_IRQ_INPROGRESS标志。

    [cpp]  view plain copy
    1. void  
    2. handle_edge_irq(unsigned int irq, struct irq_desc *desc)  
    3. {  
    4.    raw_spin_lock(&desc->lock);  
    5.    
    6.    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);  
    7.     /* 
    8.     * If we're currently running this IRQ, or its disabled, 
    9.     * we shouldn't process the IRQ. Mark it pending, handle 
    10.     * the necessary masking and go out 
    11.     */  
    12.     if (unlikely(irqd_irq_disabled(&desc->irq_data) ||  
    13.                    irqd_irq_inprogress(&desc->irq_data) ||  
    14. !desc->action))  
    15. {  
    16.        if (!irq_check_poll(desc)) {  
    17.         /* 如果IRQ被禁用了,或者没有IRQ处理程序, 
    18.             * 或者有别的CPU正在处理同一个IRQ编号的中断 
    19.             * 这时将把状态设置为IRQS_PENDING状态。并取消处理 */  
    20.            desc->istate |= IRQS_PENDING;  
    21.            mask_ack_irq(desc);  
    22.            goto out_unlock;  
    23.        }  
    24.     }  
    25.    kstat_incr_irqs_this_cpu(irq, desc);  
    26.    
    27.     /* Start handling the irq */  
    28.    desc->irq_data.chip->irq_ack(&desc->irq_data);  
    29.    
    30.     do {  
    31.        if (unlikely(!desc->action)) {  
    32.            mask_irq(desc);  
    33.            goto out_unlock;  
    34.        }  
    35.    
    36.        /* 
    37.         * When another irq arrived while we were handling 
    38.         * one, we could have masked the irq. 
    39.         * Renable it, if it was not disabled in meantime. 
    40.          */  
    41.        if (unlikely(desc->istate & IRQS_PENDING)) {  
    42.            if (!irqd_irq_disabled(&desc->irq_data) &&  
    43.                 irqd_irq_masked(&desc->irq_data))  
    44.                 unmask_irq(desc);  
    45.        }  
    46.       /*调用上层ISR函数*/  
    47.        handle_irq_event(desc);  
    48.     /*只要IRQS_PENDING仍然置位,而且IRQ没有禁用,就一直迭代下去*/  
    49.     } while ((desc->istate & IRQS_PENDING) &&  
    50.               !irqd_irq_disabled(&desc->irq_data));  
    51.    
    52. out_unlock:  
    53.    raw_spin_unlock(&desc->lock);  
    54. }  


            IRQ的处理是在一个循环中进行。假定我们刚好处于调用handle_irq_event之后的位置上。在第一个IRQ的ISR处理程序运行时,可能同时有第二个IRQ请求发送过来,前前面已经说过,这通过IRQ_PENDING表示,如果设置了该标志(同时该IRQ没有禁用),那么有另一个IRQ正在等待处理,循环将从头再次开始。

            但在这种情况下,IRQ已经被屏蔽(在设置IRQ_PENDING时同时也调用了mask_ack_irq函数将该中断屏蔽掉了)。因而必须用unmask_irq解除IRQ的屏蔽,并清除IRQ_MASKED标志,这确保在handle_irq_event执行期间只能发生一个中断(被屏蔽后将不会在收到同一个编号的中断)。

            ◎  电平触发

            与边沿触发中断相比,电平触发中断稍微容易处理一些。这也反映在电流处理程序handle_level_irq的代码流程图中。如下图:

            电平触发在处理时必须屏蔽,因此需要完成的第一件事就是调用mask_ack_irq,该函数屏蔽并确认IRQ,这是通过调用chip->irq_mask_ack,如果该方法不可用,则连续调用chip->irq_ack和chip->irq_mask。在多处理器系统上,可能发生竞态条件,尽管IRQ已经在另一个CPU上处理,但仍然在当前CPU上调用了handle_level_irq。这可以通过检查IRQD_IRQ_INPROGRESS标志来判断,这种情况下,IRQ已经在另一个CPU上处理,因而在当前CPU上可以放弃处理。

            如果没有对该IRQ注册处理程序,也可以立即放弃处理,因为无事可做。另一个导致放弃处理的原因是设置了IRQ_DISABLE。尽管被禁用,有问题的硬件仍然可能发出IRQ,但可以被忽略。

            接下来调用handle_irq_event。该函数在边沿触发中已有相关说明。最后需要解除对IRQ的屏蔽。但内核需要考虑到ISR可能禁用中断的情况,在这种情况下,ISR仍然保持屏蔽状态。否则,便调用unmask_irq来解除屏蔽。

    (二)初始化和分配IRQ

    1、注册IRQ

            由于设备驱动程序动态注册ISR的工作,可以使所述的数据结构非常简单的进行。在内核版本2.6重写中断子系统之前,该函数是由平台相关代码实现的。其原型在所有体系结构上都是相同的,因为对编写平台无关的驱动程序来说,这是一个绝对的先决条件。现在,该函数由通用代码实现:

    [cpp]  view plain copy
    1. int __must_check  
    2. request_irq( unsigned int irq, irq_handler_t handler,unsigned long flags,const char *name, void *dev);  

            该函数其实是request_thread_irq的一个包裹函数,该函数首先生成一个新的irqaction的实例,然后用函数参数填充其内容。当然,其中特别重要的是处理程序函数的handler。所有进一步的工作都委托给__setup_irq函数,它将执行下列步骤:

            如果设置了IRQF_SAMPLE_RANDOM,则该中断将对内核熵池有所贡献,熵池用于随机数发生器/dev/radom。之后调用rand_initialize_irq将该IRQ添加到对应的数据结构中。

            由request_thread_irq生成的irqaction实例被添加到所属IRQ编号对应的例程链表尾部,该链表表头为irq_desc[irq]->action。在处理中断共享中断时,内核就通过这种方式来确保中断发生时调用处理程序的顺序与其注册顺序相同。

            如果安装的处理程序是该IRQ编号对应链接中的第一个,则调用handler->tup初始化函数。如果该IRQ此前已经安装了处理程序,则没有必要再调用该函数。

            register_irq_proc在proc文件系统中建立目录/proc/irq/NULL。而register_handler_proc生成/proc/irq/NUM/name。接下来,系统中就可以看到对应的IRQ通道在使用了。

    2、释放IRQ

            释放中断的方案,与前述过程刚好相反。首先,通过硬件相关的函数chip->shutdown通知中断控制器该IRQ已经删除,接下来将相关数据项从内核的一般数据结构中删除。辅助函数free_irq承担这些任务。在重写IRQ子系统之前它是一个体系结构相关的函数。

            在IRQ处理程序需要删除一个共享的中断时,IRQ编号本身不足以标识该IRQ。在这种情况下,为提供唯一标识,还必须使用前面讲述的dev_id。内核扫描所有注册的处理程序的链表,直至找到一个匹配的处理程序。这时才能移除该项。

    3、注册中断

            前面讲述的机制喉适用于由系统外设的中断请求所引发的中断。但内核还必须考虑由处理器本身或者用户进程中的软件机制所引发的中断。与IRQ相比,内核无需提供接口,供此类中断动态注册处理程序这是因为,所使用的编号在初始化时就是已知的,此后不会改变。中断和异常的注册在内核初始化时进行,其分配在运行时并不改变。

    (三)处理IRQ

          在注册了IRQ处理程序后,每次发生中断时将执行处理程序例程。仍然会出现如何协调不同平台差异的问题,由于事情的特定性质所致,使得差别不仅涉及平台相关实现中的各个C函数,还深入到用于底层处理、人工优化的汇编语言代码。

          我们可以确定各个平台之间的几个结构上的相似性。例如,前文讨论过,各个平台上的中断操作都由3部分组成。进入路径从用户态切换到核心态,接下来执行实际的处理程序例程,最后从核心态切换回用户态。尽管涉及大量的汇编语言代码,至少有一些C代码片段在所有平台上都是相似的。

    1、切换到核心态

            到核心态的切换,是基于每个中断之后由处理器自动执行汇编代码。该代码的任务如上面所讲,其中通常定义了各个入口点,在中断发生时处理器可以将控制流转到这些入口点。

            只有那些最为必要的操作直接在汇编语言代码中执行。内核试图尽快地返回到常规的C语言,因为C语言代码更容易处理。为此,必须创建一个环境,与C编译器预期兼容。

            在C语言中调用函数时,需要将所需的灵气按一定的顺序放到栈上。在用户态和核心态之间切换时,还需要将最重要的寄存器保存到栈上,以便以后恢复。这两个操作由平台相关的汇编语言代码执行。在大多数平台上,控制流接下来传递到C函数do_IRQ,其实现也是平台相关的,但情况仍然得到了很大的简化。该函数原型如下:

    [cpp]  view plain copy
    1. unsigned int __irq_entry do_IRQ(struct pt_regs *regs);  

            pt_regs用于保存内核使用的寄存器集合。各个寄存器的值被依次压栈(通过汇编语言代码)。在C函数调用之前,一直保存在栈上。

            pt_regs的定义可以确保栈上的各个寄存器项与该结构的各个上对应。这些值并不是仅仅保存用于后续的使用,C代码也可以读取这些值。

            此外,寄存器集合也可以被复制到地址空间中栈以外的其它位置。在这咱情况下,do_IRQ的一个参数是指向pt_regs的指针,但这并没有改变以下事实:寄存器的内存已经被保存,可以由C代码读取。

    pt_regs的定义是平台相关的,因而不同的处理器提供了不同的寄存器集合。

    2、IRQ栈

            只有在内核使用内核栈来处理IRQ的情况下,上面描述的情形才是正确的。但不一定总是如此,IA-32体系结构提供了配置选项CONFIG_4KSTACKS。如果启用该配置,内核栈的长度由8KB缩减到4KB。由于IA-32计算机上页面的大小是4KB,实现内核栈所需的页数目由2个减少到一个。由于单个内存页比两个连续的内存页更容易分配,在系统中有大量活动进程时,这使得虚拟内存子系统的工作会稍微容易些。遗憾的,对常规的内核工作以及IRQ处理例程所需的空间来说,4KB并不总是够用,因而引入了另外的两个栈。

            ◎  用于硬件IRQ处理的栈

            ◎  用于软件IRQ处理的栈

            常规的内核栈对每个进程都会分配,而这两个额外的栈是针对各CPU分别分配的,在硬件中断发生时,内核需要切换到适当的栈。

    3、调用电流处理程序例程

            电流处理程序例程的调用方式,因体系结构而不同,我们假定内核栈只使用了一个页帧,即每个进程的内核栈为4KB。如果设置了CONFIG_4KSTACKS,内核栈的配置就是这样。在上面说过,如果在这种情况下,内核需要一个独立的栈处理IRQ。

            首先开始先调用set_irq_regs将一个指向寄存器集合的指针保存在一个全局的CPU变量中(中断发生之前,变量中保存的旧指针会保留下来,借后续使用)。需要访问寄存器集合的中断处理程序,可以从该变量中访问。

            接下调用irq_enter负责更新一些统计量。对于具备动态时钟周期特性的系统,如果系统已经有很长一段时间没有发生时钟中断,则更新全局计时变量jiffies。接下来内核必须切换到IRQ栈。当前栈可以通过调用辅助函数current_thead_info获得,该函数返回一个指向当前使用的thread_info实例的指针。而指向适当的IRQ栈的指针可以从上下文中的hardirq_ctx获得。有如下两种可能的情况:

            进程已经在使用IRQ栈了,因为是在处理嵌套的IRQ,在这种情况下,内核不需要做什么,所有的设置都已经完成。可以调用ird_desc[irq]->handle_irq来激活保存在IRQ数据库中的IRQ。

            当前栈不是IRQ栈,(curctx != irqctx),需要在二者之间切换,在这种情况下,内核执行所需的底层汇编语言操作来切换栈,然后调用ird_desc[irq]->handle_irq,最后再将栈切换回去。

            接下来调用irq_exit函数,该函数负责记录一些统计量,另外还要调用do_softirq来处理任何待决的软件IRQ。最后再次调用set_irq_regs,将指向struct pt_regs的指针恢复到上一次调用之前的值。这确保嵌套的处理程序能够正确工作。

    4、调用高层的ISR

            经过do_IRQ的工作最终将调到不同的电流处理函数,也就是之前所说的handle_edge_irq或handle_level_irq函数,在介绍这两个函数的时候,函数最终都会去调用handle_irq_event的函数,而这个函数最终又会调用到handle_irq_event_percpu函数,都会采用这个函数来激活与特定IRQ相关的高层ISR。现在需要仔细的说下这个函数。该函数原型如下:

    [cpp]  view plain copy
    1. irqreturn_t  
    2. handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)  

            该函数主要的任务就是逐一调用所注册的IRQ处理程序action。代码大概如下面这样:

    [cpp]  view plain copy
    1. irqreturn_t  
    2. handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)  
    3. {  
    4.     irqreturn_tretval = IRQ_NONE;  
    5.     unsigned int random = 0, irq = desc->irq_data.irq;  
    6.     ......  
    7.     do {  
    8.        irqreturn_t res;  
    9.        ......  
    10.        res = action->handler(irq, action->dev_id);  
    11.        ......  
    12.        retval |= res;  
    13.        action = action->next;  
    14.     } while (action);  
    15.     ......  
    16.     return retval;  
    17. }  

            在共享IRQ时,内核无法找出引发中断请求的设备。该工作完全留自带程序例程,其中将使用设备相关的寄存器或其他硬件特征来查找中断来源。未受影响的例程也需要识别出该中断并非来自于相关设备,应该尽可能快的将控制返回。但处理程序例程也无法向高层代码报告该中断是否是针对它的。内核总是依次执行所有处理程序例程,而不考虑实际上哪个处理程序与该中断相关。

            但内核总可以检查是否有负责该IRQ的处理程序。irqreturn_t定义为处理程序函数的返回类型,它只是一个简单的整形变量。可以接收IRQ_NONE和IRQ_HANDLED,这取决于处理程序是否处理了该IRQ。

            在执行所有处理程序例程期间,内核将返回结果用逻辑“或”操作合并起来。内核最后可以据此判断IRQ是否被处理。

    四、总结

            中断这块的内容大概说完了,不过这里也只是从硬件得到响应后的硬件中断,因为在开始也说过,由于CPU的资源宝贵,而在中断处理期间又不能有任何的抢占操作,所以在硬件中断的过程中,各个中断处理例程只是把一些必须的操作在这里面执行完,然后就把控制权交回,真正做后续处理的是后面将介绍的软中断。下一篇,将要针对软中断进行一些介绍。

    展开全文
  • 中断中断处理程序1 中断2 中断处理程序3 注册中断处理程序4 编写中断处理程序共享的中断处理程序中断处理程序实例5 中断上下文6 中断处理机制的实现7 中断控制禁止和激活中断禁止指定中断线中断系统的状态 1 中断 ...

    Linux内核要对连接到计算机上的所有硬件设备进行管理,要与它们进行通信。但是,处理器的速度跟外围硬件设备的速度往往不是一个数量级的,硬件的响应很慢,内核应该在此期间处理其他事物,等到硬件真正完成了请求的操作之后,再回过头来对它进行处理。中断机制让硬件在需要的时候再向内核发出信号,内核来处理硬件的请求。

    1 中断

    中断使得硬件得以与处理器进行通信。硬件设备生成中断的时候并不考虑与处理器的时钟同步,换句话说,中断随时都可以产生,因此,内核随时可能因为新到来的中断而被打断。

    从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。

    不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些中断值通常被称为中断请求(IRQ)线。特定的中断总是与特定的设备相关联,并且内核要知道这些消息。

    异常

    异常与中断不同,它在产生时必须考虑与处理器时钟同步。异常也常称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况(例如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。

    2 中断处理程序

    在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程(interrupt service routine,ISR)。产生中断的每个设备都有一个相应的中断处理程序。中断处理程序通常不是和特定设备关联,而是和特定中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动程序也就需要准备多个这样的函数。

    在Linux中,中断处理程序看起来就是普普通通的C函数,只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递程序的信息。中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行在我们称之为中断上下文的特殊上下文中。

    上半部与下半部的对比

    一般把中断处理切为两个部分,中断处理程序是上半部,接受到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接受的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会被推迟到下半部去。此后,在合适的时机,下半部就开中断执行,Linux提供了实现下半部的各种机制,下一篇文章会讨论,现在我们要记住上半部是中断处理程序,只做有严格时限的工作。

    3 注册中断处理程序

    中断处理程序是驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。

    驱动程序可以通过下面的函数注册并激活一个中断处理程序:

    /* request_irq分配一个中断线 */
    int request_irq(unsigned int irq,
    				irqreturn_t (*handler)(int,void *,struct pt_regs *),
    				unsigned long irqflags,
    				const char * devname,
    				void *dev_id)
    

    第一个参数irq表示要分配的中断号。对某些设备,如传统PC设备上的系统时钟或键盘,这个值通常是预先定死的。而对于大多数其他设备,这个值要么是可以通过探测获取,要么可以通过编程动态确定。

    第二个参数handle是实际的中断处理程序。只要操作系统一接收到该中断,该函数就被调用。

    第三个参数irqflags可以为0,也可能是下列一个或多个标志的位掩码:

    • SA_INTERRUPT:此标志表明给定的中断处理程序是一个快速中断处理程序。在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行
    • SA_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献。内核熵池负责提供从各种随机事件导出的真正的随机数。
    • SA_SHIRQ:此标志表明可以在多个中断处理程序之间共享中断线。

    第四个参数devname是给中断相关的设备起的名字。这些名字会被/proc/irq和/proc/Interrupt文件使用,以便与用户通信。

    第五个参数dev_id主要用于共享中断线。如果无需共享中断线,那么将该参数赋值为NULL就可以了,但是,如果中断线是被共享的,那么必须传递唯一的信息,用来区分用的是共享中断线上的那个中断处理程序。实践中往往会通过它传递驱动程序的设备结构。

    request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中用该函数,至于request_irq()会睡眠,那是在注册的过程中,内核需要在/proc/irq文件创建一个中断对应的项,会调用kmalloc()来请求分配内存,函数kmalloc()是可以睡眠的。

    在一个驱动程序中请求一个中断线,并在通过request_irq()注册中断处理程序:

    if(request_irq(irqn,my_interrupt,SA_SHIRQ,"my_device",dev))
    {
    	printk(KERN_ERR "my_device:connot register IRQ %d \n",irqn);
    	return -RIO;
    }
    

    irqn是请求的中断线,my_interrupt是中断处理程序,中断线可以共享,设备名为"my_device",而且我们通过dev_id传递dev结构体。

    释放中断处理程序

    卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线。可以调用void free_irq(unsigned int irq,void *dev_id)来释放中断线。
    如果指定的中断线不是共享的,那么,该函数删除中断处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个程序程序时才会被禁用。

    4 编写中断处理程序

    以下是一个典型的中断处理程序声明:

    static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs);
    

    它的类型与request_irq()参数中handler所要求的参数类型相匹配。第一个参数irq就是这个中断处理程序要响应的中断的中断线号。第二个参数dev_id是用一个通用指针,它与中断处理程序注册时传递给request_irq()的采纳数dev_id必须一致,可以用来区分共享同一个中断处理程序的多个设备,最后一个参数regs是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试的时候,它们很少使用到。

    中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值,IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生的中断,返回IRQ_HANDLED。

    重入和中断处理程序

    Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接受到另一个新的中断。

    共享的中断处理程序

    共享中断线的处理程序和非共享中断线的处理程序在注册和运行方式上比较相似,但主要有以下差异:

    • request_irq()的flags参数必须设置SA_SHIRQ标志
    • 对每个注册的中断处理程序来说,dev_id必须是唯一的。指向任意设备结构的指针就可以满足这一要求,通常会选择设备结构,因为它是唯一的,而且中断处理程序可能会用到它。不能共享就传NULL
    • 中断处理程序必须能区分它的设备是否真的产生了中断,

    中断处理程序实例

    让我们考察一个实际的中断处理程序,它来自RTC(real_time clock)驱动程序,可以在drivers/char/rtc.c中找到。RTC驱动程序装载时,rtc_init()函数会被调用,对这个驱动程序进行初始化。它的职责之一就是注册中断处理程序:

    /*
    	 * XXX Interrupt pin #7 in Espresso is shared between RTC and
    	 * PCI Slot 2 INTA# (and some INTx# in Slot 1). SA_INTERRUPT here
    	 * is asking for trouble with add-on boards. Change to SA_SHIRQ.
    	 */
    	if (request_irq(rtc_irq, rtc_interrupt, SA_INTERRUPT, "rtc", (void *)&rtc_port)) {
    		/*
    		 * Standard way for sparc to print irq's is to use
    		 * __irq_itoa(). I think for EBus it's ok to use %d.
    		 */
    		printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
    		return -EIO;
    	}
    

    中断线号由rtc_irq指定,rtc_interrupt是我们的中断处理程序,驱动程序的名称为rtc,因为这个设备不允许共享中断线,且处理程序没有用到什么特殊的值,因此给dev_id的值是NULL。

    5 中断上下文

    当执行一个中断处理程序或下半部时,内核处于中断上下文中。进程上下文是一种内核所处的操作模式,此时内核代表进程执行,在进程上下文中,可以通过current宏关联到当前进程,此外,因为进程是以进程上下文的形式连接到内核的,因此,在进程上下文可以睡眠,也可以调用调度程序。

    中断上下文和进程并没什么瓜葛。因为没有进程的背景,所有中断上下文不可睡眠

    中断处理程序栈的设置是一个配置选项。它们共享所属中断进程的内核栈。内核栈的大小是两页。因为这种设置,中断处理程序共享别人的堆栈,所以它们从栈中获取内容非常节省空间。

    6 中断处理机制的实现

    中断处理在linux中的实现非常依赖体系结构,实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计以及机器本身。
    下图是中断从硬件到内核的步骤。
    在这里插入图片描述
    设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到中断处理程序的入口点去运行。

    对于每条中断线,处理器都会跳到对应的唯一的位置。这样,内核就可知道所接收中断的IRQ号了。入口点只是在栈中保存这个号,并存当前寄存器的值;然后,内核调用函数do_IRQ()。

    do_IRQ()的声明如下:

    unsigned int do_IRQ(struct pt_regs regs);
    

    因为C的调用惯例是要把函数参数放在栈的顶部,因此pt_regs结构包含原始寄存器的值,这些值是以前在汇编入口例程中保存在栈的的,中断的值也会得到保存,所以,do_IRQ()可以将它提取出来。

    计算出中断号后,do_IRQ()对所接收的中断进行应答,禁止这条线上的中断传递。do_IRQ()需要确保在这条中断线上有一个有效的处理程序,而且这个程序已经启动但是当前没有执行。如果是这样的话,do_IRQ()就调用handle_IRQ_event()来运行为这条中断线所安装的中断处理程序,在kernel/irq/handle.c文件中

    /*
     * Have got an event to handle:
     */
    fastcall int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
    				struct irqaction *action)
    {
    	int ret, retval = 0, status = 0;
    
    	if (!(action->flags & SA_INTERRUPT))
    		local_irq_enable();
    
    	do {
    		ret = action->handler(irq, action->dev_id, regs);
    		if (ret == IRQ_HANDLED)
    			status |= action->flags;
    		retval |= ret;
    		action = action->next;
    	} while (action);
    
    	if (status & SA_SAMPLE_RANDOM)
    		add_interrupt_randomness(irq);
    	local_irq_disable();
    
    	return retval;
    }
    

    7 中断控制

    Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些接口是与体系有关的,可以在<asm/system.h>和<asm/irq.h>中找到。

    禁止和激活中断

    用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句是:

    local_irq_disable();
    local_irq_enable();
    

    这两个函数通常以单个汇编指令来实现(取决于体系结构)。实际上,在x86中,local_irq_disable()仅仅是cli指令,而local_irq_enable()只不过是sti指令。

    local_irq_disable是禁止当前处理器上的所有中断,local_irq_enable是开启当前处理器上的所有中断,即使在禁止前有些中断是关闭的,在调用local_irq_enable()后也会被激活。所以我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。在禁止之前保存当前的中断系统,激活时恢复就行。

    unsigned long flags;
    lcoal_irq_save(flags)
    local_irq_restore(flags);
    

    flags必须是unsigned long类型。local_irq_save () 是保存本地中断传递的当前状态,然后禁止本地中断传递。local_irq_restore () 是恢复本地中断到flags的状态。

    禁止指定中断线

    在某些情况下,只禁止整个系统中一条特定的中断线就够了。为此,Linux提供了四个接口:

    void disable_irq(unsigned int irq);
    void disable_irq_nosync(unsigned int irq);
    void enable_irq(unsigned int irq);
    void synchronize_irq(unsigned int irq);
    

    disable_irq和disable_irq_nosync禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。我们来看看disable_irq的具体实现:

    /**
     *	disable_irq - disable an irq and wait for completion
     *	@irq: Interrupt to disable
     *
     *	Disable the selected interrupt line.  Enables and disables
     *	are nested.  This functions waits for any pending IRQ
     *	handlers for this interrupt to complete before returning.
     *	If you use this function while holding a resource the IRQ
     *	handler may need you will deadlock.
     *
     *	This function may be called - with care - from IRQ context.
     */
    void disable_irq(unsigned int irq)
    {
    	struct irqdesc *desc = irq_desc + irq;
    
    	disable_irq_nosync(irq);
    	if (desc->action)
    		synchronize_irq(irq);
    }
    

    此函数在返回之前等待中断irq的任何挂起的 中断处理程序完成。disable_irq会阻塞,所以不能在在中断处理函数中使用,如果在中断处理程序中使用,会导致死锁,电脑会死机。

    disable_irq_nosync不会阻塞,会立即返回,可在中断处理函数中使用。

    enable_irq函数的参数是int型变量,代表操作中断对应的中断号

    函数synchronize_irq等待一个特定的中断处理程序的退出,所以该函数也会阻塞。

    这些函数的调用都可以嵌套,但要记住在一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正重新激活了中断线。例如,如果disable_irq()被调用了两次,那么直达第二次调用enable_irq()后,才能真正地激活中断线。

    禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此,用于新设备的驱动程序应该尽量不使用这些接口。

    中断系统的状态

    宏irqs_disable()定义在<asm/system.h>中,如果本地处理器上的中断系统被禁止,则它返回非0,否则返回0.
    在<asm/hardirq.h>中定义了的两个宏提供了一个用来检查内核的当前上下文的接口,它们是:

    in_interrupt();
    in_irq();
    

    in_interrupt用来检测内核是否处于中断上下文中,如果是的话,返回非0。说明此刻内核正在执行中断处理程序,或者正在执行下半段处理程序,如果返回0,此时内核处于进程上下文中。宏in_irq只有在内核确实正在执行中断处理程序时才返回非0.

    8 总结

    中断就是由硬件打断操作系统。内核提供的接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检查中断系统的状态。
    因为中断打断了其他代码的执行(进程,内核本身,甚至其他中断处理程序),它们必须赶快执行完。为了大量的工作与必须快速执行完之间求得平衡,内核把处理中断的工作分为两部分。中断处理程序,也就是上半部,下半部我们还没有讨论。

    展开全文
  • 一、中断   中断分为上半部和底半部。上半部也就是硬中断,软中断...基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)   2、软中断是一组静

    一、中断

      中断分为上半部和底半部。上半部也就是硬中断,软中断只是底半部的一种实现机制
      上半部主要处理有严格时限的工作,比如读取寄存的中断状态,清除中断标志,将底半部处理程序挂到底半部的执行队列中去
    底半部执行大部分耗时的工作,并且可以被其他中断打断
      1、硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)
      2、软中断是一组静态定义的下半部分接口,可以在所有的处理器上同时执行,即使两个类型相同也可以。但是一个软中断不会抢占另外的一个软中断,唯一可以抢占软中断的硬中断。可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。

    二、中断函数
    1.开关中断

    /************关闭指定中断*************************/
    void enable_irq(unsigned int irq)
    void disable_irq(unsigned int irq) //要等到当前正在执行的中断处理函数执行完才返回
    void disable_irq_nosync(unsigned int irq)  //函数调用以后立即返回
    
    /**************硬中断****************************/
    local_irq_disable():关闭中断
    local_irq_enable():开启中断
    local_irq_save(flags):关闭中断并保存中断标志位
    local_irq_restore(flags):开启中断并清除中断标志位
    
    /*************底半部开关************************/
    local_bh_disable();
    local_bh_enable();
    
    /*************判断中断状态*********************/
    #define in_interrupt() (irq_count())   // 是否处于中断状态(硬中断或软中断)
    #define in_irq() (hardirq_count())     // 是否处于硬中断
    #define in_softirq() (softirq_count()) // 是否处于软中断
    

      disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
      当任务A调用local_irq_disable企图关闭中断1s,然后再开启中断,但在中断中出现了另一个优先级更高的任务B也调用了local_irq_disable关闭中断,然后100ms后执行local_irq_enable开启中断,导致任务A的中断没有持续1s就被打开了,这显然是有问题的,所以需要用到后两个中断函数,当B任务关闭中断的时候保存中断标志位,开启中断的时候清除中断标志位,还原之前的中断状态,才能使任务A继续处于中断状态中正确执行。

    2、申请中断

    int request_irq(unsigned int irq, irq_handle_t handle, unsigned int flags, 
                    const char *name, void* dev_id);
    
    
    int devm_request_irq(struct device *dev, unsigned int irq, irq_handle_t handle, 
                    unsigned int flags, const char *name, void* dev_id);
    

    其中flags代表中断标志,可以取值如下:
      上升沿触发:IRQF_TRIGGER_RISING
      下降沿触发:IRQF_TRIGGER_FALLING
      高电平触发:IRQF_TRIGGER_HIGH
      低电平触发:IRQF_TRIGGER_LOW
      共享中断:IRQF_SHARED

      dev_id代表要传给中断处理程序的私有数据,一般是这个设备的结构体或者NULL
      devm_request_irq和request_irq区别在于前者是申请的内核“managed”资源,不需要自己手动释放,会自动回收资源,而后者需要手动调用free_irq来释放中断资源

    3、释放中断

    void free_irq(unsigned int irq, void *dev_id);
    

    二、底半部实现机制:软中断、tasklet、工作队列、线程化irq

      1、软中断一般不使用,由于必须要设计为可重入函数,导致复杂度高,并且无法睡眠、不可阻塞,无法重新调度。
      软中断应该保留给系统中对时间要求最严格的下半部使用。目前只有两个子系统直接使用软中断:网络和SCSI。此外,内核定时器和tasklet也是基于软中断实现的。由于同一个软中断可以在不同处理器上同时执行,所以软中断要求对共享资源的处理要非常严格,锁保护的要求很高。

      2、tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。
    tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者唯一的区别在于,HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。

    tasklet_struct my_tasklet;
    void my_tasklet_func(unsigned long);
    //将my_tasklet和my_tasklet_func绑定关联
    DECLARE_TASKLET(my_tasklet, may_tasklet_func, data);
    
    void my_tasklet_func(unsigned long)
    {
        ...
    }
    
    irqreturn_t my_tasklet_demo_interrupt(int irq, void *dev_id)
    {
        ...
        //执行tasklet_shedule调度,会在适当的时候执行my_tasklet_func
        tasklet_shedule(&my_tasklet);
        ...
        return IRQ_HANDLED;
    }
    
    int __init my_tasklet_demo_init(void)
    {
        ...
        int result = request_irq(irq_num, my_tasklet_demo_interrupt, IRQF_SHARED, "my_tasklet_demo", dev_id);
        ...
        return IRQ_HANDLED;
    }
    
    void __exit my_tasklet_demo_exit(void)
    {
        ...
        free_irq(irq_num, my_tasklet_demo_interrupt);
        ...
    }
    

      3、工作队列可以把工作推后,交由一个内核线程去执行。内核线程只在内核空间运行,没有自己的用户空间,它和普通进程一样可以被调度,也可以被抢占。该工作队列总是会在进程上下文执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势,最重要的就是工作队列允许重新调度甚至是睡眠。
      理论上可以用创建内核线程的方式来代替工作队列,但是由于随便创建内核线程会带来其他问题,所以实际上并不建议直接创建内核线程,而应使用工作队列的形式。
      内核中可以创建workqueue队列,将work_struct工作加入workqueue,也可以不加,会默认挂载到内核的工作队列,对应的函数有create_singlethread_workqueue(const char* name)和create_workqueue(const char* name) ,前者只是在当前CPU创建一个work_thread线程去执行工作队列里的work_struct,而后者是在每个CPU上都创建一个work_thread,使用queue_work(struct workqueue_struct *wq, struct work_struct *work)唤醒队列执行,destroy_workqueue(struct workqueue_struct *wq)释放workqueue队列

    struct work_struct my_workqueue;
    void my_wq_func(struct work_struct* work);
    
    /*中断底半部工作队列的处理*/
    void my_wq_func(struct work_struct* work)
    {
        ...
    }
    
    irqreturn_t my_wq_interrupt(int irq, void *dev_id)
    {
        ...
        schedule_work(&my_workqueue);
        ...
        return IRQ_HANDLED;
    }
    
    int __init my_wq_init(void)
    {
        ...
        int result = request_irq(irq_num, my_wq_interrupt, IRQF_TRIGGER_RISING, "my_wq", dev_id);
        //初始化工作队列,并将my_workqueue和my_wq_func处理函数关联
        INIT_WORK(&my_workqueue, my_wq_func);
        ...
        return 0;
    }
    
    void my_wq_exit(void)
    {
        ...
        free_irq(irq_num, my_wq_interrupt);
        ...
    }
    

      4、threaded_irq:内核提供request_threaded_irq和devm_request_threaded_irq为中断分配内核线程,原型如下:

    int request_threaded_irq(unsigned int irq, 
                        irq_handle_t* handle, 
                        irq_handle_t* thread_func, 
                        unsigned long flags, const char* name, void* dev_id);
    
    int devm_request_threaded_irq(struct device* dev, unsigned int irq, 
                        irq_handle_t* handle, 
                        irq_handle_t* thread_func, 
                        unsigned long flags, const char* name, void* dev_id);
    

      其中handle对应的函数执行于中断上下文,thread_func对应的函数执行于内核线程,如果handle的返回值是IRQ_WAKE_THREAD,内核会自动调度对应的线程执行thread_func函数
      若flags中设置IRQF_ONESHOT标志,内核会自动在中断上下文中屏蔽该中断号,防止上半部无法清除中断,导致上半部一退出,瞬间大量中断同时发生导致异常。

    三、中断里不能睡眠

      睡眠会导致进程切换,中断上下文没有进程的概念,中断有自己的内核栈,进程使用的task_struct结构体信息,schedule调度就是去访问这些task_struct,抢占一个进程运行另一个进程,所以中断里无法进行系统调度,如果在中断里睡眠,进程将无法被调度,从而引发内核崩溃
      假设中断里可以睡眠,也就是可以进行进程调度,如果此时内核正执行到临界区,处于加锁状态(一般是自旋锁),切换另一个进程执行到此处无法加锁,就会等待下去,造成死锁。
      用口头语说,就是当程序在运行的时候,突然来个中断,当然是希望你赶紧把中断处理完了继续运行我的程序,要是中断里还睡眠,那我的程序还咋运行了?

    No pains, no gains

    展开全文
  • cortex-m3内核中断

    2020-03-07 12:32:33
    m3内核中有一个R/W“中断寄存器阵列”,该阵列记录了外部从0到239个中断的名字PRI_0-239,中断寄存器地址,中断的优先级(8位,stm32用了其中的高4位,复位之后是0)。如下: 另外还有一个应用中断控制与复位寄存器R...
  • Linux内核中断系统

    万次阅读 2020-07-20 13:18:15
    点击上方蓝色字关注我们!前言 中断在驱动中是非常常用的,无论是外部的GPIO中断,还是SPI,I2C等发送或接收中断,都是必不可少的。所以今天来看看Linux中的中断处理。中断分类...
  • 图解Linux内核中断子系统

    千次阅读 2019-08-15 18:19:29
    Irq number:内核中断号 中断的概念 ARM工作模式 7 Processor modes: User mode SVC mode FIQ mode IRQ mode Undef mode Abort mode Hyp mode 中断发生时芯片会进入 IRQ mode ,当FIQ快速...
  • 内核IRQ中断向量

    千次阅读 2019-08-01 19:45:11
    首先看一下vector_irq的定义,此每处理器数组变量,保存每个处理器上中断向量所对应的中断号,其以中断向量值为索引。系统中定义了256个中断向量。相关代码如下: typedef int vector_irq_t[NR_VECTORS]; DEFINE_...
  • 用户态和内核中断处理机制

    千次阅读 2021-01-15 22:57:54
    1.1 用户态和内核态 1.1.1 定义:什么是用户态和内核态? Kernel 运行在超级权限模式(Supervisor Mode)下,所以拥有很高的权限。按照权限管理的原则,多数应用程序应该运行在最小权限下。因此,很多操作系统,将...
  • 文章目录1 中断1.1 中断控制器1.2 中断域1.3 中断控制器驱动初始化1.4 Linux中断处理1.5 中断线程化1.6 禁止/开启中断1.7 禁止/开启单个中断1.8 中断亲和性1.9 处理器间中断 重要:本系列文章内容摘自<Linux内核...
  • 一、进程调度、 二、内存管理、 三、中断管理、 四、设备管理、 五、文件系统、
  • linux内核学习10:中断和异常

    千次阅读 2021-10-12 16:14:54
    一、中断 1.1 中断的概念 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续...
  • 硬件中断发生频繁,是件很消耗 CPU 资源的事情,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的 CPU (core) 处理显然能很好的平衡性能。 现在的服务器上动不动就是多 CPU 多核、多网卡、多硬盘,如果能让...
  • 常见的Linux内核中内存分配

    千次阅读 2018-08-07 21:56:09
     Linux内核中采 用了一种同时适用于32位和64位系统的内 存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系 统中,用到了四级页表,如图2-1所示四级页表分别为:  * 页全局目录(Page Global ...
  • 内核自动探测中断号

    千次阅读 2015-12-03 22:09:11
    我们来看short_kernelprobe函数如何实现由内核自动探测中断号的:[cpp] view plaincopy466void short_kernelprobe(void) 467{ 468 int count = 0; 469 do { 470 unsigned long mask; 471 472 mask...
  • linux内核网络收包
  • 如何在内核中断上下文中分配内存

    千次阅读 2013-05-14 10:59:22
    判断一个内存分配函数能否用在中断上下文中,主要看这个内存函数最终调用的伙伴系统算法的函数接口page_alloc(gfp_mask,order)的gfp_mask的内容,用在中断上下文中一般是GFP_ATOMIC,否则通常GFP_KERNEL作为内存...
  • Linux内核笔记之中断映射

    千次阅读 2020-01-18 19:26:18
    GIC硬中断和软中断的映射过程
  • linux中断--内核中断编程

    千次阅读 2014-04-14 19:24:44
    在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解...
  • Linux内核中断系统处理机制-详细分析

    万次阅读 多人点赞 2018-08-23 23:09:24
    Linux内核中断 一、中断概述 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再...
  • 1、环境: 1、平台:mtk 2、内核版本:kernel-4.14 3、通用性说明:各平台通用,属于内核的gpio子系统 2、dts的配置 ...//中断号----用于映射中断号 deb-gpios = <&pio 9 0>;//gpio 编号 deboun
  • 浅谈用户空间和内核空间内存分配

    千次阅读 2018-06-12 10:01:24
    一、用户空间动态申请内存:1、malloc: malloc分配的内存大小至少为size参数所指定的字节数 malloc的返回值是一个指针,指向一段可用内存的起始地址 多次调用malloc所分配的地址不能有重叠部分,除非某次malloc...
  • 【摘要】本文叙述了在Linux内核中常见的几种内存分配函数及其异同,对理解linux底层内存分配机制有个较好理解。 1、kmalloc() kmalloc()函数类似与我们常见的malloc()函数,前者用于内核态的内存分配,后者用于用户...
  • Linux内核使用中断的方式来管理硬件设备,中断本质上是一种电信号,设备通过和中断控制器引脚相连的总线发出电信号来发出中断中断控制器是一种控制芯片,多个设备的中断请求线同时连接到中断控制器上...
  • Linux 中断 —— GIC (中断号映射)

    千次阅读 2019-05-28 00:05:18
    在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断: 1、IRQ number。CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识...
  • 1)CM3一共支持256个中断,其中16个内核中断和240个外部中断。 2)STM32没有使用完CM3的所有中断,共有84个中断,其中内核16,外部68,具有16级可编程的中断优先级 其中我们经常用的是68个可屏蔽的中断。 3)STM...
  • 第五课. 内核中断系统中的设备树

    千次阅读 2018-11-22 14:30:57
    中断体系在4.x内核中变化很大,中断体系又跟pinctrl系统密切相关,pinctrl中又涉及GPIO子系统,这样讲下去的话,设备树课程就变成驱动专题了,所以我打算只讲中断体系统,对于pinctrl、gpio等系统留待以后在驱动课程...
  • Linux(内核剖析):19---中断总体概述

    千次阅读 2020-01-12 13:39:37
    一、为什么要引入中断? 任何操作系统内核的核心任务,都包含有对连接到计算机上的硬件设备进行有效管理,如硬盘、蓝光碟机、键盘、鼠标、3D 处理器,以及无线电等。而想要管理这些设备,首先要能和它们互通音信才...
  • Linux 内核中断

    千次阅读 2015-06-19 10:52:02
    1.中断引入?  硬件如果要和cpu进行通信有那么几种方法?(把这个过程比作是老师分苹果的故事)  有程序查询法,中断,DMA,通道等。  程序查询法就是老师每一个小孩子都去问,他们的苹果有没有吃完了

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 118,108
精华内容 47,243
热门标签
关键字:

内核中断号分配

友情链接: C++Designprogram1.rar