分析报告:关于Linux中断初始化的分析

Linux中断初始化分析

 

简介:本篇报告首先从定义以及基本概念入手介绍中断的相关知识,接着重点分析了linux内核中中断初始化的过程,本文的所有代码均依据2.6.36版内核源码。

 

一、中断的基本概念

 

中断最初是用来克服CPUI/O接口采用程序查询的服务方式从而导致处理器低效率,由此而引入的一种让CPU及时响应I/O的服务方式,更通俗的来讲,就是CPU在做自己的事情,突然之间有一个硬件信号打断了CPU的执行,并要求CPU为其执行相应的代码片段。为此计算机必须有相关的软硬件来为服务提供支持,硬件方面添加了两片8259A,软件方面则体现在操作系统里的相关代码。

       要想更加明白中断的原理就必须先明确几个相关的概念:

u     中断源:顾名思义就是打断CPU执行的事件,可以是硬件的也可以是软件的,更详细的划分可以分成三类,第一类是CPU异常(执行指令出错)和非屏蔽中断(硬件错误),第二类是可屏蔽中断(I/O请求),第三类是软中断。

u     中断号:两片8259A芯片级连后有IRQ0IRQ15(除没有IRQ2)15根中断请求线,系统为每一根中断请求线分配一个中断号,而这15个中断号分别对应特定的硬件中断。

u     中断向量:随着硬件的发展,以及软件中断的需要,15个中断号根本无法满足这些中断请求,为此引出了中断向量的概念。按中断向量来为上面三类中断划分:第一类031,第二类3247,第三类48255(128号中断向量作为系统调用),此时256个中断向量共享这15根中断线。

u     中断服务程序:为每一种中断提供服务的一段代码。

u     中断向量表:也称为中断描述符表,简单的来说就是为中断服务提供索引,这个索引包括中断向量,以及与之对应的中断服务程序的入口地址,由于中断向量共256个,也就对应表中的256个表项,每一个表项又称为门描述符,用8个字节来描述。

 

二、中断的核心数据结构

 

为了描述中断的初始化过程,光有上面这些概念还是远远不够的,为此还必须知道与中断相关的几个核心数据结构,在这里主要描述其中三个,分别是struct irq_descstruct irqactionstruct irq_chip,下面给出最重要或与初始化过程相关一些成员。

      struct irq_desc

      irq_flow_handler_t handle_irq;/*层次的中断事件处理函数*/

      struct irq_chip *chip;/*指向中断控制器结构体*/

      struct irqaction *action;/*提供中断处理相关信息的结构体,如果是采用共享中断方式,它指向的将是一个链表*/

      unsigned int status;/*中断的状态,如关中断*/

      unsigned int depth;/*中断的深度*/

      const char *name;/*产生中断的名称*/

 

 

struct irqaction

      irq_handler_t handler;/*指向中断服务程序的指针*/

      unsigned long flags;/*中断标志,如标志共享中断方式*/

      const char *name;/*产生中断的设备名称*/

      void dev_id;/*采用共享中断方式时,用来标识设备的id*/

      struct irqaction *next;/*采用共享中断时,指向下一个中断的指针*/

      int irq;/*中断号*/

 

     

struct irq_chip

{

      void (*startup)(unsigned int irq);

      …...

}

      通过对上面结构体一些成员的分析,可以看出最核心的就是struct irq_desc结构体,它是门描述符,即中断描述符的表项,它通过*chip成员与中断控制器结构体struct irq_chip关联,通过*action与描述中断行为的结构体struct irqaction关联,必须注意的是在头文件中定义了irq_desc[]数组,这个数组的长度是224,也就是可屏蔽中断和软中断的中断向量的长度,IRQn对应的默认向量变成n+32,在这里对第一类中断进行特殊处理。

 

三、进一步分析源代码

 

       2.6.36版内核中,描述中断初始化的源文件是arch/x86/kernel/irqinit.c,而定义中断初始化的函数则是其中的init_ISA_irqs下面给出其代码并简述其含义:

      void __init init_ISA_irqs(void)

{

      int i;

 

#if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)

      init_bsp_APIC();

#endif

      legacy_pic->init(0);

 

      for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) {

           struct irq_desc *desc = irq_to_desc(i);

 

           desc->status = IRQ_DISABLED;

           desc->action = NULL;

           desc->depth = 1;

 

           set_irq_chip_and_handler_name(i, &i8259A_chip,handle_level_irq, "XT");

      }

}

      如果硬件架构采用X86系列的64位或X86系列的LOCAL_APIC,则采用init_bsp_APIC()函数来初始化芯片的相关设置,其他构架则采用默认的legacy_pic->init(0)函数来初始化芯片设置,struct legacy_pic结构体中主要是勾子函数,包括void (*init)(int auto_eoi)int (*irq_pending)(unsigned int irq)等成员。根据不同的体系构架最终调用的init()函数将有所不同,以X86体系为例,用legacy_pic结构体定义了一个变量,并对其进行赋值,代码如下:struct legacy_pic *legacy_pic = &default_legacy_pic;而对default_legacy_pic变量赋值的代码如下:

