LINUX-内核-中断分析-中断向量表(1)-x86

x86体系中断概念

在《深入理解Linux内核》第四章中提到,Intel文档提出了两个 概念。
第一个概念是把同步和异步中断分别称为异常(exception)和中断(interrupt);
第二个概念是进一步的,对于中断和异常,Intel又再细分了很多类,将中断分为可屏蔽中断和不可屏蔽中断;将异常分为故障、陷阱、异常中止和编程异常。
LINUX-内核-中断分析-中断向量表(1)-x86_第1张图片

x86体系中断向量概念的引出

以上这些概念及其逻辑当然会体现在x86的中断向量表的设计中。特别重要的:每个中断和异常是由0~255之间的一个数来标识。在Intel中,把这个8位的无符号整数叫做一个向量(vector),所以,x86中中断和异常都有自己的向量。其中,异常和非屏蔽中断两者的向量是固定的,而可屏蔽中断的向量可以通过对中断控制器的编程来改变。


x86体系中断向量表的初始化以及中断向量的分配

上文已经说到,所有的中断和异常都会占用0~255这256个中断向量中的一个。

异常和非屏蔽中断占用向量

其中异常和非屏蔽中断占用的向量是固定的。在各种书籍中,都介绍x86体系结构的异常和非屏蔽中断占用了0~19这20个中断向量。且其中仅有2号向量用于非屏蔽中断,其余19个全部分类为异常。
这20个向量的设置或初始化会在trap_init()函数中完成到可用状态,在最新的3.0内核中,通过调用set_intr_gate()、set_system_intr_gate()、set_task_gate()等一系列函数设置这20个向量。也即是说,在x86体系下,这20个向量的用处是定死的,是死规定,不可改变,而且在trap_init()中直接设置到位,所谓的设置到位,主要是指对这20个向量的门进行设置。
需要强调的是,由此我们可以发现,x86体系结构下的trap_init()实际上设置的就是idt_table表,也即是给各个门赋值。在3.4.35版本内核源码中,这个idt_table表是在traps.c文件下定义的一个全局变量。

 -----------------------arch\x86\include\asm\irq_vectors.h 
#define NR_VECTORS           256
-----------------------------------arch\x86\kernel\traps.c
/*
 * The IDT has to be page-aligned to simplify the Pentium
 * F0 0F bug workaround.
 * 一些旧的Pentium模式有声名狼藉的“F00F”bug,能让用户态程序冻结系统
 */
 gate_desc idt_table[NR_VECTORS] __page_aligned_data = 
 { { { { 0, 0 } } }, };

系统调用占用向量

128号向量被系统调用占用。

外部中断(可屏蔽中断)占用向量

在x86体系中,外部可屏蔽中断,也就是物理IRQ所对应的中断向量号,占用了32~238这个范围(128系统调用除外)。
这里涉及一个设备的IRQ号分配(间接隐含设备的中断向量号分配)的问题,根据《深入理解Linux内核》一书介绍,IBM兼容机(其实就是x86体系)的体系结构要求,某些设备必须被静态地分配指定的IRQ线,给出的例子包括:
1. 间隔定时器必须分配IRQ0,也即是32号中断向量;
2. 从8259A PIC(如果有的话)必须分配IRQ2,也即是34号中断向量;
3. 外部协处理器(如果有的话)必须分配IRQ13,也即是45号中断向量。


初始化大蓝图

中断相关的初始化差不多是从start_kernel()函数开始的,包括两个层次。

  • 一个层次是初始化所谓中断向量表,也即是填充256个门的值,为这256个门设置正式、或者是初始化时的默认向量处理函数。
  • 另外一个层次是初始化IRQ描述符,也就是所谓的irq_desc[]数组。主要是设置一些默认的irq_chip和默认的中断处理函数。
    LINUX-内核-中断分析-中断向量表(1)-x86_第2张图片

setup_arch()函数中做前期的硬件平台体系相关设置

其实setup_arch()不仅仅是为中断或异常完成一些设置,但其中涉及到和中断相关的有两步。

  • 第一步是提前设置1号Debug调试向量、3号Breakpoint断点向量和14号Page Fault缺页异常向量的门。分别为这三个门注册的向量处理函数是debug(),int3()和page_fault()。在2.6内核中是没有这一步的,而在3.4内核中根据注释提前设置这三个向量是为了Set of traps needed for early debugging的目的。
  • 第二步是将idt_table表整个的给加载到idt寄存器里面去。
----- arch/x86/kernel/traps.c ----->
1 /* Set of traps needed for early debugging. */
2 void __init early_trap_init(void)
3 {
4     set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK);
5      /* int3 can be called from all */
6     set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK);
7       set_intr_gate(X86_TRAP_PF, &page_fault);
8     load_idt(&idt_descr);
9 }

LINUX-内核-中断分析-中断向量表(1)-x86_第3张图片

trap_init()函数设置中断向量表

trap_init()主要是对一些系统保留的中断向量进行初始化,也就是设置中断向量表开头的0~19这20个陷阱门,当然,其中1号Debug调试向量、3号Breakpoint断点向量和14号Page Fault缺页异常已经在setup_arch()中设置好了。另外,trap_init()还会对系统调用的向量做初始化。

early_irq_init()函数对IRQ子系统的前期初始化

分两步,第一步是与硬件和平台无关的通用逻辑层处理,其本质就是一个for循环执行一个函数desc_set_defaults(),为所有的irq_desc填充默认值。其关键点是为这些irq_desc注册的中断控制器全部先填充为一个所谓no_irq_chip。

------kernel/irq/irqdesc.c------->
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node, struct module *owner)
{
    int cpu;

    desc->irq_data.irq = irq;
    desc->irq_data.chip = &no_irq_chip;
    desc->irq_data.chip_data = NULL;
    desc->irq_data.handler_data = NULL;
    desc->irq_data.msi_desc = NULL;
    irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
    irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
    desc->handle_irq = handle_bad_irq;
    desc->depth = 1;
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;
    desc->name = NULL;
    desc->owner = owner;
    for_each_possible_cpu(cpu)
        *per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
    desc_smp_init(desc, node);
}

第二步就与硬件平台相关了,调用arch_early_irq_init()进行一些平台相关的irq系统前期初始化。在x86体系中,arch_early_irq_init()主要是处理的8259和IOAPC的差异相关,这导致对前16个固定IRQ的处理会有些区别,此处不做深究。
LINUX-内核-中断分析-中断向量表(1)-x86_第4张图片

init_IRQ()对外设中断进行初始化

init_IRQ()主要用于对外设中断的初始化,可以总结为5步过程。
第一步是调用init_ISA_irqs()对前16个被所谓的legacy_pic控制的legacy_irqs进行一些相关的初始化。包括:

  1. 使用irq_set_chip_and_handler_name()函数设置这16个legacy_irqs的irq_chip为legacy_pic也就是传统的i8259A_chip,电流处理函数为handle_level_irq;
  2. 调用apic_intr_init()设置一些本地APIC中断相关和处理器间中断相关,不深究;
  3. 从第0x20号开始,将所有的中断向量描述符的服务函数注册为来自一个数组interrupt[ ] ;

第二步就是setup_irq()两个IRQ,分别是:

  1. 在传统i8259模式下,setup_irq()2号NVR,这是PCI级联中断;
  2. setup_irq() 3号IRQ,这是数字协处理器中断;
    LINUX-内核-中断分析-中断向量表(1)-x86_第5张图片

你可能感兴趣的:(linux内核)