精华内容
下载资源
问答
  • 一,认识几个重要结构体:1.中断描述符对于每一条中断线都由一个irq_desc结构来描述。//在include/linux/irq.h中struct irq_desc { unsigned intirq;//中断号 struct timer_rand_state *timer_rand_state; unsigned ...

    一,认识几个重要结构体:

    1.中断描述符

    对于每一条中断线都由一个irq_desc结构来描述。

    //在include/linux/irq.h中

    struct irq_desc {  unsigned int irq;//中断号  struct timer_rand_state *timer_rand_state;  unsigned int *kstat_irqs; #ifdef CONFIG_INTR_REMAP  struct irq_2_iommu *irq_2_iommu; #endif

    irq_flow_handler_t handle_irq;//上层中断处理函数,  struct irq_chip *chip;//底层硬件操作 struct msi_desc *msi_desc;  void *handler_data;//附加参数,用于handle_irq  void *chip_data;//平台相关附加参数,用于chip  struct irqaction *action; //中断服务例程链表  unsigned int status; //中断当前的状态

    //中断关闭打开层数调用一次disable_irq( ) ,depth加1;调用一次enable_irq( )该值减1,

    //如果depth等于0就开启这条中断线,如果depth大于0就关闭中断线。

    unsigned int depth; unsigned int wake_depth; * 唤醒次数 */  unsigned int irq_count; /* 发生的中断次数 */  unsigned long last_unhandled; unsigned int irqs_unhandled;  spinlock_t lock; #ifdef CONFIG_SMP  cpumask_var_t affinity;  unsigned int cpu; #ifdef CONFIG_GENERIC_PENDING_IRQ  cpumask_var_t pending_mask; #endif #endif  atomic_t threads_active;  wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS  struct proc_dir_entry *dir;///proc/irq/ 入口 #endif  const char *name;///proc/interrupts 中显示的中断名称 } ____cacheline_internodealigned_in_smp;

    2. 中断硬件操作函数集

    //在include/linux/irq.h中定义

    //该结构体中各函数在文件linux/arch/arm/plat-s3c24xx/irq.c中实现

    struct irq_chip {  const char *name; //用于 /proc/interrupts unsigned int (*startup)(unsigned int irq); //默认为 enable 如果为NULL  void (*shutdown)(unsigned int irq); //默认为 disable 如果为NULL void (*enable)(unsigned int irq); //允许中断,默认为 unmask 如果为NULL  void (*disable)(unsigned int irq); //禁止中断,默认为 mask 如果为 NULL

    void (*ack)(unsigned int irq); //响应一个中断,清除中断标志  void (*mask)(unsigned int irq); //mask 一个中断源,通常是关闭中断  void (*mask_ack)(unsigned int irq); //响应并 mask 中断源  void (*unmask)(unsigned int irq); //unmask 中断源  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); //设置中断触发方式 IRQ_TYPE_LEVEL  int (*set_wake)(unsigned int irq, unsigned int on);

    #ifdef CONFIG_IRQ_RELEASE_METHOD  void (*release)(unsigned int irq, void *dev_id); #endif  const char *typename; };

    3.中断处理例程描述符

    //在include/linux/interrupt.h中

    struct irqaction {  irq_handler_t handler; unsigned long flags; //用一组标志描述中断线与 I/O 设备之间的关系。  cpumask_t mask;  const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir;  irq_handler_t thread_fn;  struct task_struct *thread;  unsigned long thread_flags; };

    这三个结构体间的关系表示如下

    0818b9ca8b590ca3270a3433284dd417.png

    二,中断初始化过程

    中断机制的初始化通过 两个函数完成:early_trap_init()和init_IRQ(),在此我们先讨论函数init_IRQ()。

    //函数

    init_IRQ在文件linux/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

    cpumask_setall(bad_irq_desc.affinity);

    bad_irq_desc.cpu = smp_processor_id();

    #endif

    init_arch_irq();

    }

    //函数s3c24xx_init_irq在文件linux/arch/arm/plat-s3c24xx/irq.c中实现

    void __init s3c24xx_init_irq(void)

    {

    unsigned long pend;

    unsigned long last;

    int irqno;

    int i;

    irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");

    last = 0;

    for (i = 0; i < 4; i++) {

    pend = __raw_readl(S3C24XX_EINTPEND);

    if (pend == 0 || pend == last)

    break;

    __raw_writel(pend, S3C24XX_EINTPEND);

    //清除外部中断寄存器EINTPEND中的请求标志,

    printk("irq: clearing pending ext status x\n", (int)pend);

    last = pend;

    }

    last = 0;

    。。。。。。

    //设置各中断的底层硬件操作函数集desc->chip,中断上层处理函数desc->handle_irq

    for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {

    switch (irqno) {

    case IRQ_EINT4t7:

    case IRQ_EINT8t23:

    case IRQ_UART0:

    case IRQ_UART1:

    case IRQ_UART2:

    case IRQ_ADCPARENT:

    set_irq_chip(irqno, &s3c_irq_level_chip);

    set_irq_handler(irqno, handle_level_irq);

    break;

    case IRQ_RESERVED6:

    case IRQ_RESERVED24:

    break;

    default:

    //irqdbf("registering irq %d (s3c irq)\n", irqno);

    set_irq_chip(irqno, &s3c_irq_chip);

    set_irq_handler(irqno, handle_edge_irq);

    set_irq_flags(irqno, IRQF_VALID);

    }

    }

    //以下几个中断都有多个中断源,每一个中断源也都有各自的中断号,它们的多个中断源中任意一个产生中断

    //该中断都会被触发,而不是直接出发子中断。这几个中断并不处理中断函数,它们的中作是计算子中断的中断号,

    //并根据子中断的中断号在数组irq_desc[NR_IRQS]中去找出该中断号对应的irq_desc结构,并调用该结构中的中断处理函数。

    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);

    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

    set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);

    set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);

    set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);

    set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

    。。。。。。

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

    }

    。。。。。。

    }

    比如此时外部中断10产生了中断,中断号为IRQ_EINT8t23的中断被触发,执行函数s3c_irq_demux_extint8()。

    static void s3c_irq_demux_extint8(unsigned int irq,  struct irq_desc *desc) {  unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);  unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

    eintpnd &= ~eintmsk;  eintpnd &= ~0xff;

    while (eintpnd) {  irq = __ffs(eintpnd); //计算该中断在外部中断寄存器EINTPEND中的偏移量  eintpnd &= ~(1<

    irq += (IRQ_EINT4 - 4); //根据这个偏移量重新计算中断号  generic_handle_irq(irq);//根据重新计算的中断号获取对应的结构体irq_desc,并调用它的上层中断处理函数。  }

    }

    static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc) { #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ  desc->handle_irq(irq, desc); //直接调用上层中断处理函数 #else  if (likely(desc->handle_irq))  desc->handle_irq(irq, desc);  else  __do_IRQ(irq); //通用中断处理函数,该函数最终调用desc->handle_irq(irq, desc); #endif }

    上层中断处理函数

    上层中断处理函数有5个分别为:handle_simple_irq(),handle_level_irq(), handle_edge_irq(),handle_fasteoi_irq()以及handle_percpu_irq()。

    这几个函数在文件kernel/irq/chip.c中实现。常用的有两个handle_level_irq(),和handle_edge_irq()。

    这5个上层中断处理函数都是通过调用函数handle_IRQ_event()来做进一步处理。

    irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) {  irqreturn_t ret, retval = IRQ_NONE;  unsigned int status = 0;

    if (!(action->flags & IRQF_DISABLED))  local_irq_enable_in_hardirq();

    do {

    。。。。。。

    ret = action->handler(irq, action->dev_id); //执行中断处理函数。

    。。。。。。

    retval |= ret;  action = action->next;  } while (action); //调用该中断线上的所有例程

    if (status & IRQF_SAMPLE_RANDOM)  add_interrupt_randomness(irq);  local_irq_disable();

    return retval; }

    void handle_level_irq(unsigned int irq, struct irq_desc *desc) {  struct irqaction *action;  irqreturn_t action_ret;

    。。。。。。

    desc = irq_remap_to_desc(irq, desc);

    。。。。。。

    action = desc->action;

    action_ret = handle_IRQ_event(irq, action);

    。。。。。。

    }

    void handle_edge_irq(unsigned int irq, struct irq_desc *desc) {  spin_lock(&desc->lock);

    。。。。。。  desc = irq_remap_to_desc(irq, desc); 。。。。。。  desc->status |= IRQ_INPROGRESS;

    do {  struct irqaction *action = desc->action;  。。。。。。

    desc->status &= ~IRQ_PENDING;  spin_unlock(&desc->lock);  action_ret = handle_IRQ_event(irq, action);  if (!noirqdebug)  note_interrupt(irq, desc, action_ret);  spin_lock(&desc->lock);

    //该函数与函数handle_level_irq不太一样的是,该函数多了一个循环。即如果在本次中断

    //的处理过程中该中断线上又有中断产生,则再次执行该中断线上的处理例程

    #define IRQ_DISABLED 2

    #define IRQ_PENDING 4 #define IRQ_REPLAY 8 #define IRQ_WAITING 32

    */

    } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

    desc->status &= ~IRQ_INPROGRESS; out_unlock:  spin_unlock(&desc->lock); }

    展开全文
  • 原标题:Linux中断机制:硬件处理,初始化中断处理来源: CSDN | phenix_lord的专栏硬件处理最近解决一关于Linux中断的问题,把相关机制整理了一遍,记录在此。不同的外部设备、不同的体系结构、不同的OS其中断...

    原标题:Linux中断机制:硬件处理,初始化和中断处理

    来源: CSDN | phenix_lord的专栏

    硬件处理

    最近解决一个关于Linux中断的问题,把相关机制整理了一遍,记录在此。

    不同的外部设备、不同的体系结构、不同的OS其中断实现机制都有差别,本文对应的OS为linux3.4版本,外部设备为PCI设备、系统为X86。

    概览

    中断让外设能够通知CPU他需要获得服务(让CPU执行指定的中断服务例程ISR)。为了达到这个目的,首先要为中断执行做好准备,完成初始化相关的操作。包括:

    1、 初始化中断控制器等相关器件(OS初始化过程中完成);

    2、 配置并使能外部设备(比如使用pci_enable_msix),得到irq号;在这个操作过程中,内核需要完成的大致操作是:

    1、 确定该中断的执行CPU,并在对应CPU上建立vector和irq号的对应关系(利用全局per-cpu变量vector_irq),配置中断控制器(I/OAPIC、PIR等),可能还需要设置外部设备(比如设置MSI

    Capacity registers);

    2、 为对应的irq_desc初始化正确的handle_irq接口(通用逻辑接口);

    3、 为对应的irq_desc初始化正确的底层chip操作接口。

    3、 使用request_irq号为该中断号指定一个服务例程;

    完成了以上的初始化操作,在外设中断到来的时候,为该中断指定的ISR(Interrupt Service Routines)就能得到执行,这个执行过程大致如下:

    1、 外设根据各自的配置,产生中断信号或者中断消息(MSI,INT# message)。

    2、 中断控制器从外设获取中断电信号或者中断消息,把它翻译为vector(CPU使用这个参数来决定是谁发生了中断,要如何处理)并提交到CPU。

    3、 对X86系统,CPU利用从中断控制器获取到的vector为索引,查询IDT (interrupt deor table)得到该中断的处理接口(对linux,是在entry_64.s中定义的函数common_interrupt接口)并执行。

    4、 在linux定义的common_interrupt接口中,执行完中断执行环境建立后,会进入generic interrupt layer执行,其首先通过vector查找到irq和对应的irq_desc结构,并执行该结构的handle_irq接口,这个接口就是generic interrupt layer的通用逻辑接口,比如handle_edge_irq/handle_level_irq等;在中断执行的通用逻辑接口中,会通过irq_desc::action调用外设指定的ISR。

    在linux中可以通过/proc/interrupts查看当前系统中所有中断的统计信息,在/proc/irq/xxx(中断号)下面,可以看到该中断的详细信息。

    中断相关硬件

    这里的描述很多来自INTEL的文档《Intel Software developer’s Manual, system programming guide》和《PCI Express System Architecture》

    中断控制器

    中断控制器的功能是:把外设的中断信号,转换成CPU能够明白的vector,并完成中断执行控制,确保在合适的时机把中断提交给CPU执行。对这部分内容,《interrupt in linux》有详细的描述。

    1、 8259A:

    每个8259A有8个管脚,每个管脚对应其连接的CPU的IDT中的一个vector,单独使用8259A,其硬件连线就决定了对设备vector的使用。典型的场景是使用两个8259A级联,理论最多16个中断号(就是ISA IRQs),实际能提供对15个中断线的处理(master的IRQ2用于连接slave),其具体的分配见下图。

    2、 PIR:

    用于完成输入的信号到输出信号的映射。在下图中PIR被用于完成多个PCI设备的INT#信号到8259A对应引脚的路由。对应这种连接方式,在PCI设备初始化的时候,OS会根据BISO提供的信息设置PIR,把INT#路由到O0-O3中正确的管脚,从而体现到8259A的正确管脚(对应了vector),这样INT#信号就被转换为vector并提交到CPU。由于可能有较多的PCI设备,而PIR的输入/出错管脚有限,所以连接到相同输入关键的INT#会共享一个中断。

    7b6a87a4ef3cfc9e4306d4d1786d3637.png

    3、 I/O APIC

    每个I/O APIC提供24个管脚,能够和外部设备的中断线连接,每个管脚都可以通过配RTE(Redirection table entry)配置对应的vector。其功能是:把外部设备的中断请求,翻译为local APIC的interrupt message,并按照配置的vector,发送给指定的local APIC处理(在SMP系统,存在多个CPU,也就有多个local APIC)。通常的配置方式是:第一个I/O APIC的前16个管脚,配置来处理之前的ISA IRQs,其它外设比如PCI设备,则直接使用其他管脚连接。

    4、 local APIC

    其负责处理IPI(inter-process interrupt)、直接连接的中断处理、接收和处理interrupt message,每个CPU有自己的local APIC。

    对应I/O APIC和local APIC的组合,其连接方式见下图

    f6cab52a340f4e0d42b24be61f64c49c.png

    针对X86中断控制器硬件和linux对这些硬件的初始化,在《interrupt in linux》中有很详细的描述。

    X86对中断的处理

    Local APIC的处理过程

    每个local APIC对应了一个CPU。其处理interrupt message的过程如下:

    1、 判断该中断的destination是否为当前APIC,如果不是则忽略,否则继续处理

    2、 如果是SMI/NMI/INIT/ExtINT, or SIPI(这些中断都负责特殊的系统管理任务,外设一般不会使用)被直接送到CPU执行,否则执行下一步。

    3、 设置Local APIC 的IRR寄存器的对应bit位。

    4、 如果该中断优先级高于当前CPU正在执行的中断,且当前CPU没有屏蔽中断(按照X86和LINUX的实现,这时是屏蔽了中断的),则该高优先级中断会中断当前正在执行的中断(置ISR位,并开始执行),低优先级中断会在高优先级中断完成后继续执行,否则只有等到当前中断执行完成(写了EOI寄存器)后才能开始执行下一个中断。

    5、 在CPU可以处理下一个中断的时候,从IRR中选取最高优先级的中断,清0 IRR中的对应位,并设置ISR中的对应位,然后ISR中最高优先级的中断被发送到CPU执行(如果其它优先级和屏蔽检查通过)。

    6、 CPU执行中断处理例程,在合适的时机(在IRET指令前)通过写EOI寄存器来确认中断处理已经完成,写EOI寄存器会导致local APIC清理ISR的对应bit,对于level trigged中断,还会向所有的I/O APIC发送EOI message,通告中断处理已经完成。

    说明:

    1、 关于Local APIC的IRR和ISR

    寄存器interrupt request register (IRR) 和 in-service register (ISR),都是256bit寄存器,每个bit对应一个中断(其中[0-15]不能使用,SMI/NMI/INIT/ExtINT/SIPI的发送和执行不经过ISR和IRR) 。IRR中保存的是已经被local APIC接纳但是还没有开始执行的中断;ISR中保持的是当前正在执行但是还没有完成的中断。

    2、 中断优先级

    对应通过local APIC发送到CPU的中断,按照其vector进行优先级排序:

    优先级=vector/16

    数值越大,优先级越高。由于local APIC允许的vector范围为[16,255],而X86系统预留了[0,31]作为系统保留使用的vector,实际的用户定义中断的优先级的取值范围为[2,15],在每个优先级内部,vector的值越大,优先级越高。

    Local APIC中还有一个关于中断优先级的寄存器TPR(task priority register)寄存器:用于确定打断线程执行需要的中断优先级级别,只有优先级高于设置值的中断才会被CPU执行 (SMI/NMI/INIT/ExtINT, or SIPI不受限制),也就是除了特殊中断外,优先级低于TPR指定值的中断将被忽略。

    3、 中断的pending

    对于同一个vector,如果有多次中断请求,可能IRR和ISR对应的bit位都被置位,也就是对同一个vector,local APIC可以pending两个中断,其后的即使有多处,也会被合并为一个执行。

    4、 中断执行时机

    中断的执行总是在指令边界开始(只有一个特殊的exception:abort在外,出现了这个中断,系统基本上也就完蛋了),也就是中断不可能打断指令的执行。

    CPU对中断和异常的处理

    相关概念

    1、 vector(中断向量)

    vector是一个整数,在X86CPU上,使用vector对中断(interrupt,外部设备产生)和异常(exception,CPU在程序执行中产生)统一编号,每个CPU核心内部,中断/异常和vector所以一一对应的;但是在各个不同的CPU核心上,相同的vector可以对应不同的中断(至少对于linux的设置,异常还是使用相同的vector)。

    vector的取值范围为[0,255],其中[0,31]被系统保留使用(多数作为异常的vector),其余的可供外设中断使用(系统设备比如local APIC也占用了部分[32,255]这个范围的vector)。

    2、 IDT(interrupt deor table)

    X86 CPU采用一个有256个元素的数组来描述中断/异常,该数组的index为vector;其内容包括了三种gate deor,用于描述一个中断/异常的处理接口;这个数组就是IDT,CPU在收到中断请求的时候,就利用vector获取到对应的中断处理接口描述并执行。

    3、 可屏蔽中断

    通过CPU INTR管脚/local APIC接收到的中断是可屏蔽中断,这些中断能够通过清零EFLAGS的IF来屏蔽(CLI指令)。通过INT n指令生成的中断即使使用了和外部中断一样的vector,也是不可屏蔽的;同样CPU运行过程中同步产生的trap、fault、abort等异常也是不可屏蔽的。

    4、 NMI

    NMI是不可屏蔽中断(不可通过IF标志屏蔽),是通过CPU的NMI管脚发出的中断或者通过delivery mode为NMI的方式提交的中断。NMI中断在执行前,CPU不仅会屏蔽其它中断,也会屏蔽NMI中断,直到NMI中断处理执行完成(IRET指令被执行)。使用INT 2指令虽然能执行NMI中断处理函数,但是相关硬件不会介入,也就是没有相关的屏蔽NMI中断的操作。

    CPU执行中断的过程

    1、 利用vector,查IDT得到中断描述符;

    2、 如果中断发生在用户态,会首先执行stack switch切换到内核态执行;

    3、 依次保存EFLAGS CS IP到当前栈,如果需要(有error code的异常),把error code PUSH到当前栈。并把IF/TF位清零屏蔽可屏蔽中断;至此,CPU完成了中断处理程序执行环境的建立。

    4、 执行中断描述符定义的中断处理入口(IDT中指定地址的代码);

    5、 根据环境执行不同的中断退出方式,比如执行现场调度操作(retint_careful和retint_kernel),最终都会执行IRET指令;至此,中断执行完成。

    异常的执行过程类似,只不过异常在执行前不会把IF位清零,只清零TF位。

    PCI设备的中断

    本部分的很多内容来自《PCI Interrupts for x86 Machines under FreeBSD》和《PCI Express®

    Base Specification Revision 3.0》和《PCI Express System Architecture》。

    PCI设备的中断有两种模式:一种是INT#模式,一种是MSI模式。

    INT#模式

    每个PCI设备用四个中断信号,对应INTA#、INTB# INTC#、INTD#,这些中断信号采用level trigger 的方式并且为低电平有效,PCI设备通过拉低对应的信号来assert对应的中断,并在ISR访问PCI设备的指定寄存器deassert该中断。

    中断线和X86系统的连接

    这里存在两种常见连接模式,一种是使用老的8259A+PIR的系统,一种是使用新的I/O APIC的系统。

    对于使用8259A的系统:PCI的中断线连接到一个可编程的PIR设备,再通过该设备连接到8259A(见X86中断控制器一章的图);对于采用I/OAPIC的系统,可以使用以下的连接方式,同样这里只画出了一个中断线,同时根据不同的系统配置可能存在多个I/OAPIC。除了采用直接的中断引脚连接,PCI还支持virtual INT#,使用INT# message(Assert INT# message和deassert INT# message)的方式来使用INT#信号。

    828fa01910460651d26013a395e85883.png

    NT#模式的局限

    1、 中断数量有限且不方便扩展:每个物理的PCI设备,最多只有4个中断但是至少能支持8个function,且系统中可能存在多个PCI设备,不得不使用中断共享的模式,影响使用性能。

    2、 同步问题:由于INT#中断采用的是side channel,中断信号和数据本身存在不同步的问题:可能在中断到达的时候,对应的数据没有达到,为了处理这个问题,一般采用“读刷新”的做法,也就是在使用该设备写入到X86的数据之前,ISR先对这个设备进行一次读操作来确保相关数据已经写入完成,比如读PCI设备的中断状态寄存器等。

    MSI/MSI-X模式

    在这种模式下,PCI设备通过和数据DMA一样的通道来完成中断处理,通过向特定地址空间(系统FSB Interrupt存储器空间)发起一个写操作来发起中断。该写操作的地址和数据信息在PCI设备初始化MSI功能的时候已经填写到MSI Capacity registers(MSI模式)/MSI-X table(MSI-X)中(对X86,这个地址空间是FEE00000H开始的地址空间,其实就是local APIC寄存器映射的地址空间),地址信息保存在Message address register,其中包含了目标CPU信息和FSB Interrupt存储器空间;数据中包含了该MSI中断对应的vector,保存在Message data register中。 MCH(memory control hub)截获这个写操作,转换为FSB interrupt message并向各个CPU核心广播,local APIC接收并处理这个消息,最终触发CPU的中断处理过程。使用这种机制,中断的数量不受PIR/ IOAPIC等各种器件管脚数量的限制,MSI可以支持32个中断,而MSI-X可以达到2048个;中断的传递相当直接,省略了中断路由的过程;并且能直接从interrupt message中获取vector信息,减少了交互过程。

    初始化

    相关概念和关键数据结构

    1、 irq号:在当前系统中全局唯一,对应内核数据结构struct irq_desc,每个外设的中断有一个irq号(体系结构预留的中断,是没有对应的irq_desc结构和irq号的),该irq在该中断的生命周期内都不会改变,且和该中断的中断处理函数关联;内核使用一个bitmap allocated_irqs来标识当前系统已经分配的irq;irq号的管理与底层中断设备和配置无关,属于Generic Interrupt Layer;对于irq号分布集中的情况,不配置CONFIG_SPARSE_IRQ,内核采用数组直接管理,数组下标就是irq号;而对于irq号比较分散的,设置CONFIG_SPARSE_IRQ,内核采用radix tree来管理所有的irq号。

    2、 vector号:内核使用全局bitmap used_vectors来标识那些vector被系统预留,不能被外设分配使用。

    3、 irq号和vector号的关联:内核中使用per-cpu变量vector_irq来描述irq号和vector号的关联,对每个CPU,vector_irq是一个数组,在X86架构下成员数量为256,其数组的index为vector,值为irq,如果为-1则表示该CPU上的这个vector尚未分配。

    4、 struct irq_desc结构,用来描述一个中断,是内核generic interrupt layer的关键数据结构,其包含了中断的大部分信息,并连接了driver层和物理中断设备层,每个irq号对应一个该结构,共享相同irq号的中断共享该结构。它的关键成员包括:

    a) irq_data :为该中断对应的物理中断设备层相关的数据。

    b) handle_irq:为该该中断使用的通用逻辑接口。

    c) action:为driver层提供的ISR信息,其为一个单向链表结构,所有共享该中断的设备的ISR都链接在这里。

    内核关键数据结构和相关初始化

    对X86 CPU,Linux内核使用全局idt_table来表达当前的IDT,该变量定义在traps.c

    gate_desc idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化为全0。

    对中断相关的初始化,内核主要有以下工作:

    1、 设置used_vectors,确保外设不能分配到X86保留使用的vector(预留的vector范围为[0,31],另外还有其他通过apic_intr_init等接口预留的系统使用的vector);

    2、 设置X86CPU保留使用的vector对应的IDT entry;这些entry使用特定的中断处理接口;

    3、 设置外设 (包括ISA中断)使用的中断处理接口,这些中断处理接口都一样。

    4、 设置ISA IRQ使用的irq_desc;

    5、 把IDT的首地址加载到CPU的IDTR(Interrupt Deor Table Register);

    6、 初始化中断控制器(下一章描述)

    以上工作主要在以下函数中完成:

    ee88cd13da54c947d1306d00d2d7a3c2.png

    可以看到,这个过程会完成每个中断vector对应的idt entry的初始化,系统把这些中断vector分成以下几种:

    1、X86保留vector,这些vector包括[0,0x1f]和APIC等系统部件占用的vector,对这些vector,会记录在bitmap used_vectors中,确保不会被外设分配使用;同时这些vector都使用各自的中断处理接口,其中断处理过程相对简单(没有generic interrupt layer的参与,CPU直接调用到各自的ISR)。

    2、ISA irqs,对这些中断,在初始化过程中已经完成了irq_desc、vector_irq、以及IDT中对应entry的分配和设置,同时可以发现ISA中断,在初始化的时候都被设置为运行在0号CPU。

    3、其它外设的中断,对这些中断,在初始化过程中仅设置了对应的IDT,和ISA中断一样,其中断处理接口都来自interrupt数组。

    中断处理接口interrupt数组

    interrupt数组是内核中外设中断对应的IDT entry,其在entry_64.S中定义,定义如下:

    39765218668bf0107b4f6256f8d16396.png

    这段汇编的效果是:在代码段,生成了一个符号irq_entries_start,该符号对应的内容是一组可执行代码,一共(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7组,每组为7个中断入口,为:

    f93a970f1a992bea3c304951db146007.png

    每组的最后一个中断入口不需要jmp 2f是因为其pushq_cfi(就是pushq咯)下面就

    是2f这个标号的地址了。(不明白的是:为什么不在jmp 2f的地方直接写上jmp common_interrupt?非要jmp 2f,2f的地方再次jmp common_interrupt?)

    而interrupt是一个数组,该数组在初始化完成后释放,其每个数组项都是一个地址,是对应的“pushq_cfi”代码的地址(每个代表中断入口的标号)。系统在初始化的时候,对外设使用interrupt数组作中断处理接口,就是在中断发生时,执行代码段:

    7f2561ff7a551a7129369330ec6936c7.png

    初始化中断控制器

    对中断控制器的使用基本上有三种机制:

    1、 中断路由表 $PIR

    struct irq_routing_table,该结构用于使用PIR和8259A的系统,在微软的文献《PCI IRQ Routing Table Specification》中描述了该结构详细信息。其描述了一个PCI设备的INT#是如何连接到PIR设备的输入端口的。其关键数据是一个可变长的struct irq_info数组,每个struct irq_info描述了一个PCI物理设备的4个INT#相关的中断路由信息和对应可用的ISA IRQs的bitmap。BIOS根据相关设备的物理连接填写该数据结构,OS在设备初始化过程中使用这些信息为使用INT#的设备分配对应的vector和irq。

    2、 MP table

    struct mpc_intsrc,该数据结构用于使用I/O APIC的系统中,描述系统中所有PCI设备4个INT#信号和I/O APIC输入引脚的对应关系。该数据结构的srcbus成员为对应PCI设备的bus id;srcbusirq描述了一个INT#信号,其bit0-bit1用于描述是INTA#–INTD#中的哪一个(对应值为0-3),bit2-bit6描述该PCI设备的slot id。dstapic为该描述对应的I/O APIC的ID。dstirq描述srcbus和srcbusirq确定的INT#对应的irq号信息(具体的解析有多种情况)。在系统中有一个以该数据结构为成员的全局数组mp_irqs,用于管理系统中所有的硬件中断信号和irq之间的关联。对MP table及其使用的更加详细的描述,见《Multiprocessor Specification v1.4》

    3、 ACPI(Advanced Configuration and PowerInterface)机制

    这种机制为I/O APIC机制和中PIR机制提供统一的管理界面,该机制使用struct acpi_prt_entry描述INT#和GSI(能和vector、irq对应)的关系,系统中所有的struct acpi_prt_entry由OS从BIOS提供的信息中获取,并保存在链表acpi_prt_list中。

    注:对GSI的说明,GSI(global system interrupt)表示的是系统中中断控制器的每个输入管脚的唯一编号,在使用ACPI模式管理中断控制器的时候使用。对使用8259A的系统,GSI和ISA IRQ是一一对应的。对于使用APIC的,每个I/O APIC会由BISO分配一个基址,这个base+对应管脚的编号(从0开始)就是对应的GSI。通常是基址为0的I/O APIC的前16个管脚用于ISA IRQS,对GSI更加详细的描述,见《Advanced Configuration and Power Interface Revision 2.0》

    除了中断路由表,其它两种机制的初始化(包括相关中断路由信息的初始化)的在《interrupt in linux》中有很详细的描述。这些初始化操作都在内核初始化的时候完成。

    为PCI设备配置中断

    为PCI设备配置中断,分为两个步骤,

    步骤一:为设备分配irq号(对MSIX,会有多个),为该中断分配执行CPU和它使用的vector,并通过对中断控制器的设置,确保对应的中断信号和vector匹配。对于使用INT#类型的中断,通常通过pci_enable_device/pci_enable_device_mem/pci_enable_device_io中对函数pcibios_enable_device的调用来完成(只有在没有开启MSI/MSIX的时候才会为INT#做配置),而要配置MSI/MSIX中断要使用的是pci_enable_msix。

    步骤二:request_irq为该设备的irq指定对应的中断处理例程,把irq号和驱动定义ISR关联。

    pcibios_enable_device

    该接口用于使能PCI设备INT#模式的中断。其主要功能由pcibios_enable_irq(dev)完成,pcibios_enable_irq是一个函数指针,对于ACPI模式,其在上电过程中被设置为acpi_pci_irq_enable,其它情况被设置为pirq_enable_irq。

    对ACPI模式,其执行过程为:

    1、 acpi_pci_irq_enable:其先根据设备的管脚信息获取一个GSI(可以认为有了GSI,就有了irq号,gsi_to_irq可以完成其转换),有了gsi/irq,要完成设置还必须有vector并且把它们关联起来,因此如果GSI获取成功,会使用acpi_register_gsi来完成后续操作。

    2、 acpi_register_gsi:其主要功能由__acpi_register_gsi来完成,该函数指针在ACPI模式下被设置为acpi_register_gsi_ioapic,acpi_register_gsi_ioapic的执行过程如下:

    mp_register_gsi===>io_apic_set_pci_routing===>io_apic_set_pci_routing===>io_apic_setup_irq_pin_once===>io_apic_setup_irq_pin===>setup_ioapic_irq,在setup_ioapic_irq中,就会利用assign_irq_vector为该irq选择对应的执行CPU,并分配该CPU上的vector,同时还把该vector等配置写入到I/O APIC对应管脚的RTE,从而完成整个中断的配置。这样在该INT#信号到来的时候,I/O APIC就能根据对应管脚的RTE,把该信号翻译为一个vector,并通过中断消息发送到local APIC。同时在setup_ioapic_irq中,还通过ioapic_register_intr===>irq_set_chip_and_handler_name为得到的irq号对应的irq_desc设置了->irq_data.chip和handle_irq函数指针(对level触发的,为handle_fasteoi_irq,否则为handle_edge_irq)

    对其它模式,其通过pcibios_lookup_irq完成执行:

    在配置了I/O APIC的场景,pirq_enable_irq通过IO_APIC_get_PCI_irq_vector获取到irq号,然后和ACPI模式一样,通过io_apic_set_pci_routing完成对I/O APIC的配置。而对没有配置I/O APIC的场景,主要通过pcibios_lookup_irq来完成相关操作:

    1、 pcibios_lookup_irq通过读取BIOS提供的中断路由表 ($PIR表,irq_routing_table)信息和当前irq分配情况(pirq_penalty数组),在考虑均衡的前提下为当前设备分配一个可用的irq。

    2、 根据当前PIR的相关信息,决定最终的irq号选择,相关代码行如下

    97e4aaef0ff8b3b17f4b5734830854cb.png

    也就是:如果是硬链接(INT#直接连接到了8259A,没有经过PIR),直接获取irq号,如果PIR中已经有该输入线的配置,使用已有的值,否则利用刚刚分配的可用irq,并写入到PIR,以便能够完成中断信号到irq号的转换。

    注意:

    1、这里的r,也就是pirq_router,代表一种PIR硬件,全局配置pirq_routers中描述了当前支持的PIR,并在初始化的时候通过pirq_find_router获取了对应当前配置的PIR对应的描述。

    2、这里没有分配vector,是因为这里使用的irq号范围为0-16,是ISA IRQs,其与vector的对应关系简单:vector = IRQ0_VECTOR + irq,并在系统初始化过程中,已经通过early_irq_init中分配了irq_desc结构,通过init_IRQ设置了vector_irq(只运行于CPU0上),然后通过x86_init.irqs.intr_init(native_init_IRQ)===> x86_init.irqs.pre_vector_init(init_ISA_irqs)设置了->irq_data.chip(i8259A_chip)和handle_irq函数指针(handle_level_irq)。

    Pci_enable_msix

    该函数完成MSIX中断相关的设置。

    adaab560a416f7d5087f1d80d88e9417.png

    msix_capability_init中实现中断初始化的是arch_setup_msi_irqs,对于X86系统,其为x86_setup_msi_irqs,x86_setup_msi_irqs中直接调用了native_setup_msi_irqs,该函数是X86系统中实现MSIX中断初始化的关键函数,对于没有启用interrupt remap的系统,其实现如下:

    c7762c7f1f8078645937915d2fb50177.png

    该函数中有两个关键函数,分别是create_irq_nr和setup_msi_irq,其中create_irq_nr是分配一个vector给当前的中断,分配vector的同时,也为该中断指定了执行CPU。setup_msi_irq则负责把相关配置信息写入到PCIE配置区,并设置irq_desc的数据,其中关键的是irq_desc的handle_irq被设置为handle_edge_irq。

    create_irq_nr的实现如下:

    6db9c1e9799d931941d1ba0b1a00f600.png

    其中__assign_irq_vector负责分配vector,并和中断在CPU上的调度相关,其实现如下

    3143e1dd6c3b3ab6c42d80187e75e20a.png

    e663243736b364e4bea5fb22b8617ac1.png

    1dd7d3962c07ca073f966972fa060ecd.png

    从实现中可以看到,该函数从FIRST_EXTERNAL_VECTOR(外设中断的起始vector号,通常是0x20) 到first_system_vector(外部中断结束vector号,通常是254,255被系统作为保留的SPURIOUS_APIC_VECTOR使用)的范围中,为当前中断分配一个vector,要求该vector在对应的cpu上均可用,该vector按照系统配置的要求和对应的cpu核心绑定,并在要求的cpu中没有被其它中断使用。需要说明的是,在setup_msi_irq中会再次通过msi_compose_msg再次调用__assign_irq_vector,但是由于这时已经存在满足CPU绑定要求的vector,不会多次分配。

    从以上分析可以得到MSI-X中断的一个绑定特征:根据当前APIC配置,每个中断都有对应的可以运行的cpu,pci_enable_msix在这些要求的cpu核心上建立了vector (APIC的配置由数据结构struct apic来抽象,其vector_allocation_domain用于决定需要在那些cpu核心上为该中断建立vector),当前我的系统使用的是apic_physflat,对每个MSI中断,其只在一个cpu核心上建立vector,对应的MSI-X中断事实上被绑定到该cpu核心上。在用户通过echo xxx > /proc/irq/xxx/affinity来调整中断的绑定属性时,内核会重新为该中断分配一个新的在对应核心上可用的vector,但是irq号不会改变。绑定属性调整的调用路径大致为irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>__irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最终通过msi_set_affinity来实现,在该函数中首先通过 __ioapic_set_affinity在绑定属性要求的cpu中选择空闲vector,然后通过__write_msi_msg把配置写入PCIE配置区。需要说明的是:该irq最终可以运行的cpu数量并不完全由用户指定,还与apic的模式相关,对于apic_physflat,实际上只为该irq分配了一个cpu核心,该irq只能运行在用户指定的cpu中的一个,而不是全部。

    附:关于全局变量apic

    该全局变量为local apic的抽象,在不同的系统配置下,有不同的选择,其最终的选择结果,由内核的config(反应在/arch/x86/kernel/apic/Makefile)和硬件配置等来决定。

    1、 定义各种apic driver

    首先,每种apic配置都会使用apic_driver/ apic_drivers来定义,apic_driver的定义如下

    d523e816887c145af35ee17c6cda5d46.png

    这个定义的目的是把sym的地址写入到名为” .apicdrivers”的段中。

    2、 定义全局符号__apicdrivers和__apicdrivers_end

    在linker vmlinux.lds.S中,定义了__apicdrivers为” .apicdrivers”段的开始地址,而__apicdrivers_end为结束地址。” .apicdrivers”段中是各个不同的apic配置对应的struct apic。

    8cc54b443e296df3bbcc9d672d4029df.png

    3、 apic的probe

    在初始化过程(start_kernel)中,会调用default_setup_apic_routing(probe_64.c中定义)来完成apic的probe,该函数会按照各个struct apic结构在.apicdrivers中的顺序,依次调用其probe接口,第一个调用返回非0的struct apic结构就被初始化到全局变量apic。也就是:如果有多个apic结构可用,最终会选择在.apicdrivers段中出现的第一个;所以makefile文件中各个.o出现的顺序也会觉得最终的apic probe结果。

    request_irq

    该函数把irq和用户指定的中断处理函数关联。用户指定的每个处理函数对应于一个struct irqaction结构,这些处理函数构成一个链表,保存在struct irq_desc::action成员中。详细见request_irq===>request_threaded_irq中的处理。

    中断的执行

    在内核代码中,对X86平台中断执行的基本过程是:

    1、 通过IDT中的中断描述符,调用common_interrupt;

    2、 通过common_interrupt,调用do_IRQ,完成vector到irq_desc的转换,进入Generic interrupt layer(调用处理函数generic_handle_irq_desc);

    3、 调用在中断初始化的时候,按照中断特性(level触发,edge触发等、simple等)初始化的irq_desc:: handle_irq,执行不同的通用处理接口,比如handle_simple_irq;

    4、 这些通用处理接口会调用中断初始化的时候注册的外部中断处理函数;完成EOI等硬件相关操作;并完成中断处理的相关控制。

    common_interrupt

    按照之前CPU执行中断过程的描述,X86 CPU在准备好了中断执行环境后,会调用中断描述符定义的中断处理入口;根据中断相关初始化过程我们知道,对于用户自定义中断,中断处理入口都是(对系统预留的,就直接执行定义的接口了):

    848161c933dd35eb7d3798eb779481ae.png

    就是在把vector入栈后,执行common_interrupt,common_interrupt在entry_64.S中定义,其中关键步骤为:调用do_IRQ,完成后会根据环境判断是否需要执行调度,最后执行iretq指令完成中断处理,iret指令的重要功能就是回复中断函数前的EFLAGS(执行中断入口前被入栈保存,并清零IF位关中断),并恢复执行被中断的程序(这里不一定会恢复到之前的执行环境,可能执行软中断处理,或者执行调度)。

    do_IRQ

    do_IRQ的基本处理过程如下,其负责中断执行环境建立、vector到irq的转换等

    06311771b3fe7ae19ce7b9d1beb6a827.png

    Generic interrupt layer

    该层负责的是平台无关/设备无关的中断通用逻辑,对这部分,在《Linux generic IRQ handling》中有详细描述。其负责完成中断处理的接口是generic_handle_irq_desc,该接口会执行irq_desc::handle_irq; Generic interrupt layer根据中断特性的不同,把中断分成几类,包括:level type(handle_level_irq)、edge type(handle_edge_irq)、simple type(handle_simple_irq)等,这些中断类型对应的处理函数是都在kernel/irq/chip.c中定义,并入前面的描述,在相关中断初始化的时候,被赋值给irq_desc::handle_irq;对于PCI设备,只用了两种,level type(INT#模式)、edge type(MSI/MSI-X模式)。

    edge 触发中断的基本处理过程:

    电压跳变触发中断===>中断控制器接收中断,记IRR寄存器===>中断控制器置ISR寄存器===>CPU屏蔽本CPU中断===>CPU处理中断,发出EOI===>中断控制器确认可以处理下一次中断===>ISR清中断源,电压归位===>中断源可以发起下一次中断===>CPU中断处理完成,执行完现场处理后执行IRET,不再屏蔽本CPU中断。

    edge触发的特点:

    a) 中断不会丢

    如果中断触发时中断被屏蔽,那么中断控制器会记录下该中断,在屏蔽取消的时候会再执行。

    b) edge触发的缺点是完成共享不方便:

    比如A和B两个中断源共享一个中断,每次ISR先检查A再检查B,如果B先发生中断,在ISR检查完A,检查B的过程中,A发生中断。那么在ISR处理开始的时候,A会告诉ISR,不是它干的,然后ISR处理B的中断,完成后通过清理中断源把B的电压归位,但是由于A的中断没有得到处理,电压没有归位,这个共享的中断就不能得到再次触发了。

    edge触发对应的通用逻辑接口

    045b7512e7deccd432da221234e6833f.png

    level 触发:

    这种模式下,外设通过把电压保持到某个门限值来完成触发中断,在处理完成(EOI)后,如果电压还在门限值,就会再次触发中断的执行。

    level触发的特点:

    a) 方便中断共享

    b) 对中断触发时中断被屏蔽的情况,如果中断屏蔽解除后仍然引脚电压仍然在门限值,就执行该中断的ISR,否则不执行。

    需要说明的是:对于使用local APIC的系统,level触发和edge触发需要配置local APIC的Local Vector Table。

    4、 level触发对应的通用逻辑接口

    1a1cfaea64a9abd7c91db5d997018931.png

    level触发和edge触发在通用逻辑层最大的不同就是当其他CPU正在处理该中断的时候,系统的行为,对edge触发,会把该中断记录下来,当前处理结束后再次执行,而level直接退出。产生这种差异的原因是:level触发不怕丢?

    无论是那种触发方式,都会调用handle_irq_event处理中断,该函数中会遍历irq_desc::action链表,执行action->handler,也就是驱动在中断初始化的时候,通过request_irq注册的中断处理接口。

    总结 中断的使能状态

    1、 在local APIC层次(当前CPU),一个中断正在处理的时候,不会有相同的中断或者优先级低于该中断的其它中断来打断当前中断的执行;但是高优先级中断可以打断低优先级中断。

    2、 在X86 CPU层次(当前CPU),从中断执行开始到IRET,IF位都被清零,也就是只有不可屏蔽中断能够打断当前中断的执行。

    3、 在Generic interrupt layer层次,如果一个中断已经在系统中执行,会阻止该中断在其它CPU上的执行。

    4、 在外设/驱动中断处理函数层次往往也有中断使能的功能,比如启用了NAPI的网卡,在中断处理函数开始执行的时候,往往会通过硬件功能关闭该中断,要在对应的软中断完成处理后才通过硬件功能使能该中断。

    注:NMI中断虽然称为不可屏蔽中断,也有一个例外:NMI中断执行过程中,该CPU屏蔽了后来的NMI中断。

    中断的执行CPU

    通过中断初始化过程我们知道:中断在那个CPU上执行,取决于在那个CPU上申请了vector并配置了对应的中断控制器(比如local APIC)。如果想要改变一个中断的执行CPU,必须重新申请vector并配置中断控制器。一般通过echo xxx > /proc/irq/xxx/affinity来完成调整,同时irq_balance一类软件可以用于完成中断的均衡。

    责任编辑:

    展开全文
  • (2)init_IRQ才是中断初始化。 2、trap_init都干了啥呢? void __init trap_init(void) { .... 把向量表拷贝到ffff0000地址 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); .....

    1、Linux内核从start_kernel函数开始进行初始化

    (1)trap_init主要就是异常的初始化。

    (2)init_IRQ才是中断的初始化。 

    注意:ffff0000并不对应实际的物理内存,是虚拟地址。当建立地址映射之后,就需要把物理地址对应的向量表拷贝到虚拟地址ffff0000

     2、trap_init都干了啥呢?

    void __init trap_init(void)
    {
    .... 把向量表拷贝到ffff0000地址
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    .... 把复杂向量表拷贝到ffff0200
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    }

    3、__vectors_start和__stubs_start分别对应什么东西
    ①发生异常先到vectors地址找跳转代码
    __vectors_start: 第一阶段的跳转
         swi SYS_ERROR0 /* 复位时,CPU将执行这条指令 */
         b vector_und + stubs_offset /* 未定义指令 */
         ldr pc, .LCvswi + stubs_offset /* swi异常 */
         b vector_pabt + stubs_offset /* 指令预取中止 */
         b vector_dabt + stubs_offset /* 数据访问中止 */
         b vector_addrexcptn + stubs_offset /* 没有用到 */ 
         b vector_irq + stubs_offset /* irq异常 */
         b vector_fiq + stubs_offset /* fiq异常 */

    ②上面这些表示发生异常时,要跳转去执行的代码。(举例 vector_und说明)stubs实际上一个宏,宏值里面处理了很多事情。
    vector_und, UND_MODE /* "vector_stub" 是一个宏,根据 "und, UND_MODE" 定义一段代码*/
    上面这段代码会处理很多东西,最后决定跳转到底下的哪个分支。
     /* 下面这些代码是跳转去执行更复杂的代码 */
         .long __und_usr @ 0 (USR_26 / USR_32) /* 用户模式下执行了未定义指令 */
         .long __und_invalid @ 1 (FIQ_26 / FIQ_32)
         .long __und_invalid @ 2 (IRQ_26 / IRQ_32)
         .long __und_svc @ 3 (SVC_26 / SVC_32) /* 在管理模式下执行了未定义指令 */

         ...

    ③__und_usr以后又会跳转到可执行的C处理函数(其他异常处理同理)
    __und_usr: /* __und_usr 用户模式下发生未定义指令异常的处理代码 */ 
            usr_entry 
            tst r3, #PSR_T_BIT @ Thumb mode?
            bne __und_usr_unknown @ ignore FP //跳转到__und_usr_unknown 
            sub r4, r2, #4 
            ........ //下面还有一些汇编指令

    __und_usr_unknown: 
            mov r0, sp /* 将栈顶地址,作为参数传入 */ 
            adr lr, ret_from_exception /* 将返回地址写入到 r0, 处理完C函数后将返回到这里 */
            b do_undefinstr /*C函数入口,处理未定义指令异常*/

    同理:vector_irq最终跳转到asm_do_IRQ函数

    以上2、3说明了两件事情:(1)trap_init的异常初始化只是把向量表(就是好几个跳转指令)复制到高地址ffff0000; (2)异常发生后,会直接跳到异常向量表里去寻找对应的分支,例如中断的话最终就跳到了asm_do_IRQ函数。也就是说上述两件事情完全是两码事情,一个是初始化,一个动作发生后的处理。

    3、中断最终跳转到asm_do_IRQ函数
    使用中断结构体数组irq_desc[]来描述一个中断,这样在初始化start_kernel的时候就可以

     中断处理的流程:
    ①发生中断时,cpu响应执行异常向量表__vectors_start  
    ②接着到--》vector_irq中断代码(在这里会计算返回值,保存一些寄存器,进入管理模式)
    ③vector_irq最后会调用中断处理总入口asm_do_IRQ
    ④asm_do_IRQ根据中断号调用irq_desc[??]数组项的处理函数handle_irq
    ⑤handle_irq接着会调用irq_desc[??]中chip成员设置硬件,比如清除中断,禁止中断,重新使能中断
    ⑥handle_irq会逐个调用action成员链表中注册的处理函数

    4、再回到初始化init_IRQ
    void __init init_IRQ(void)
    {
        for (irq = 0; irq < NR_IRQS(宏值16); irq++) 将每个中断结构体数组元素都初始化状态
         {

            irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

         }
        init_arch_irq();这个地方一个全局函数的地址。初始化为空,因为这里应该是初始化为架构相关的初始化函数,这里是中断处理的初始化函数s3c24xx_init_irq, 这里确实没看懂??
    }
    void (*init_arch_irq)(void) __initdata = NULL;  谁把这函数地址赋值s3c24xx_init_irq函数?????
    5、先跳过去,看看s3c24xx_init_irq都干了啥?这里也就是芯片侧所需要的处理
    void __init s3c24xx_init_irq(void)
    {
    ....
        for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23(宏值); irqno++) {
            irqdbf("registering irq %d (extended s3c irq)\n", irqno);
            set_irq_chip(irqno, &s3c_irqext_chip);初始化设置chip,以后就可以设置irq_desc[??].chip成员了
            set_irq_handler(irqno, handle_edge_irq);初始化设置处理函数,**发生中后handle_edge_irq会调用用户注册的函数了
            set_irq_flags(irqno, IRQF_VALID);设置它生效
        }
    }

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

    void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, const char *name)
    {
        desc->handle_irq = handle;也就说最终irq_desc[???].hander_irq 指向了-->handle_edge_irq才是最终的处理函数
        desc->name = name;
    }

    6、内核侧已经将handle_edge_irq的处理过程定义到了action中的处理函数了
    static struct irq_chip s3c_irqext_chip = {  全局结构体
        .name        = "s3c-ext",
        .mask        = s3c_irqext_mask,
        .unmask        = s3c_irqext_unmask,
        .ack        = s3c_irqext_ack,
        .set_type    = s3c_irqext_type,
        .set_wake    = s3c_irqext_wake
    };

    void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
    {
            /* Start handling the irq */
            desc->chip->ack(irq);
            /* Mark the IRQ currently in progress.*/
            desc->status |= IRQ_INPROGRESS;
            do {
                        struct irqaction *action = desc->action;循环处理每个action的处理函数
                        action_ret = handle_IRQ_event(irq, action);

            } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
    }


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

        return retval;
    }

    7、用户注册(驱动程序)中断处理函数的过程
    用户通过request_irq函数向内核注册中断函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表中添加一个选项。linux-2.6.22\kernel\irq\manage.c
    int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
    {
             去构建struct irqaction结构体成员
            action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
            action->handler = handler;
            action->flags = irqflags;
            cpus_clear(action->mask);
            action->name = devname;
            action->next = NULL;
            action->dev_id = dev_id;

            retval = setup_irq(irq, action);
    }
    int setup_irq(unsigned int irq, struct irqaction *new)
    {
    ①将新建的链表结构action成员加入链表,如果链表空就直接加入,否则先判断新建的action和链表中的action结构体中段类型是否一致;
    是否都生命了共享中断,触发方式(flags),如果一致就加入链表
    ②设置irq_desc[irq]结构中的chip成员还没设置的指针,让他们指向默认函数。
    ③设置中断触发方式desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
    ④启用中断desc->status & IRQ_NOAUTOEN:即注册就已经是能了
            irq_chip_set_defaults(desc->chip);
            new->irq = irq;
            register_irq_proc(irq);
            new->dir = NULL;
            register_handler_proc(irq, new);
    }

    8、再回头看(3)中断最终跳转到asm_do_IRQ函数
    asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
    {
        struct pt_regs *old_regs = set_irq_regs(regs);
        struct irq_desc *desc = irq_desc + irq;
        irq_enter();

        desc_handle_irq(irq, desc);直接调用了irq_desc[???]对应的handle_irq成员
    }
    static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
    {
        desc->handle_irq(irq, desc);
    }

    9、中断的初始化和中断的发生对比

    中断初始化:确定中断发生的时候应该跳哪个地址

    中断发生:cpu放弃当前任务,跳到中断向量表;

            b verctor_irq + stubs_offset;

            然后跳到verctor_irq(执行一系列操作)

            最后跳到do_asm_IRQ;

    void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
    {
        desc_handle_irq(irq, desc);
    }
    
    static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
    {
    	desc->handle_irq(irq, desc);
    }
    也就说中断发生后,直接指向了desc[??]数组的第一个元素:即处理函数desc->handle_irq

    那么handle_irq又是谁来设置的呢?????这就决定了最终处理函数是处理谁了!!!!!

    void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
    		  const char *name)
    {
        。。。。
    	desc->handle_irq = handle;
    }
    
    static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
    {
    	__set_irq_handler(irq, handle, 0, NULL);
    }
    
    
    void set_irq_chip_and_handler(unsigned int irq, struct irq_chip *chip,
    			 irq_flow_handler_t handle)
    {
    	set_irq_chip(irq, chip);
    	__set_irq_handler(irq, handle, 0, NULL);
    }
    
    void set_irq_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
    			      irq_flow_handler_t handle, const char *name)
    {
    	set_irq_chip(irq, chip);
    	__set_irq_handler(irq, handle, 0, name);
    }
    

    set_irq_chip_and_handler_name和set_irq_chip_and_handler函数最终就是被芯片相关的初始化调用的。我们可以发现很多地方都有进行设置handler函数

    ./arch/arm/plat-s3c24xx/irq.c:725:              set_irq_handler(irqno, handle_level_irq);
    ./arch/arm/plat-s3c24xx/irq.c:736:              set_irq_handler(irqno, handle_edge_irq);
    ./arch/arm/plat-s3c24xx/irq.c:756:              set_irq_handler(irqno, handle_edge_irq);
    ./arch/arm/plat-s3c24xx/irq.c:763:              set_irq_handler(irqno, handle_edge_irq);
    ./arch/arm/plat-s3c24xx/irq.c:774:              set_irq_handler(irqno, handle_level_irq);
    ./arch/arm/plat-s3c24xx/irq.c:781:              set_irq_handler(irqno, handle_level_irq);
    ./arch/arm/plat-s3c24xx/irq.c:788:              set_irq_handler(irqno, handle_level_irq);
    ./arch/arm/plat-s3c24xx/irq.c:795:              set_irq_handler(irqno, handle_edge_irq);

    void __init s3c24xx_init_irq(void)
    {
    。。。
    	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);
    	}
    。。。
    }

    handle_edge_irq才是最终的处理函数。

     

    展开全文
  • 硬件处理最近解决一关于Linux中断的问题,把相关机制整理了一遍,记录在此。不同的外部设备、不同的体系结构、不同的OS其中断实现机制都有差别,本文对应的OS为linux3.4版本,...包括:1、 初始化中断控制器等相...

    硬件处理

    最近解决一个关于Linux中断的问题,把相关机制整理了一遍,记录在此。

    不同的外部设备、不同的体系结构、不同的OS其中断实现机制都有差别,本文对应的OS为linux3.4版本,外部设备为PCI设备、系统为X86。

    概览

    中断让外设能够通知CPU他需要获得服务(让CPU执行指定的中断服务例程ISR)。为了达到这个目的,首先要为中断执行做好准备,完成初始化相关的操作。包括:1、 初始化中断控制器等相关器件(OS初始化过程中完成);2、 配置并使能外部设备(比如使用pci_enable_msix),得到irq号;在这个操作过程中,内核需要完成的大致操作是:

    Capacity registers);

    2、 为对应的irq_desc初始化正确的handle_irq接口(通用逻辑接口);

    3、 为对应的irq_desc初始化正确的底层chip操作接口。

    3、 使用request_irq号为该中断号指定一个服务例程;

    完成了以上的初始化操作,在外设中断到来的时候,为该中断指定的ISR(Interrupt Service Routines)就能得到执行,这个执行过程大致如下:

    1、 外设根据各自的配置,产生中断信号或者中断消息(MSI,INT# message)。2、 中断控制器从外设获取中断电信号或者中断消息,把它翻译为vector(CPU使用这个参数来决定是谁发生了中断,要如何处理)并提交到CPU。3、 对X86系统,CPU利用从中断控制器获取到的vector为索引,查询IDT (interrupt descriptor table)得到该中断的处理接口(对linux,是在entry_64.s中定义的函数common_interrupt接口)并执行。4、 在linux定义的common_interrupt接口中,执行完中断执行环境建立后,会进入generic interrupt layer执行,其首先通过vector查找到irq和对应的irq_desc结构,并执行该结构的handle_irq接口,这个接口就是generic interrupt layer的通用逻辑接口,比如handle_edge_irq/handle_level_irq等;在中断执行的通用逻辑接口中,会通过irq_desc::action调用外设指定的ISR。在linux中可以通过/proc/interrupts查看当前系统中所有中断的统计信息,在/proc/irq/xxx(中断号)下面,可以看到该中断的详细信息。

    中断相关硬件

    这里的描述很多来自INTEL的文档《Intel Software developer’s Manual, system programming guide》和《PCI Express System Architecture》

    中断控制器

    中断控制器的功能是:把外设的中断信号,转换成CPU能够明白的vector,并完成中断执行控制,确保在合适的时机把中断提交给CPU执行。对这部分内容,《interrupt in linux》有详细的描述。1、 8259A:每个8259A有8个管脚,每个管脚对应其连接的CPU的IDT中的一个vector,单独使用8259A,其硬件连线就决定了对设备vector的使用。典型的场景是使用两个8259A级联,理论最多16个中断号(就是ISA IRQs),实际能提供对15个中断线的处理(master的IRQ2用于连接slave),其具体的分配见下图。2、 PIR:用于完成输入的信号到输出信号的映射。在下图中PIR被用于完成多个PCI设备的INT#信号到8259A对应引脚的路由。对应这种连接方式,在PCI设备初始化的时候,OS会根据BISO提供的信息设置PIR,把INT#路由到O0-O3中正确的管脚,从而体现到8259A的正确管脚(对应了vector),这样INT#信号就被转换为vector并提交到CPU。由于可能有较多的PCI设备,而PIR的输入/出错管脚有限,所以连接到相同输入关键的INT#会共享一个中断。

    b087afc590b1a914f2464699613562f6.png

    3、 I/O APIC每个I/O APIC提供24个管脚,能够和外部设备的中断线连接,每个管脚都可以通过配RTE(Redirection table entry)配置对应的vector。其功能是:把外部设备的中断请求,翻译为local APIC的interrupt message,并按照配置的vector,发送给指定的local APIC处理(在SMP系统,存在多个CPU,也就有多个local APIC)。通常的配置方式是:第一个I/O APIC的前16个管脚,配置来处理之前的ISA IRQs,其它外设比如PCI设备,则直接使用其他管脚连接。4、 local APIC其负责处理IPI(inter-process interrupt)、直接连接的中断处理、接收和处理interrupt message,每个CPU有自己的local APIC。对应I/O APIC和local APIC的组合,其连接方式见下图

    0c894136f80406ebca2c8380c5c75a32.png

    针对X86中断控制器硬件和linux对这些硬件的初始化,在《interrupt in linux》中有很详细的描述。

    X86对中断的处理

    Local APIC的处理过程每个local APIC对应了一个CPU。其处理interrupt message的过程如下:1、 判断该中断的destination是否为当前APIC,如果不是则忽略,否则继续处理2、 如果是SMI/NMI/INIT/ExtINT, or SIPI(这些中断都负责特殊的系统管理任务,外设一般不会使用)被直接送到CPU执行,否则执行下一步。3、 设置Local APIC 的IRR寄存器的对应bit位。4、 如果该中断优先级高于当前CPU正在执行的中断,且当前CPU没有屏蔽中断(按照X86和LINUX的实现,这时是屏蔽了中断的),则该高优先级中断会中断当前正在执行的中断(置ISR位,并开始执行),低优先级中断会在高优先级中断完成后继续执行,否则只有等到当前中断执行完成(写了EOI寄存器)后才能开始执行下一个中断。5、 在CPU可以处理下一个中断的时候,从IRR中选取最高优先级的中断,清0 IRR中的对应位,并设置ISR中的对应位,然后ISR中最高优先级的中断被发送到CPU执行(如果其它优先级和屏蔽检查通过)。6、 CPU执行中断处理例程,在合适的时机(在IRET指令前)通过写EOI寄存器来确认中断处理已经完成,写EOI寄存器会导致local APIC清理ISR的对应bit,对于level trigged中断,还会向所有的I/O APIC发送EOI message,通告中断处理已经完成。

    说明:1、 关于Local APIC的IRR和ISR寄存器interrupt request register (IRR) 和 in-service register (ISR),都是256bit寄存器,每个bit对应一个中断(其中[0-15]不能使用,SMI/NMI/INIT/ExtINT/SIPI的发送和执行不经过ISR和IRR) 。IRR中保存的是已经被local APIC接纳但是还没有开始执行的中断;ISR中保持的是当前正在执行但是还没有完成的中断。2、 中断优先级对应通过local APIC发送到CPU的中断,按照其vector进行优先级排序:优先级=vector/16数值越大,优先级越高。由于local APIC允许的vector范围为[16,255],而X86系统预留了[0,31]作为系统保留使用的vector,实际的用户定义中断的优先级的取值范围为[2,15],在每个优先级内部,vector的值越大,优先级越高。Local APIC中还有一个关于中断优先级的寄存器TPR(task priority register)寄存器:用于确定打断线程执行需要的中断优先级级别,只有优先级高于设置值的中断才会被CPU执行 (SMI/NMI/INIT/ExtINT, or SIPI不受限制),也就是除了特殊中断外,优先级低于TPR指定值的中断将被忽略。3、 中断的pending对于同一个vector,如果有多次中断请求,可能IRR和ISR对应的bit位都被置位,也就是对同一个vector,local APIC可以pending两个中断,其后的即使有多处,也会被合并为一个执行。4、 中断执行时机中断的执行总是在指令边界开始(只有一个特殊的exception:abort在外,出现了这个中断,系统基本上也就完蛋了),也就是中断不可能打断指令的执行。

    CPU对中断和异常的处理相关概念1、 vector(中断向量)vector是一个整数,在X86CPU上,使用vector对中断(interrupt,外部设备产生)和异常(exception,CPU在程序执行中产生)统一编号,每个CPU核心内部,中断/异常和vector所以一一对应的;但是在各个不同的CPU核心上,相同的vector可以对应不同的中断(至少对于linux的设置,异常还是使用相同的vector)。vector的取值范围为[0,255],其中[0,31]被系统保留使用(多数作为异常的vector),其余的可供外设中断使用(系统设备比如local APIC也占用了部分[32,255]这个范围的vector)。2、 IDT(interrupt descriptor table)X86 CPU采用一个有256个元素的数组来描述中断/异常,该数组的index为vector;其内容包括了三种gate descriptor,用于描述一个中断/异常的处理接口;这个数组就是IDT,CPU在收到中断请求的时候,就利用vector获取到对应的中断处理接口描述并执行。3、 可屏蔽中断通过CPU INTR管脚/local APIC接收到的中断是可屏蔽中断,这些中断能够通过清零EFLAGS的IF来屏蔽(CLI指令)。通过INT n指令生成的中断即使使用了和外部中断一样的vector,也是不可屏蔽的;同样CPU运行过程中同步产生的trap、fault、abort等异常也是不可屏蔽的。4、 NMINMI是不可屏蔽中断(不可通过IF标志屏蔽),是通过CPU的NMI管脚发出的中断或者通过delivery mode为NMI的方式提交的中断。NMI中断在执行前,CPU不仅会屏蔽其它中断,也会屏蔽NMI中断,直到NMI中断处理执行完成(IRET指令被执行)。使用INT 2指令虽然能执行NMI中断处理函数,但是相关硬件不会介入,也就是没有相关的屏蔽NMI中断的操作。

    CPU执行中断的过程1、 利用vector,查IDT得到中断描述符;2、 如果中断发生在用户态,会首先执行stack switch切换到内核态执行;3、 依次保存EFLAGS CS IP到当前栈,如果需要(有error code的异常),把error code PUSH到当前栈。并把IF/TF位清零屏蔽可屏蔽中断;至此,CPU完成了中断处理程序执行环境的建立。4、 执行中断描述符定义的中断处理入口(IDT中指定地址的代码);5、 根据环境执行不同的中断退出方式,比如执行现场调度操作(retint_careful和retint_kernel),最终都会执行IRET指令;至此,中断执行完成。异常的执行过程类似,只不过异常在执行前不会把IF位清零,只清零TF位。

    PCI设备的中断

    本部分的很多内容来自《PCI Interrupts for x86 Machines under FreeBSD》和《PCI Express®Base Specification Revision 3.0》和《PCI Express System Architecture》。PCI设备的中断有两种模式:一种是INT#模式,一种是MSI模式。

    INT#模式每个PCI设备用四个中断信号,对应INTA#、INTB# INTC#、INTD#,这些中断信号采用level trigger 的方式并且为低电平有效,PCI设备通过拉低对应的信号来assert对应的中断,并在ISR访问PCI设备的指定寄存器deassert该中断。

    中断线和X86系统的连接这里存在两种常见连接模式,一种是使用老的8259A+PIR的系统,一种是使用新的I/O APIC的系统。对于使用8259A的系统:PCI的中断线连接到一个可编程的PIR设备,再通过该设备连接到8259A(见X86中断控制器一章的图);对于采用I/OAPIC的系统,可以使用以下的连接方式,同样这里只画出了一个中断线,同时根据不同的系统配置可能存在多个I/OAPIC。除了采用直接的中断引脚连接,PCI还支持virtual INT#,使用INT# message(Assert INT# message和deassert INT# message)的方式来使用INT#信号。

    37cac39f2b610b417ee645ba38f671ee.png

    NT#模式的局限1、 中断数量有限且不方便扩展:每个物理的PCI设备,最多只有4个中断但是至少能支持8个function,且系统中可能存在多个PCI设备,不得不使用中断共享的模式,影响使用性能。2、 同步问题:由于INT#中断采用的是side channel,中断信号和数据本身存在不同步的问题:可能在中断到达的时候,对应的数据没有达到,为了处理这个问题,一般采用“读刷新”的做法,也就是在使用该设备写入到X86的数据之前,ISR先对这个设备进行一次读操作来确保相关数据已经写入完成,比如读PCI设备的中断状态寄存器等。

    MSI/MSI-X模式在这种模式下,PCI设备通过和数据DMA一样的通道来完成中断处理,通过向特定地址空间(系统FSB Interrupt存储器空间)发起一个写操作来发起中断。该写操作的地址和数据信息在PCI设备初始化MSI功能的时候已经填写到MSI Capacity registers(MSI模式)/MSI-X table(MSI-X)中(对X86,这个地址空间是FEE00000H开始的地址空间,其实就是local APIC寄存器映射的地址空间),地址信息保存在Message address register,其中包含了目标CPU信息和FSB Interrupt存储器空间;数据中包含了该MSI中断对应的vector,保存在Message data register中。 MCH(memory control hub)截获这个写操作,转换为FSB interrupt message并向各个CPU核心广播,local APIC接收并处理这个消息,最终触发CPU的中断处理过程。使用这种机制,中断的数量不受PIR/ IOAPIC等各种器件管脚数量的限制,MSI可以支持32个中断,而MSI-X可以达到2048个;中断的传递相当直接,省略了中断路由的过程;并且能直接从interrupt message中获取vector信息,减少了交互过程。

    初始化

    相关概念和关键数据结构

    1、 irq号:在当前系统中全局唯一,对应内核数据结构struct irq_desc,每个外设的中断有一个irq号(体系结构预留的中断,是没有对应的irq_desc结构和irq号的),该irq在该中断的生命周期内都不会改变,且和该中断的中断处理函数关联;内核使用一个bitmap allocated_irqs来标识当前系统已经分配的irq;irq号的管理与底层中断设备和配置无关,属于Generic Interrupt Layer;对于irq号分布集中的情况,不配置CONFIG_SPARSE_IRQ,内核采用数组直接管理,数组下标就是irq号;而对于irq号比较分散的,设置CONFIG_SPARSE_IRQ,内核采用radix tree来管理所有的irq号。2、 vector号:内核使用全局bitmap used_vectors来标识那些vector被系统预留,不能被外设分配使用。3、 irq号和vector号的关联:内核中使用per-cpu变量vector_irq来描述irq号和vector号的关联,对每个CPU,vector_irq是一个数组,在X86架构下成员数量为256,其数组的index为vector,值为irq,如果为-1则表示该CPU上的这个vector尚未分配。4、 struct irq_desc结构,用来描述一个中断,是内核generic interrupt layer的关键数据结构,其包含了中断的大部分信息,并连接了driver层和物理中断设备层,每个irq号对应一个该结构,共享相同irq号的中断共享该结构。它的关键成员包括:

    a) irq_data :为该中断对应的物理中断设备层相关的数据。

    b) handle_irq:为该该中断使用的通用逻辑接口。

    c) action:为driver层提供的ISR信息,其为一个单向链表结构,所有共享该中断的设备的ISR都链接在这里。

    内核关键数据结构和相关初始化

    对X86 CPU,Linux内核使用全局idt_table来表达当前的IDT,该变量定义在traps.c

    gate_desc idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化为全0。

    对中断相关的初始化,内核主要有以下工作:1、 设置used_vectors,确保外设不能分配到X86保留使用的vector(预留的vector范围为[0,31],另外还有其他通过apic_intr_init等接口预留的系统使用的vector);2、 设置X86CPU保留使用的vector对应的IDT entry;这些entry使用特定的中断处理接口;3、 设置外设 (包括ISA中断)使用的中断处理接口,这些中断处理接口都一样。4、 设置ISA IRQ使用的irq_desc;5、 把IDT的首地址加载到CPU的IDTR(Interrupt Descriptor Table Register);6、 初始化中断控制器(下一章描述)以上工作主要在以下函数中完成:

    773f68f971677d70d2c7889e84eb3156.png

    可以看到,这个过程会完成每个中断vector对应的idt entry的初始化,系统把这些中断vector分成以下几种:1、X86保留vector,这些vector包括[0,0x1f]和APIC等系统部件占用的vector,对这些vector,会记录在bitmap used_vectors中,确保不会被外设分配使用;同时这些vector都使用各自的中断处理接口,其中断处理过程相对简单(没有generic interrupt layer的参与,CPU直接调用到各自的ISR)。2、ISA irqs,对这些中断,在初始化过程中已经完成了irq_desc、vector_irq、以及IDT中对应entry的分配和设置,同时可以发现ISA中断,在初始化的时候都被设置为运行在0号CPU。3、其它外设的中断,对这些中断,在初始化过程中仅设置了对应的IDT,和ISA中断一样,其中断处理接口都来自interrupt数组。

    中断处理接口interrupt数组interrupt数组是内核中外设中断对应的IDT entry,其在entry_64.S中定义,定义如下:

    babbedd3caafbe30d4ded7c9e2cbf208.png

    这段汇编的效果是:在代码段,生成了一个符号irq_entries_start,该符号对应的内容是一组可执行代码,一共(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7组,每组为7个中断入口,为:

    cab5518ccffb1afb891af4a7c3fb5fdc.png

    每组的最后一个中断入口不需要jmp 2f是因为其pushq_cfi(就是pushq咯)下面就是2f这个标号的地址了。(不明白的是:为什么不在jmp 2f的地方直接写上jmp common_interrupt?非要jmp 2f,2f的地方再次jmp common_interrupt?)

    而interrupt是一个数组,该数组在初始化完成后释放,其每个数组项都是一个地址,是对应的“pushq_cfi”代码的地址(每个代表中断入口的标号)。系统在初始化的时候,对外设使用interrupt数组作中断处理接口,就是在中断发生时,执行代码段:

    b781a788b5c96732feff86935440b269.png

    初始化中断控制器

    对中断控制器的使用基本上有三种机制:1、 中断路由表 $PIRstruct irq_routing_table,该结构用于使用PIR和8259A的系统,在微软的文献《PCI IRQ Routing Table Specification》中描述了该结构详细信息。其描述了一个PCI设备的INT#是如何连接到PIR设备的输入端口的。其关键数据是一个可变长的struct irq_info数组,每个struct irq_info描述了一个PCI物理设备的4个INT#相关的中断路由信息和对应可用的ISA IRQs的bitmap。BIOS根据相关设备的物理连接填写该数据结构,OS在设备初始化过程中使用这些信息为使用INT#的设备分配对应的vector和irq。2、 MP tablestruct mpc_intsrc,该数据结构用于使用I/O APIC的系统中,描述系统中所有PCI设备4个INT#信号和I/O APIC输入引脚的对应关系。该数据结构的srcbus成员为对应PCI设备的bus id;srcbusirq描述了一个INT#信号,其bit0-bit1用于描述是INTA#–INTD#中的哪一个(对应值为0-3),bit2-bit6描述该PCI设备的slot id。dstapic为该描述对应的I/O APIC的ID。dstirq描述srcbus和srcbusirq确定的INT#对应的irq号信息(具体的解析有多种情况)。在系统中有一个以该数据结构为成员的全局数组mp_irqs,用于管理系统中所有的硬件中断信号和irq之间的关联。对MP table及其使用的更加详细的描述,见《Multiprocessor Specification v1.4》3、 ACPI(Advanced Configuration and PowerInterface)机制这种机制为I/O APIC机制和中PIR机制提供统一的管理界面,该机制使用struct acpi_prt_entry描述INT#和GSI(能和vector、irq对应)的关系,系统中所有的struct acpi_prt_entry由OS从BIOS提供的信息中获取,并保存在链表acpi_prt_list中。注:对GSI的说明,GSI(global system interrupt)表示的是系统中中断控制器的每个输入管脚的唯一编号,在使用ACPI模式管理中断控制器的时候使用。对使用8259A的系统,GSI和ISA IRQ是一一对应的。对于使用APIC的,每个I/O APIC会由BISO分配一个基址,这个base+对应管脚的编号(从0开始)就是对应的GSI。通常是基址为0的I/O APIC的前16个管脚用于ISA IRQS,对GSI更加详细的描述,见《Advanced Configuration and Power Interface Revision 2.0》

    除了中断路由表,其它两种机制的初始化(包括相关中断路由信息的初始化)的在《interrupt in linux》中有很详细的描述。这些初始化操作都在内核初始化的时候完成。

    为PCI设备配置中断

    为PCI设备配置中断,分为两个步骤,步骤一:为设备分配irq号(对MSIX,会有多个),为该中断分配执行CPU和它使用的vector,并通过对中断控制器的设置,确保对应的中断信号和vector匹配。对于使用INT#类型的中断,通常通过pci_enable_device/pci_enable_device_mem/pci_enable_device_io中对函数pcibios_enable_device的调用来完成(只有在没有开启MSI/MSIX的时候才会为INT#做配置),而要配置MSI/MSIX中断要使用的是pci_enable_msix。步骤二:request_irq为该设备的irq指定对应的中断处理例程,把irq号和驱动定义ISR关联。

    pcibios_enable_device

    该接口用于使能PCI设备INT#模式的中断。其主要功能由pcibios_enable_irq(dev)完成,pcibios_enable_irq是一个函数指针,对于ACPI模式,其在上电过程中被设置为acpi_pci_irq_enable,其它情况被设置为pirq_enable_irq。

    对ACPI模式,其执行过程为:1、 acpi_pci_irq_enable:其先根据设备的管脚信息获取一个GSI(可以认为有了GSI,就有了irq号,gsi_to_irq可以完成其转换),有了gsi/irq,要完成设置还必须有vector并且把它们关联起来,因此如果GSI获取成功,会使用acpi_register_gsi来完成后续操作。2、 acpi_register_gsi:其主要功能由__acpi_register_gsi来完成,该函数指针在ACPI模式下被设置为acpi_register_gsi_ioapic,acpi_register_gsi_ioapic的执行过程如下:mp_register_gsi===>io_apic_set_pci_routing===>io_apic_set_pci_routing===>io_apic_setup_irq_pin_once===>io_apic_setup_irq_pin===>setup_ioapic_irq,在setup_ioapic_irq中,就会利用assign_irq_vector为该irq选择对应的执行CPU,并分配该CPU上的vector,同时还把该vector等配置写入到I/O APIC对应管脚的RTE,从而完成整个中断的配置。这样在该INT#信号到来的时候,I/O APIC就能根据对应管脚的RTE,把该信号翻译为一个vector,并通过中断消息发送到local APIC。同时在setup_ioapic_irq中,还通过ioapic_register_intr===>irq_set_chip_and_handler_name为得到的irq号对应的irq_desc设置了->irq_data.chip和handle_irq函数指针(对level触发的,为handle_fasteoi_irq,否则为handle_edge_irq)

    对其它模式,其通过pcibios_lookup_irq完成执行:在配置了I/O APIC的场景,pirq_enable_irq通过IO_APIC_get_PCI_irq_vector获取到irq号,然后和ACPI模式一样,通过io_apic_set_pci_routing完成对I/O APIC的配置。而对没有配置I/O APIC的场景,主要通过pcibios_lookup_irq来完成相关操作:1、 pcibios_lookup_irq通过读取BIOS提供的中断路由表 ($PIR表,irq_routing_table)信息和当前irq分配情况(pirq_penalty数组),在考虑均衡的前提下为当前设备分配一个可用的irq。2、 根据当前PIR的相关信息,决定最终的irq号选择,相关代码行如下

    7fa34c1329acc4444e0ee47306fbc85b.png

    也就是:如果是硬链接(INT#直接连接到了8259A,没有经过PIR),直接获取irq号,如果PIR中已经有该输入线的配置,使用已有的值,否则利用刚刚分配的可用irq,并写入到PIR,以便能够完成中断信号到irq号的转换。注意:1、这里的r,也就是pirq_router,代表一种PIR硬件,全局配置pirq_routers中描述了当前支持的PIR,并在初始化的时候通过pirq_find_router获取了对应当前配置的PIR对应的描述。2、这里没有分配vector,是因为这里使用的irq号范围为0-16,是ISA IRQs,其与vector的对应关系简单:vector = IRQ0_VECTOR + irq,并在系统初始化过程中,已经通过early_irq_init中分配了irq_desc结构,通过init_IRQ设置了vector_irq(只运行于CPU0上),然后通过x86_init.irqs.intr_init(native_init_IRQ)===> x86_init.irqs.pre_vector_init(init_ISA_irqs)设置了->irq_data.chip(i8259A_chip)和handle_irq函数指针(handle_level_irq)。

    Pci_enable_msix

    该函数完成MSIX中断相关的设置。

    24e2c52eec3611f93de6151eac09cb20.png

    msix_capability_init中实现中断初始化的是arch_setup_msi_irqs,对于X86系统,其为x86_setup_msi_irqs,x86_setup_msi_irqs中直接调用了native_setup_msi_irqs,该函数是X86系统中实现MSIX中断初始化的关键函数,对于没有启用interrupt remap的系统,其实现如下:

    afe828b3ea266a84070b9c14f7bf21a6.png

    该函数中有两个关键函数,分别是create_irq_nr和setup_msi_irq,其中create_irq_nr是分配一个vector给当前的中断,分配vector的同时,也为该中断指定了执行CPU。setup_msi_irq则负责把相关配置信息写入到PCIE配置区,并设置irq_desc的数据,其中关键的是irq_desc的handle_irq被设置为handle_edge_irq。create_irq_nr的实现如下:

    427d31ecee96760f926d2a01114c339b.png

    其中__assign_irq_vector负责分配vector,并和中断在CPU上的调度相关,其实现如下

    729acebb03ef65cede4ee4b3d5319622.png

    119c7ad7c206b89b5da65cbc3f902bb0.png

    37e968820fe932ac4ea3a01ac1ad4ba9.png

    从实现中可以看到,该函数从FIRST_EXTERNAL_VECTOR(外设中断的起始vector号,通常是0x20) 到first_system_vector(外部中断结束vector号,通常是254,255被系统作为保留的SPURIOUS_APIC_VECTOR使用)的范围中,为当前中断分配一个vector,要求该vector在对应的cpu上均可用,该vector按照系统配置的要求和对应的cpu核心绑定,并在要求的cpu中没有被其它中断使用。需要说明的是,在setup_msi_irq中会再次通过msi_compose_msg再次调用__assign_irq_vector,但是由于这时已经存在满足CPU绑定要求的vector,不会多次分配。

    从以上分析可以得到MSI-X中断的一个绑定特征:根据当前APIC配置,每个中断都有对应的可以运行的cpu,pci_enable_msix在这些要求的cpu核心上建立了vector (APIC的配置由数据结构struct apic来抽象,其vector_allocation_domain用于决定需要在那些cpu核心上为该中断建立vector),当前我的系统使用的是apic_physflat,对每个MSI中断,其只在一个cpu核心上建立vector,对应的MSI-X中断事实上被绑定到该cpu核心上。在用户通过echo xxx > /proc/irq/xxx/affinity来调整中断的绑定属性时,内核会重新为该中断分配一个新的在对应核心上可用的vector,但是irq号不会改变。绑定属性调整的调用路径大致为irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>__irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最终通过msi_set_affinity来实现,在该函数中首先通过 __ioapic_set_affinity在绑定属性要求的cpu中选择空闲vector,然后通过__write_msi_msg把配置写入PCIE配置区。需要说明的是:该irq最终可以运行的cpu数量并不完全由用户指定,还与apic的模式相关,对于apic_physflat,实际上只为该irq分配了一个cpu核心,该irq只能运行在用户指定的cpu中的一个,而不是全部。

    附:关于全局变量apic

    该全局变量为local apic的抽象,在不同的系统配置下,有不同的选择,其最终的选择结果,由内核的config(反应在/arch/x86/kernel/apic/Makefile)和硬件配置等来决定。1、 定义各种apic driver首先,每种apic配置都会使用apic_driver/ apic_drivers来定义,apic_driver的定义如下

    5709bc5e5749d3d5844cff1270e1895c.png

    这个定义的目的是把sym的地址写入到名为” .apicdrivers”的段中。2、 定义全局符号__apicdrivers和__apicdrivers_end在linker script vmlinux.lds.S中,定义了__apicdrivers为” .apicdrivers”段的开始地址,而__apicdrivers_end为结束地址。” .apicdrivers”段中是各个不同的apic配置对应的struct apic。

    9f4e192e9553e76ecfdffb35453e052c.png

    3、 apic的probe在初始化过程(start_kernel)中,会调用default_setup_apic_routing(probe_64.c中定义)来完成apic的probe,该函数会按照各个struct apic结构在.apicdrivers中的顺序,依次调用其probe接口,第一个调用返回非0的struct apic结构就被初始化到全局变量apic。也就是:如果有多个apic结构可用,最终会选择在.apicdrivers段中出现的第一个;所以makefile文件中各个.o出现的顺序也会觉得最终的apic probe结果。

    request_irq

    该函数把irq和用户指定的中断处理函数关联。用户指定的每个处理函数对应于一个struct irqaction结构,这些处理函数构成一个链表,保存在struct irq_desc::action成员中。详细见request_irq===>request_threaded_irq中的处理。

    中断的执行

    在内核代码中,对X86平台中断执行的基本过程是:1、 通过IDT中的中断描述符,调用common_interrupt;2、 通过common_interrupt,调用do_IRQ,完成vector到irq_desc的转换,进入Generic interrupt layer(调用处理函数generic_handle_irq_desc);3、 调用在中断初始化的时候,按照中断特性(level触发,edge触发等、simple等)初始化的irq_desc:: handle_irq,执行不同的通用处理接口,比如handle_simple_irq;4、 这些通用处理接口会调用中断初始化的时候注册的外部中断处理函数;完成EOI等硬件相关操作;并完成中断处理的相关控制。

    common_interrupt

    按照之前CPU执行中断过程的描述,X86 CPU在准备好了中断执行环境后,会调用中断描述符定义的中断处理入口;根据中断相关初始化过程我们知道,对于用户自定义中断,中断处理入口都是(对系统预留的,就直接执行定义的接口了):

    2249d084448f56b5587c78d6332cfc38.png

    就是在把vector入栈后,执行common_interrupt,common_interrupt在entry_64.S中定义,其中关键步骤为:调用do_IRQ,完成后会根据环境判断是否需要执行调度,最后执行iretq指令完成中断处理,iret指令的重要功能就是回复中断函数前的EFLAGS(执行中断入口前被入栈保存,并清零IF位关中断),并恢复执行被中断的程序(这里不一定会恢复到之前的执行环境,可能执行软中断处理,或者执行调度)。

    do_IRQ

    do_IRQ的基本处理过程如下,其负责中断执行环境建立、vector到irq的转换等

    b6e18214db14bd104084ee53020729f4.png

    Generic interrupt layer

    该层负责的是平台无关/设备无关的中断通用逻辑,对这部分,在《Linux generic IRQ handling》中有详细描述。其负责完成中断处理的接口是generic_handle_irq_desc,该接口会执行irq_desc::handle_irq; Generic interrupt layer根据中断特性的不同,把中断分成几类,包括:level type(handle_level_irq)、edge type(handle_edge_irq)、simple type(handle_simple_irq)等,这些中断类型对应的处理函数是都在kernel/irq/chip.c中定义,并入前面的描述,在相关中断初始化的时候,被赋值给irq_desc::handle_irq;对于PCI设备,只用了两种,level type(INT#模式)、edge type(MSI/MSI-X模式)。

    edge 触发中断的基本处理过程:

    电压跳变触发中断===>中断控制器接收中断,记IRR寄存器===>中断控制器置ISR寄存器===>CPU屏蔽本CPU中断===>CPU处理中断,发出EOI===>中断控制器确认可以处理下一次中断===>ISR清中断源,电压归位===>中断源可以发起下一次中断===>CPU中断处理完成,执行完现场处理后执行IRET,不再屏蔽本CPU中断。edge触发的特点:a) 中断不会丢如果中断触发时中断被屏蔽,那么中断控制器会记录下该中断,在屏蔽取消的时候会再执行。b) edge触发的缺点是完成共享不方便:比如A和B两个中断源共享一个中断,每次ISR先检查A再检查B,如果B先发生中断,在ISR检查完A,检查B的过程中,A发生中断。那么在ISR处理开始的时候,A会告诉ISR,不是它干的,然后ISR处理B的中断,完成后通过清理中断源把B的电压归位,但是由于A的中断没有得到处理,电压没有归位,这个共享的中断就不能得到再次触发了。edge触发对应的通用逻辑接口

    632988b93f61ce62afef98f5d21b0485.png

    level 触发:

    这种模式下,外设通过把电压保持到某个门限值来完成触发中断,在处理完成(EOI)后,如果电压还在门限值,就会再次触发中断的执行。level触发的特点:a) 方便中断共享b) 对中断触发时中断被屏蔽的情况,如果中断屏蔽解除后仍然引脚电压仍然在门限值,就执行该中断的ISR,否则不执行。需要说明的是:对于使用local APIC的系统,level触发和edge触发需要配置local APIC的Local Vector Table。4、 level触发对应的通用逻辑接口

    0e88380a7218232a8f08f7e5a88ad25e.png

    level触发和edge触发在通用逻辑层最大的不同就是当其他CPU正在处理该中断的时候,系统的行为,对edge触发,会把该中断记录下来,当前处理结束后再次执行,而level直接退出。产生这种差异的原因是:level触发不怕丢?

    无论是那种触发方式,都会调用handle_irq_event处理中断,该函数中会遍历irq_desc::action链表,执行action->handler,也就是驱动在中断初始化的时候,通过request_irq注册的中断处理接口。

    总结

    中断的使能状态

    1、 在local APIC层次(当前CPU),一个中断正在处理的时候,不会有相同的中断或者优先级低于该中断的其它中断来打断当前中断的执行;但是高优先级中断可以打断低优先级中断。2、 在X86 CPU层次(当前CPU),从中断执行开始到IRET,IF位都被清零,也就是只有不可屏蔽中断能够打断当前中断的执行。3、 在Generic interrupt layer层次,如果一个中断已经在系统中执行,会阻止该中断在其它CPU上的执行。4、 在外设/驱动中断处理函数层次往往也有中断使能的功能,比如启用了NAPI的网卡,在中断处理函数开始执行的时候,往往会通过硬件功能关闭该中断,要在对应的软中断完成处理后才通过硬件功能使能该中断。注:NMI中断虽然称为不可屏蔽中断,也有一个例外:NMI中断执行过程中,该CPU屏蔽了后来的NMI中断。

    中断的执行CPU

    通过中断初始化过程我们知道:中断在那个CPU上执行,取决于在那个CPU上申请了vector并配置了对应的中断控制器(比如local APIC)。如果想要改变一个中断的执行CPU,必须重新申请vector并配置中断控制器。一般通过echo xxx > /proc/irq/xxx/affinity来完成调整,同时irq_balance一类软件可以用于完成中断的均衡。

    (完)

    展开全文
  • 过程称为中断中断实例图示: 中断可以分为 外部中断 定时器中断 串口中断 中断源: 引起中断的原因即发送中断请求的来源。中断源具体可参考下面这图片: 中断优先级以及相对应的寄存器: 图中从上到下依次代表...
  • 一、GPIO的初始化1、GPIO初始化• 在内核源码目录下使用“lsdrivers/gpio/*.o”,可以看到“gpio-exynos4”被编译进了内核-生成.o文件代表最终被编译进了内核-除了menuconfig配置文件,还可以通过.o文件来判断该文件...
  • 前言:建议阅读上一篇文章《计算机体系结构变迁》...文章目录一、从BIOS讲起二、内核初始化(1)一号进程的意义(2)从内核态返回用户态(3)ramdisk(基于内存的文件系统)(4)fork与kernel_thread的区别一、从BIOS讲起通电...
  • DSP—使用定时器0中断

    2021-05-19 11:56:59
    定时器0 中断设置定时器0中断设置由以下几个步骤组成。1)定时器0 中断的基本条件除了对周期寄存器及定时器分频器进行必要的设置之外,使能定时器0中断有两条必须的指令:CpuTimer0Regs.TCR.bit.TSS= 0; //启动定时器...
  • 在调用第一c函数之前,我们仍然要写一段汇编,切换CPU进入长模式,初始化CPU寄存器和C语言要用的栈。 因为目前代码执行流在二级引导器中,进入到 Cosmos 中这样在二级引导器中初始过的东西都不能用了 因为 CPU ...
  • 当内核引导时,会执行start_kernel对一些子系统做初始化,在... 在初始化任务中,主要有三: 引导期间选项参数:调用两次parse_args(一次直接调用,一次通过parse_early_param间接调用)以处理引导加载程序(boot l...
  • 文章目录第一C函数,实现板级初始化第一C函数hal层初始化初始化平台初始化内存初始化中断初始化中断控制器进入内核层重点回顾 今天我们会继续在这 hal_start 函数里,首先执行板级初始化,其实就是 hal 层...
  • 结构体初始化

    2021-05-05 13:44:36
    stm32学习笔记:常用的几个初始化1.结构体与枚举的写法 1.结构体与枚举的写法
  • 概述内核的初始化过程过程中,与网络相关的工作如下所示:内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线程init(在Linux3-12中为kernel_init)。asmlinkage void __init ...
  • BIOS 的引导当按下开机键,你的主板开始加电,在刚加电的时候电脑会做一些初始化寄存器的工作,比如将 CS代码段寄存器 设置为 0xFFFF ,将 IP指令寄存器 设置为 0x0000,这样我们第一条指令就会指向 0xFFFF0 ,于是 ...
  • 神魔波烂字,草
  • 可以调用下表函数之一进行初始化,它们都会返回一int , 返回 -1 表示初始化失败。 int wiringPiSetup (void) 返回:执行状态,-1表示失败 当使用这函数初始化树莓派引脚时,程序使用的是wiringPi ...
  • 一般情况下,中断的处理函数有两,其一为中断初始化函数,其二为中断服务函数。 中断服务函数的结构: 中断相关的4寄存器 写程序的时候会用到 分为两控制寄存器和两中断请求标志 一例题
  • 系统初始化

    2021-07-19 20:10:05
    06 x86架构 x86架构统一了计算机的硬件环境,避免了linux...数据总线的位数:决定了一次能拿多少数据进来。例如只有两位,那 CPU 一次只能从内存拿两位数。要想拿八位,就要拿四次。位数越多,一次拿的数据就越多,.
  • 1:Cube初始化设置 2:配置过滤器 3:添加接收中断函数 4:通信测试
  • 在内存管理的上下文中, 初始化(initialization)...而我们今天要讲的boot阶段就是系统初始化阶段使用的内存分配器.1 前景回顾1.1 Linux内存管理的层次结构Linux把物理内存划分为三层次来管理层次描述存储节点(Node...
  • uboot step-14 串口初始化UART关于串口对于嵌入式设备的开发,刚开始好多设备都无法使用,由于无法获得程序的运行状态,调试程序需要花费好多时间和精力,因此串口对于嵌入式程序的调试的作用显而易见,当串口不能...
  • 虚拟设备虚拟设备是建立在一或多真实...虚拟设备的使用情形有如下种:绑定(Bonding):一组真实设备虚拟为一虚拟设备,使其如同单一设备来提高性能;802.1Q:这是一种VLAN标准,用VLAN报头扩充802.3/Ethern...
  • 通知链元素结构则保存着回调函数的类型以及优先级 2、时钟初始化 2.1 内核初始化部分( start_kernel 函数)和时钟相关的过程主要有以下几个: tick_init() init_timers() hrtimers_init() time_init() 其中函数 ...
  • 原文链接 ...RT-Thread 自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。 5.1普通初始化 前面也讲了,我们在写单片机的
  • STM32有好几个串口。比如说STM32F103ZET6有5个串口,串口1的引脚对应的IO...复用端口初始化几个步骤:1)GPIO端口时钟使能。要使用到端口复用,当然要使能端口的时钟了。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GP...
  • 点击文末阅读原文给胖哥投票另外这是我的视频号,分享一些有用的知识。也可以关注一下哦。1. 前言今天有同学告诉我,在Security Learning项目的day11分支中出现了一问题...
  • 本章主要记录外部中断函数的学习情况,实验过程首先完成按键扫描实验,使用外部中断完成了按键中断实验。 需要说明的是,外部按键扫描即使用的是GPIO读取功能,HAL_GPIO_ReadPin()函数,该函数在[00_]有说明。GPIO...
  • 我们知道,在写裸机程序时,当我们完成硬件初始化的封装后,其初始化函数则需要在主函数中进行调用。当我们使用RT-Thread后,完全不需要这样做了,我们可以将硬件等自动初始化。RT-Thread 自动初始化机制是指初始化...
  • 实验板子:tiny210(芯片:s5pv210)实验目的:通过外部中断操作两按键实现对LED灯的亮灭控制步骤:①初始化GPIO端口,使GPIO为外部中断状态;(寄存器:GPxxCON)例:②配置外部中断触发模式,上升沿触发,下降沿触发...
  • 使用新发布的快照初始化订阅Initialize a Subscription with a Snapshot for a New Publication03/23/2020本文内容适用于:Applies to: SQL ServerSQL Server(所有支持的版本)SQL ServerSQL Server (all supported ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 163,573
精华内容 65,429
关键字:

中断初始化包括几个方面