struct legacy_pic default_legacy_pic = {

     .nr_legacy_irqs = NR_IRQS_LEGACY,

     .chip  = &i8259A_chip,

     .mask_all  = mask_8259A,

     .restore_mask = unmask_8259A,

     .init = init_8259A,

     .irq_pending = i8259A_irq_pending,

     .make_irq = make_8259A_irq,

 };

      其中在X86体系下NR_IRQS_LEGACY的值为16init_8259A函数则主要是对中断控制字的设置。

      下面则主要是for循环的执行,共循环了16次,首先定义指向irq_desc数组成员的指针,这里对irq_to_desc(i)采用宏定义:#define irq_to_desc(irq) (&irq_desc[irq]),接下来设置desc结构体中的statusactiondepth三个字段。最关键的则是 set_irq_chip_and_handler_name()函数的执行,这个函数的定义如下:

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

}

这个函数接受的参数有中断号、中断控制器结构体、高层次的中断事件处理函数、产生的中断名称。

封装内的两个函数的定义如下:

      int set_irq_chip(unsigned int irq, struct irq_chip *chip)

{

      struct irq_desc *desc = irq_to_desc(irq);

      unsigned long flags;

 

      if (!desc) {

           WARN(1, KERN_ERR "Trying to install chip for IRQ%d/n", irq);

           return -EINVAL;

      }

 

      if (!chip)

           chip = &no_irq_chip;

      raw_spin_lock_irqsave(&desc->lock, flags);

      irq_chip_set_defaults(chip);

      desc->chip = chip;

      raw_spin_unlock_irqrestore(&desc->lock, flags);

 

      return 0;

}

      __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)

{

      struct irq_desc *desc = irq_to_desc(irq);

      unsigned long flags;

 

      if (!desc) {

           printk(KERN_ERR"Trying to install type control for IRQ%d/n", irq);

           return;

      }

 

      if (!handle)

           handle = handle_bad_irq;

      else if (desc->chip == &no_irq_chip) {

           printk(KERN_WARNING "Trying to install %sinterrupt handler "

                  "for IRQ%d/n", is_chained ? "chained " : "", irq);

           desc->chip = &dummy_irq_chip;

      }

}

      这两个函数判断在chip指针为空时会将其值赋为&no_irq_chip,这是不需要中断控制器的情况,而在chip指针不为空时将其值赋为&dummy_irq_chip,也就是有中断控制器的情况,也就是前16个门描述符表需要设置相关的控制器,而后面的则不需要进行控制器的设置。

      初始化函数定义好了,现在来看看调用它的函数在哪?

      void __init native_init_IRQ(void)

{

      int i;

      x86_init.irqs.pre_vector_init();

 

      apic_intr_init();

      for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {

           if (!test_bit(i, used_vectors))

                 set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);

      }

      if (!acpi_ioapic)

           setup_irq(2, &irq2);

#ifdef CONFIG_X86_32

      if (boot_cpu_data.hard_math && !cpu_has_fpu)

           setup_irq(FPU_IRQ, &fpu_irq);

      irq_ctx_init(smp_processor_id());

#endif

}

      在这个函数中,如果将x86_init.irqs.pre_vector_init()展开,则会发现pre_vector_init是一个函数指针,在x86_init.c里有对它的赋值.pre_vector_inti = init_ISA_irqa,也就完成了调用init_ISA_irqs 函数的任务。我们现在主要来看for循环,先看FIRST_EXTERNAL_VECTOR 的值是0x20,也就是32号中断向量,NR_VECTORS 的值为256,也就是设置第二类和第三类的各个门描述符。set_intr_gate(n,*add)则会调用_set_gate(int gate, unsigned type, void *addr,unsigned dpl, unsigned ist, unsigned seg),根据n设置IDT的第n个表项,并且会将段选择符设置成代码段的段选择符,还包括类型码,请求优先级dpl,以及中断服务程序的偏移地址。

      以上所述是单处理机的初始化过程,多处理机的初始化涉及到中断向量的设置,以及对中断门的分配,其初始化函数在smp_intr_init()里定义,在native_init_IRQ函数里被apic_intr_init()函数调用,在这里对其初始化过程不作详细描述。

  

四、用流程图展现函数调用过程

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值