精华内容
下载资源
问答
  • ARM linux的中断向量表初始化分析

    千次阅读 2010-06-29 13:53:00
     ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表. <br /> /* arch/arm/kernel/traps.c */ void __init trap_init(void) {  ...

    本文分析基于linux2.4.19 source,pxa 270 cpu.

      ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表.

    /* arch/arm/kernel/traps.c */
    void __init trap_init(void)
    {
       extern void __trap_init(unsigned long);
       unsigned long base = vectors_base();

       __trap_init(base);
       if (base != 0)
          oopsprintk(KERN_DEBUG "Relocating machine vectors to 0x%08lx/n", base);
    #ifdef CONFIG_CPU_32
    modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
    #endif
    }

        vectors_base是一个宏,它的作用是获取ARM异常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中定义:

    extern unsigned long cr_no_alignment; /* defined in entry-armv.S */
    extern unsigned long cr_alignment; /* defined in entry-armv.S */

    #if __LINUX_ARM_ARCH__ >= 4
    #define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
    #else
    #define vectors_base() (0)
    #endif

      对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器 (control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则 为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)
    下面分析一下cr_alginment的值是在哪确定的,我们在arch/arm/kernel/entry-armv.S找到cr_alignment的定义:

                    .globl SYMBOL_NAME(cr_alignment)
                    .globl SYMBOL_NAME(cr_no_alignment)
    SYMBOL_NAME(cr_alignment):
                    .space 4
    SYMBOL_NAME(cr_no_alignment):
                    .space 4

    分析过head-armv.S文件的朋友都会知道,head-armv.S是非压缩内核的入口:
                  
    1               .section ".text.init",#alloc,#execinstr
    2               .type   stext, #function
    3ENTRY(stext)   
    4               mov     r12, r0
    5               
    6               mov     r0, #F_BIT | I_BIT | MODE_SVC   @ make sure svc mode
    7               msr     cpsr_c, r0                      @ and all irqs disabled
    8               bl      __lookup_processor_type        
    9               teq     r10, #0                         @ invalid processor?
    10               moveq   r0, #'p'                        @ yes, error 'p'
    11               beq     __error
    12               bl      __lookup_architecture_type
    13               teq     r7, #0                          @ invalid architecture?
    14               moveq   r0, #'a'                        @ yes, error 'a'
    15               beq     __error
    16               bl      __create_page_tables           
    17               adr     lr, __ret                       @ return address
    18               add     pc, r10, #12                    @ initialise processor
    19                                                       @ (return control reg)
    20
    21               .type   __switch_data, %object
    22__switch_data: .long   __mmap_switched
    23                .long   SYMBOL_NAME(__bss_start)
    24                .long   SYMBOL_NAME(_end)
    25                .long   SYMBOL_NAME(processor_id)
    26                .long   SYMBOL_NAME(__machine_arch_type)
    27                .long   SYMBOL_NAME(cr_alignment)
    28                .long   SYMBOL_NAME(init_task_union)+8192
    29
    30                .type   __ret, %function
    31__ret:          ldr     lr, __switch_data
    32                mcr     p15, 0, r0, c1, c0
    33                mrc     p15, 0, r0, c1, c0, 0           @ read it back.
    34                mov     r0, r0
    35                mov     r0, r0
    36                mov     pc, lr

      这里我们关心的是从17行开始,17行code处将lr放置为__ret标号处的相对地址,以便将来某处返回时跳转到31行继续运行;
    18行,对于我所分析的pxa270平台,它将是跳转到arch/arm/mm/proc-xscale.S中执行__xscale_setup函数, 在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其中包括设置V位=1),但在__xscale_setup中,r1寄存 器并不立即写回到Cp15的control register中,而是在返回后的某个地方,接下来会慢慢分析到。__xscale_setup调用move pc, lr指令返回跳转到31行。
    31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
    32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。

    接下来再来看一下跳转到__mmap_switched处的代码:
    40 _mmap_switched:
    41                 adr     r3, __switch_data + 4
    42                 ldmia   r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
    43                                                        @ sp = stack pointer
    44
    45                 mov     fp, #0                          @ Clear BSS (and zero fp)
    46 1:              cmp     r4, r5
    47                 strcc   fp, [r4],#4
    48                 bcc     1b
    49
    50                 str     r9, [r6]                        @ Save processor ID
    51                 str     r1, [r7]                        @ Save machine type
    52                 bic     r2, r0, #2                      @ Clear 'A' bit
    53                 stmia   r8, {r0, r2}                    @ Save control register values
    54                 b       SYMBOL_NAME(start_kernel)

      41~42行的结果是:r4=__bss_start,r5=__end,...,r8=cr_alignment,..,这里r8保存的是cr_alignment变量的地址.
    到了53行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。

      让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
           .align 5
    __stubs_start:
    vector_IRQ:
    ...
    vector_data:
        ....
    vector_prefetch:
    ...                                                                                                                       
    vector_undefinstr:
    ...
    vector_FIQ: disable_fiq
    subs pc, lr, #4
    vector_addrexcptn:
    b vector_addrexcptn       
           ...
    __stubs_end:
       .equ __real_stubs_start, .LCvectors + 0x200

    .LCvectors: swi SYS_ERROR0
       b __real_stubs_start + (vector_undefinstr - __stubs_start)
       ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
       b __real_stubs_start + (vector_prefetch - __stubs_start)
       b __real_stubs_start + (vector_data - __stubs_start)
       b __real_stubs_start + (vector_addrexcptn - __stubs_start)
       b __real_stubs_start + (vector_IRQ - __stubs_start)
       b __real_stubs_start + (vector_FIQ - __stubs_start)

    ENTRY(__trap_init)
           stmfd sp!, {r4 - r6, lr}   /* 压栈,保存数据*/

           /* 复制异常向量表(.LCvectors起始的8个地址)到r0指向的地址(异常向量地址),r0就是__trap_init(base)函数调用时传递的参数,不明白的请参考ATPCS*/
           adr r1, .LCvectors    @ set up the vectors
           ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
       stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
      
       /* 在异常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个异常处理代码*/
       add r2, r0, #0x200
       adr r0, __stubs_start   @ copy stubs to 0x200
       adr r1, __stubs_end
    1:               ldr r3, [r0], #4
    str r3, [r2], #4
    cmp r0, r1
                      blt 1b
                      LOADREGS(fd, sp!, {r4 - r6, pc}) /*出栈,恢复数据,函数__trap_init返回*/

        __trap_init函数填充后的向量表如下:
        虚拟地址       异常              处理代码
        0xffff0000       reset              swi SYS_ERROR0
        0xffff0004      undefined       b __real_stubs_start + (vector_undefinstr - __stubs_start)
        0xffff0008      软件中断      ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
        0xffff000c      取指令异常   b __real_stubs_start + (vector_prefetch - __stubs_start)
        0xffff0010      数据异常      b __real_stubs_start + (vector_data - __stubs_start)
        0xffff0014      reserved         b __real_stubs_start + (vector_addrexcptn - __stubs_start)
        0xffff0018      irq                  b __real_stubs_start + (vector_IRQ - __stubs_start)
        0xffff001c      fiq                   b __real_stubs_start + (vector_FIQ - __stubs_start)
        
    当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而 且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这 里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

    -------------------------参考资料--------------------
    1, 刘淼,嵌入式系统接口设计与Linux驱动程序开发,北京航天航空大学出版社,2006.
    2, ARM Architecture Reference Manual, ARM limited,2000.

     

     原文地址 http://hi.baidu.com/_%C5%CE%C8%FD%C4%EA_/blog/item/cceac934d9dd95b1d1a2d370.html

    展开全文
  • 中断之中断向量表IDT的初始化

    千次阅读 2016-08-25 21:54:48
    中断的初始化是在哪里完成的呢?是在start_kernel()中: 512 trap_init(); 535 /* init some links before init_ISA_irqs() */ 536 early_irq_init();  537 init_IRQ();...中断向量表初始化分为

    link from   http://blog.csdn.net/sunnybeike/article/details/6958473

    中断的初始化是在哪里完成的呢?是在start_kernel()中:

    512	trap_init();
    
    535    /* init some links before init_ISA_irqs() */
    536    early_irq_init();
     537    init_IRQ(); //最终调用native_init_IRQ,由它来完成主要工作。

    中断向量表的初始化分为两个部分:

    (1)对0~19号和0x80号系统保留中断向量的初始化,在trap_init中完成

    (2)对其它中断向量的初始化,在init_IRQ中完成。

    我们首先来看对系统保留中断向量的初始化,这部分的初始化工作实在trap_init中完成的:

    void __init trap_init(void)
    {
    	int i;
    
    #ifdef CONFIG_EISA
    	void __iomem *p = early_ioremap(0x0FFFD9, 4);
    
    	if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
    		EISA_bus = 1;
    	early_iounmap(p, 4);
    #endif
    
    	set_intr_gate(0, ÷_error);
    	set_intr_gate_ist(2, &nmi, NMI_STACK);
    	/* int4 can be called from all */
    	set_system_intr_gate(4, &overflow);
    	set_intr_gate(5, &bounds);
    	set_intr_gate(6, &invalid_op);
    	set_intr_gate(7, &device_not_available);
    #ifdef CONFIG_X86_32
    	set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
    #else
    	set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
    #endif
    	set_intr_gate(9, &coprocessor_segment_overrun);
    	set_intr_gate(10, &invalid_TSS);
    	set_intr_gate(11, &segment_not_present);
    	set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
    	set_intr_gate(13, &general_protection);
    	set_intr_gate(15, &spurious_interrupt_bug);
    	set_intr_gate(16, &coprocessor_error);
    	set_intr_gate(17, &alignment_check);
    #ifdef CONFIG_X86_MCE
    	set_intr_gate_ist(18, &machine_check, MCE_STACK);
    #endif
    	set_intr_gate(19, &simd_coprocessor_error);
    
    	/* Reserve all the builtin and the syscall vector: */
    	for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
    		set_bit(i, used_vectors);
    
    #ifdef CONFIG_IA32_EMULATION
    	set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
    	set_bit(IA32_SYSCALL_VECTOR, used_vectors);
    #endif
    
    #ifdef CONFIG_X86_32
    	set_system_trap_gate(SYSCALL_VECTOR, &system_call);
    	set_bit(SYSCALL_VECTOR, used_vectors);
    #endif
    
    	/*
    	 * Should be a barrier for any external CPU state:
    	 */
    	cpu_init();
    
    	x86_init.irqs.trap_init();  //这个是什么意思呢?好像跟虚拟机有关,我们不用关注。
    }
    

    程序中首先设置中断向量表的头19个陷阱门,这些中断向量表都是CPU保留用于异常处理的。 

    接着,有这样的操作:

    <pre name="code" class="cpp">/* Reserve all the builtin and the syscall vector: */  
    for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)  
        set_bit(i, used_vectors); 


    系统设置了一个位图used_vectors,来表示每个中断向量表的使用情况,FIRST_EXTERNAL_VECTOR = 20,
    可以看到,这里是将前20(0~19)个向量表项对应的位图设置为1,表示已经被占用了。

    紧接着:
    set_system_trap_gate(SYSCALL_VECTOR, &system_call);

    初始化系统调用向量。SYS_CALL_VECTOR = 0x80。这里有一个这样的问题,为什么用的是set_system_trap_gate,而不是像2.4那样用set_system_gate呢?
    我们可以将系统调用看成是trap,因为它同样要陷入内核空间,可以这样理解,当陷入陷阱时,EIP指向的是下一条指令,
    而当故障(fault)发生时,EIP指向当前指令,当异常发生时,EIP的指向是不固定的,因此想想系统调用后EIP的变化,它必然是属于陷阱范畴的。

    接下来,看这样一个操作cpu_init()
    /*
     * cpu_init() initializes state that is per-CPU. Some data is already
     * initialized (naturally) in the bootstrap process, such as the GDT
     * and IDT. We reload them nevertheless, this function acts as a
     * 'CPU state barrier', nothing should get across.
     * A lot of state is already set up in PDA init for 64 bit
     */
    
    void __cpuinit cpu_init(void)
    {
        int cpu = smp_processor_id();
        struct task_struct *curr = current;
        struct tss_struct *t = &per_cpu(init_tss, cpu);
        struct thread_struct *thread = &curr->thread;
    
        if (cpumask_test_and_set_cpu(cpu, cpu_initialized_mask)) { //test a bit and return its old value;测试cpumask是否已经被设置了。
            printk(KERN_WARNING "CPU#%d already initialized!\n", cpu);
            for (;;)
                local_irq_enable(); //实际上就是执行开中断指令:sti
        }
    
        printk(KERN_INFO "Initializing CPU#%d\n", cpu);
    
        if (cpu_has_vme || cpu_has_tsc || cpu_has_de)
            clear_in_cr4(X86_CR4_VME|X86_CR4_PVI|X86_CR4_TSD|X86_CR4_DE);
    
        load_idt(&idt_descr); //下文详述
        switch_to_new_gdt(cpu);
    
        /*
         * Set up and load the per-CPU TSS and LDT
         */
        atomic_inc(&init_mm.mm_count);
        curr->active_mm = &init_mm;
        BUG_ON(curr->mm);
        enter_lazy_tlb(&init_mm, curr);
    
        load_sp0(t, thread);
        set_tss_desc(cpu, t);
        load_TR_desc();
        load_LDT(&init_mm.context);
    
        t->x86_tss.io_bitmap_base = offsetof(struct tss_struct, io_bitmap);
    
    #ifdef CONFIG_DOUBLEFAULT
        /* Set up doublefault TSS pointer in the GDT */
        __set_tss_desc(cpu, GDT_ENTRY_DOUBLEFAULT_TSS, &doublefault_tss);
    #endif
    
        clear_all_debug_regs();
        dbg_restore_debug_regs();
    
        fpu_init();
        xsave_init();
    }
    
    



    
    

    我们来看一下load_idt()操作,这是一个宏定义:

    #define load_idt(dtr)				native_load_idt(dtr)
    

    在展开宏之前,先来看一下idt_descr结构:

    struct desc_ptr idt_descr = { NR_VECTORS * 16 - 1, (unsigned long) idt_table };
    
    //desc_ptr结构体如下:
    struct desc_ptr {
        unsigned short size;
        unsigned long address;
    } __attribute__((packed)) ;
    
    //idt_table如下:
    extern gate_desc idt_table[];
    //接着看gate_desc:
    typedef struct desc_struct gate_desc;
    
    //好了,我们终于来到了终极目标,desc_struct:
    
    /*
     * FIXME: Accessing the desc_struct through its fields is more elegant,
     * and should be the one valid thing to do. However, a lot of open code
     * still touches the a and b accessors, and doing this allow us to do it
     * incrementally. We keep the signature as a struct, rather than an union,
     * so we can get rid of it transparently in the future -- glommer
     */
    /* 8 byte segment descriptor */
    struct desc_struct {
        union {
            struct {
                unsigned int a;
                unsigned int b;
            };
            struct {
                u16 limit0;
                u16 base0;
                unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
                unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
            };
        };
    } __attribute__((packed));
    
    
    

    这样可以看到,idt_desc实际上是以idt_table为起始地址,拥有NR_VECTOR*16-1个8个字节长的描述符,而这个描述符结构包含了任务门、中断门、陷阱门和调用门,这几种门的基本结构是相同的,只是相关的字段的设置不同而已,可以参考《情景分析》中相关内容理解。

    接下来,看下面一个函数switch_new_gdt():

    /*
     * Current gdt points %fs at the "master" per-cpu area: after this,
     * it's on the real one.
     */
    void switch_to_new_gdt(int cpu)
    {
    	struct desc_ptr gdt_descr;
    
    	gdt_descr.address = (long)get_cpu_gdt_table(cpu); //指向给定cpu的gdt <span style="color:#FF0000;">(有个问题,难倒每个CPU都有一个GDT么?)</span>
    	gdt_descr.size = GDT_SIZE - 1;  //gdb_desc为32项
    	load_gdt(&gdt_descr); //执行lgdt指令将gdb_descr的地址加载到相应的寄存器中。
    
                 //#define load_gdt(dtr)                native_load_gdt(dtr)
    
                 //static inline void native_load_gdt(const struct desc_ptr *dtr)
                 //{
                 //  asm volatile("lgdt %0"::"m" (*dtr));
                 //}
    
    
            /* Reload the per-cpu base */
    
    	load_percpu_segment(cpu);
    } 
    

    这样看来,应该是每个CPU对应一个GDT的。

    我们好像忘了很重要的东西:

    (1)set_intr_gate

    /*
     * This needs to use 'idt_table' rather than 'idt', and
     * thus use the _nonmapped_ version of the IDT, as the
     * Pentium F0 0F bugfix can have resulted in the mapped
     * IDT being write-protected.
     */
    static inline void set_intr_gate(unsigned int n, void *addr)
    {
    	BUG_ON((unsigned)n > 0xFF);
    	_set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS); //设置中断门
    }
    

    (2)set_system_intr_gate

    /*
     * This routine sets up an interrupt gate at directory privilege level 3.
     */
    static inline void set_system_intr_gate(unsigned int n, void *addr)
    {
    	BUG_ON((unsigned)n > 0xFF);
    	_set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS); //设置中断门
    }
    

    (3)set_task_gate

    static inline void set_task_gate(unsigned int n, unsigned int gdt_entry)
    {
    	BUG_ON((unsigned)n > 0xFF);
    	_set_gate(n, GATE_TASK, (void *)0, 0, 0, (gdt_entry<<3)); //设置任务门
    }
    

    (4)set_system_trap_gate

    static inline void set_system_trap_gate(unsigned int n, void *addr)
    {
    	BUG_ON((unsigned)n > 0xFF);
    	_set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS); //设置陷阱门
    }
    

    我们观察一下这几个函数,都是调用_set_gate,区别在于第二个和第四个参数。第二个参数对应于中断门或陷阱门格式中的D标志位加上类型位段;第四个参数对应于DPL。下面,我们来看一下_set_gate函数:

    static inline void _set_gate(int gate, unsigned type, void *addr,
    			     unsigned dpl, unsigned ist, unsigned seg)
    {
    	gate_desc s;
    
    	pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); //根据门的类型、入口地址等信息,组装一个64位的门描述符,保存在a,b中
    	/*
    	 * does not need to be atomic because it is only done once at
    	 * setup time
    	 */
    	write_idt_entry(idt_table, gate, &s); //将a,b写入idt。
    }
    

    其第一个操作pack_gate():

    static inline void pack_gate(gate_desc *gate, unsigned char type,
    			     unsigned long base, unsigned dpl, unsigned flags,
    			     unsigned short seg)
    {
    	gate->a = (seg << 16) | (base & 0xffff); //设置门的低32位
    	gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8); //设置门的高32位
    }
    


    而wirte_idt_entry():

    #define write_idt_entry(dt, entry, g)		native_write_idt_entry(dt, entry, g)
    

    static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
    {
    	memcpy(&idt[entry], gate, sizeof(*gate));
    }
    

    以上我们在trap_init()中设置了一些为CPU保留专用的IDT表项以及系统调用所用的陷阱门。

    下面,就进入init_IRQ()设置大量用于外设的通用中断门了:

    void __init init_IRQ(void)
    {
    	int i;
    
    	/*
    	 * We probably need a better place for this, but it works for
    	 * now ...
    	 */
    	x86_add_irq_domains();
    
    	/*
    	 * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
    	 * If these IRQ's are handled by legacy interrupt-controllers like PIC,
    	 * then this configuration will likely be static after the boot. If
    	 * these IRQ's are handled by more mordern controllers like IO-APIC,
    	 * then this vector space can be freed and re-used dynamically as the
    	 * irq's migrate etc.
    	 */
    	for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) //对于单CPU结构,
    		per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
    
    	x86_init.irqs.intr_init();
    }
    

    我们来看这个函数的最后一个操作:

    首先,我们得知道x86_init.irqs.intr_init是什么东西。

    /*
     * The platform setup functions are preset with the default functions
     * for standard PC hardware.
     */
    struct x86_init_ops x86_init __initdata = {
    
    	.resources = {
    		.probe_roms		= probe_roms,
    		.reserve_resources	= reserve_standard_io_resources,
    		.memory_setup		= default_machine_specific_memory_setup,
    	},
    
    	.mpparse = {
    		.mpc_record		= x86_init_uint_noop,
    		.setup_ioapic_ids	= x86_init_noop,
    		.mpc_apic_id		= default_mpc_apic_id,
    		.smp_read_mpc_oem	= default_smp_read_mpc_oem,
    		.mpc_oem_bus_info	= default_mpc_oem_bus_info,
    		.find_smp_config	= default_find_smp_config,
    		.get_smp_config		= default_get_smp_config,
    	},
    <span style="color:#FF0000;">
    	.irqs = {
    		.pre_vector_init	= init_ISA_irqs,
    		.intr_init		= native_init_IRQ,
    		.trap_init		= x86_init_noop,
    	},
    </span>
    	。。。。。。。
            。。。。。。。
     };
    
     原来在init_IRQ的最后一行调用的是native_init_IRQ函数!

    void __init native_init_IRQ(void)
    {
        int i;
    
        /* Execute any quirks before the call gates are initialised: */
        x86_init.irqs.pre_vector_init(); //调用 init_ISA_irqs ,我们将在“中断队列初始化”的博文中详述
    
        apic_intr_init(); //不关心
    
        /*
         * Cover the whole vector space, no vector can escape
         * us. (some of these will be overridden and become
         * 'special' SMP interrupts)
         */
        for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) { //设置20~255号中断
            /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
            if (!test_bit(i, used_vectors)) //要除去0x80中断
                set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
        }
    
        if (!acpi_ioapic && !of_ioapic)
            setup_irq(2, &irq2);
    
    #ifdef CONFIG_X86_32
        /*
         * External FPU? Set up irq13 if so, for
         * original braindamaged IBM FERR coupling.
         */
        if (boot_cpu_data.hard_math && !cpu_has_fpu)
            setup_irq(FPU_IRQ, &fpu_irq);
    
        irq_ctx_init(smp_processor_id());
    #endif
    }

    现在,我们需要看一下好好看一下interrupt数组,它保存的是每个中断服务程序的入口地址,它的定义是在entry_32.S中,一下代码的解释参考

    http://blog.csdn.net/jinhongzhou/article/details/6015551:

    .section .init.rodata,"a"  //定义一个段,.init.rodata表示该段可以被读写操作,"a"表示需要为该段分配内存
    ENTRY(interrupt)           //定义数据段的入口为interrupt
    
    .text     //是告诉连接器,这部分数据是程序代码
        .p2align 5     //advances the location counter until it a multiple of 32. If the location counter is already a multiple of 32, no change is needed. //按32字节对齐
                           ///.p2align指定下一行代码的对齐方式,第1参数表示按2的多少次幂字节对齐,第2参数表示对齐是跨越的位置用什么数据来填充,第3字节表示最多允许跨越多少字节。
    
    
    
        .p2align CONFIG_X86_L1_CACHE_SHIFT         //如果上一行.p2align 5没有执行,那么执行这一条:按2的CONFIG_X86_L1_CACHE_SHIFT次幂的字节对齐,其中CONFIG_X86_L1_CACHE_SHIFT是在内核配置中设定的
    
    ENTRY(irq_entries_start)   //代码段的入口定义为irq_entries_start
    
    
        RING0_INT_FRAME       //宏展开:.macro RING0_INT_FRAME        //# can't unwind into user space anyway
                                             CFI_STARTPROC simple   #define CFI_STARTPROC .cfi_startproc //用在每个函数的开始,用于初始化一些内部数据结构
                                             CFI_SIGNAL_FRAME      //#define CFI_SIGNAL_FRAME    .cfi_signal_frame;作用和上面一句类似
                                             CFI_DEF_CFA esp, 3*4  #define CFI_DEF_CFA .cfi_def_cfa //定义计算CFA的规则
                                             /*CFI_OFFSET cs, -2*4;*/
                                             CFI_OFFSET eip, -3*4  //#define CFI_OFFSET .cfi_offset //xx reg ,offset reg中的值保存在offset中,offset是CFA的
                                            .endm
    
    vector=FIRST_EXTERNAL_VECTOR   //#define FIRST_EXTERNAL_VECTOR        0x20  在irq_verctors.h中,定义了0~31号内部中断
    .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7              //.rept表示循环,#define NR_VECTORS             256,为256-32+6=230;230/7=32
        .balign 32                                       //按32字节对齐
    
      .rept    7                                               //加上前面的那个rept,则需要循环32*7=224次,这有点类似于两个for循环,在每次进行内循环时都要进行32字节的对齐操作
    
    
    
    
        .if vector < NR_VECTORS               //vector=0x20;NR_VECCTORS=256;         
          .if vector <> FIRST_EXTERNAL_VECTOR
        CFI_ADJUST_CFA_OFFSET -4     //#define CFI_ADJUST_CFA_OFFSET .cfi_adjust_cfa_offset //xx offset 修改计算CFA的规则,修改前面一个offset。达到按4字节对齐
          .endif
    1:    pushl $(~vector+0x80)    /* Note: always in signed byte range */   ????
        CFI_ADJUST_CFA_OFFSET 4     //4字节对齐
          .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6    //当vector=41,48 等等时,if为假,则不jmp到标号2执行,而这样的情况总共有32次:我不知道为什么??
        jmp 2f                   //数字定义的标号为临时标号,可以任意重复定义,例如:"2f"代表正向第一次出现的标号"2:",3b代表反向第一次出现的标号"3:"
          .endif
          .previous      //.previous使汇编器返回到该自定义段之前的段进行汇编,则回到上面的数据段
        .long 1b     //在数据段中执行标号1的操作
          .text          //回到代码段
    vector=vector+1      //vector增加1
        .endif
      .endr      
    2:    jmp common_interrupt
    .endr   //结束224次循环
    END(irq_entries_start)  //结束代码段
    
    .previous  //返回数据段,结束数据段的interrupt
    END(interrupt)
    .previous  //返回定义数据段之前定义的那个段
    

    如果你没有兴趣仔细研读这段代码,那么记着下面的解释就行了:数组中每个元素的初始值是标号1的地址。因此访问数组中的元素时,都会跳到标号1处,执行相应的指令。

    也就是,在除了0~19号和0x80号中断外,其余的所有中断在进入其自己的中断服务程序之前,必须是先条转执行common_interrupt的,而在“中断队列初始化”的博文中,我将详细讲述有关common_interrupt的知识。
    现在,我们已经把中断向量表IDT初始化完毕了。

    展开全文
  • 一、中断中断通常被定义为一个事件,该事件改变处理器执行的指令...另一种是由CPU本身在执行程序的过程中产生的,例如X86中的“INT n”。Intel手册中分别将这两种中断称为中断和异常。Intel文档中,中断又可分为可屏蔽

    一、中断

    中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。中断有两种,一种是由CPU外部产生的,对于执行中的软件来说,这种中断的发生完全是“异步”的,根本无法预料此类中断会在什么时候发生,一般由其他硬件设备产生(例如键盘中断);另一种是由CPU本身在执行程序的过程中产生的,例如X86中的“INT n”。Intel手册中分别将这两种中断称为中断和异常。Intel文档中,中断又可分为可屏蔽中断和非屏蔽中断,异常分为:故障、陷阱、异常中止和编程异常。不管是哪种中断,CPU的相应过程基本上一致,即:在执行完当前指令以后,或者在执行当前指令中途,根据中断源提供的“中断向量”,在内存中找到相应的服务程序入口并调用该服务程序。外部中断的向量是由软件或硬件设置好了的,陷阱的向量是在“自陷”指令中发出的(INT n中的n),而各种异常的向量则是CPU的硬件结构中预先规定好的。系统调用一般是通过INT指令实现的,所以也与中断密切相关。

    Intel X86 CPU支持256个不同的中断向量,早期X86 CPU的中断响应机制是非常简单的,内存中从0开始的1K字节作为一个中断向量表,表中每个表项占四个字节,由两字节的段地址和两字节的位移组成,构成的地址便是相应中断服务程序的入口地址。由于中断发生时,有可能涉及用户态和内核态的切换,那种简单的表项无法实现这种运行模式的切换,所以在Intel实现保护模式时,中断向量表中的表项从单纯的入口地址改成了更为复杂的描述项,称为“门”,当中断发生时必须通过这些门,才能进入相应的服务程序。按不同的用途和目的,CPU中一共有四种门:任务门、中断门、陷阱门和系统门(除了系统门外,其它三种都是用户态进程无法访问的中断门)。其中除任务门外其它三种门的结构基本相同,先看任务门,其大小为64位,结构如下图:

    这里写图片描述

    当中断发生时,CPU在中断向量表中找到相应表项,如果是一个任务门,CPU就会将当前任务的运行现场保存在相应的TSS中,并将任务门所指向的TSS作为当前任务,将其内存装入CPU中的各个寄存器,从而完成一次任务切换;

    其它三种门大小也是64位,如下图:

    这里写图片描述

    三种门之间的区别为3位的类型码。与任务门相比,不同之处主要在于:任务门中不需要段内位移,因为任务门指向一个段;而其它门则指向一个子程序,所以必须结合使用段选择码和段内位移。

    前面讲过,内核通过门描述符来实现运行模式的切换,这是怎么实现的呢?答案就在描述符的DPL字段,CPU先根据中断向量找到一扇门描述项,然后,将这个门的DPL与CPU的CPL相比,CPL必须小于或等于DPL,也就是优先级别不低于DPL,才能穿过这扇门。不过,如果中断是由外部产生或是因CPU异常而产生的话,就免去这一层检验。所以通过将系统门的DPL设为3,而其它的门的DPL设为0,就可以使用户态进程只能访问系统门,从而防止用户通过int指令模拟非法的中断和异常。

    进入中断服务程序时,如果中断服务程序的运行级别,也就是目标代码段的DPL,与中断发生时的CPL不同,那就要引起堆栈的更换。Linux中是这样更换堆栈的:如果进程thread_union结构大小为8KB,则当前进程的内核栈被用于所有类型的内核控制路径;相反,如果thread_union结构大小为4KB,内核使用三种类型的内核栈:

    • 异常栈:用于处理异常(包括系统调用)。这个栈包括在每个进程的thread_union数据结构中。
    • 硬中断请求栈:用于处理中断。系统中每个CPU都有一个硬中断请求栈,而且每个栈占用一个单独的页框。
    • 软中断请求栈:用于处理可延迟函数(软中断或tasklet)。系统中每个CPU都有一个软中断请求栈,而且每个栈占用一个单独的页框。

    二、中断向量表的初始化

    Linux内核在初始化阶段完成了页式虚存管理的初始化以后,便调用trap_init()和init_IRQ两个函数进行中断机制的初始化。其中trap_init()中主要是对一些系统保留的中断向量的初始化,而init_IRQ()则主要是用于外设的中断,这两个函数源码如下:

    1. trap_init()是在arch/i386/kelnel/traps.c中定义的:

          void __init trap_init(void)
          {
          #ifdef CONFIG_EISA
              void __iomem *p = ioremap(0x0FFFD9, 4);
              if (readl(p) == 'E'+('I'<<8)+('S'<<16)+('A'<<24)) {
                  EISA_bus = 1;
              }
              iounmap(p);
          #endif
      
          /*
           * APIC为高级可编程中断控制器
           */
          #ifdef CONFIG_X86_LOCAL_APIC
              init_apic_mappings();
          #endif
      
              /*
               * 程序中先设置中断向量表开头的19个陷阱门,这些中断向量都是CPU保留用于
               * 异常处理的,例如中断向量14就是为页面异常保留的
               */
              set_trap_gate(0,&divide_error);
              set_intr_gate(1,&debug);
              set_intr_gate(2,&nmi);
              set_system_intr_gate(3, &int3); /* int3-5 can be called from all */
              set_system_gate(4,&overflow);
              set_system_gate(5,&bounds);
              set_trap_gate(6,&invalid_op);
              set_trap_gate(7,&device_not_available);
              set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
              set_trap_gate(9,&coprocessor_segment_overrun);
              set_trap_gate(10,&invalid_TSS);
              set_trap_gate(11,&segment_not_present);
              set_trap_gate(12,&stack_segment);
              set_trap_gate(13,&general_protection);
              set_intr_gate(14,&page_fault);
              set_trap_gate(15,&spurious_interrupt_bug);
              set_trap_gate(16,&coprocessor_error);
              set_trap_gate(17,&alignment_check);
          #ifdef CONFIG_X86_MCE
              set_trap_gate(18,&machine_check);
          #endif
              set_trap_gate(19,&simd_coprocessor_error);
      
              /*
               * 然后是对系统调用的初始化,常数SYSCALL_VECTOR定义为0x80
               */
              set_system_gate(SYSCALL_VECTOR,&system_call);
      
              /*
               * Should be a barrier for any external CPU state.
               */
              cpu_init();
      
              trap_init_hook();
          }

      从程序中可以看到,这里用了三个函数来进行这些表项的初始化,分别为set_trap_gate()、set_system_gate()和set_intr_gate(),在同文件中定义如下:

       /*
           * 陷阱门
           */
          static void __init set_trap_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
          }
      
          /*
           * 系统门
           */
          static void __init set_system_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
          }
      
          /*
           * 中断门
           */
          void set_intr_gate(unsigned int n, void *addr)
          {
              _set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
          }
      
          /* 
           * _set_gate函数如下,代码的理解需要结合上面门描述符的格式
           * %i为输出输入的变量,依次编号,%0与参数gate_addr结合,%1与(gate_addr+1)结合二者为内存单元
           * %2与局部变量__d0结合,存放在寄存器%%eax中,%3与局部变量__d1结合,存放在寄存器%%edx中
           * 输入部中第一个变量为%4,后面的两个变量等价于输出部的%3和%2,即存放在edx和eax中
      
          #define _set_gate(gate_addr,type,dpl,addr,seg) \
          do { \
            int __d0, __d1; \
            __asm__ __volatile__ (
              /* 
               * 将edx设为addr,eax设为(__KERNEL_CS << 16),edx的低16位移入eax的低16位,这样,在
               * %%eax中就形成了所需要的中断门的第一个长整数,其高16位为__KERNEL_CS,低16位为addr的低16位
               */
              "movw %%dx,%%ax\n\t" \
      
              /*
               * 将(0x8000+(dpl<<13)+(type<<8)))装入%%edx的低16位,这样,%%edx中高16位为addr高16位,
               * 而低16位的P位为1,DPL位段为dpl(因为dpl<<13),D位加上类型位段为type,其余各位都为0,这就是中断门的第二个长整数
               */
              "movw %4,%%dx\n\t" \
      
              /* 
               * 将%%eax写入*gate_addr
               */
              "movl %%eax,%0\n\t" \
      
              /*
               * 将%%edx写入*(gate_addr+1)
               */
              "movl %%edx,%1" \
              :"=m" (*((long *) (gate_addr))), \
               "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
              :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
               "3" ((char *) (addr)),"2" ((seg) << 16)); \
          } while (0)
    2. 系统初始化时,在trap_init()中设置了一些为CPU保留的专用的IDT表项以及系统调用所用的陷阱门以后,就要进入init_IRQ()设置大量用于外设的通用中断门了,函数init_IRQ()源码在arch/i386/kernel/i8259.c中:

      void __init init_IRQ(void)
          {
              int i;
      
              pre_intr_init_hook();
      
              for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
                  int vector = FIRST_EXTERNAL_VECTOR + i;
                  if (i >= NR_IRQS)
                      break;
                  if (vector != SYSCALL_VECTOR) 
                      set_intr_gate(vector, interrupt[i]);
              }
      
              /* setup after call gates are initialised (usually add in
               * the architecture specific gates)
               */
              intr_init_hook();
      
              /*
               * Set the clock to HZ Hz, we already have a valid
               * vector now:
               */
              setup_pit_timer();
      
              /*
               * External FPU? Set up irq13 if so, for
               * original braindamaged IBM FERR coupling.
               */
              if (boot_cpu_data.hard_math && !cpu_has_fpu)
                  setup_irq(FPU_IRQ, &fpu_irq);
      
              irq_ctx_init(smp_processor_id());
          }

      pre_intr_init_hook()函数相当于一下代码:

      
      #ifndef CONFIG_X86_VISWS_APIC
      
              init_ISA_irqs();
          #else
              init_VISWS_APIC_irqs();
          #endif
      
          void __init init_ISA_irqs (void)
          {
              int i;
      
          #ifdef CONFIG_X86_LOCAL_APIC
              init_bsp_APIC();
          #endif
              init_8259A(0);
      
              for (i = 0; i < NR_IRQS; i++) {
                  irq_desc[i].status = IRQ_DISABLED;
                  irq_desc[i].action = NULL;
                  irq_desc[i].depth = 1;
      
                  if (i < 16) {
                      /*
                       * 16 old-style INTA-cycle interrupts:
                       */
                      irq_desc[i].handler = &i8259A_irq_type;
                  } else {
                      /*
                       * 'high' PCI IRQs filled in on demand
                       */
                      irq_desc[i].handler = &no_irq_type;
                  }
              }
          }

      init_ISA_irqs()和init_VISWS_APIC_irqs()分别是对8259A中断控制器和高级可编程中断控制器的初始化,并且初始化一个结构数组irq_desc[]。为什么要有这么一个结构数组呢?i386系统结构支持256个中断向量,除去CPU本身保留的,很难说剩下的这些向量是否够用。而且,很多外部设备由于各种原因本来就不得不共用中断向量,所以,Linux通过IRQ共享和IRQ动态分配。因此,系统中为每个中断向量设置一个队列,根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中去,而irq_desc[]中的每个元素则是这样一个队列头部及控制结构。当中断发生时,首先执行与中断向量相对应的一段总服务程序,根据具体的中断源的设备号在其所属队列中找到特定的服务程序加以执行,该结构在下文再详细分析。
      回到init_IRQ()函数,接下来的循环从FIRST_EXTERNAL_VECTOR开始,设立NR_IRQS个中断向量的IDT表项常数FIRST_EXTERNAL_VECTOR为0x20,这里设置的服务程序入口地址都来自一个函数指针interrupt[],该函数指针数组定义如下(arch/i386/kernel/entry.S):

       .data
          ENTRY(interrupt)
          .text
      
          vector=0
          ENTRY(irq_entries_start)
          .rept NR_IRQS
              ALIGN
          1:  pushl $vector-256
              jmp common_interrupt
          .data
              .long 1b
          .text
          vector=vector+1
          .endr

      数组包括NR_IRQS个元素,这个宏产生的数为224或16,当内核支持I/O APIC芯片时,为224,当内核支持8259A时,为16。可以看出,数组中索引为n元素把中断号减256的结果保存在栈中(原因:内核用负数表示所有中断,正数用来表示系统调用,用汇编指令可以很容易判断正负)。由此可以看出,实际上由外设产生的中断处理全部进入一段公共的程序common_interrupt中,在同一文件中定义如下:

      common_interrupt:
              SAVE_ALL
              movl %esp,%eax
              call do_IRQ
              jmp ret_from_intr
      
          /* SAVE_ALL如下 */
          #define SAVE_ALL \
          cld; \
          pushl %es; \
          pushl %ds; \
          pushl %eax; \
          pushl %ebp; \
          pushl %edi; \
          pushl %esi; \
          pushl %edx; \
          pushl %ecx; \
          pushl %ebx; \
          movl $(__USER_DS), %edx; \
          movl %edx, %ds; \
          movl %edx, %es;

      SAVE_ALL在栈中保存中断处理程序可能会使用的所有CPU寄存器,但eflags、cs、eip、ss及esp除外,因为这几个寄存器已经由控制单元自动保存了,然后这个宏把用户数据段选择符装到ds和es寄存器中。保存寄存器值以后,栈顶地址被存放到eax寄存器中,然后中断处理程序调用do_IRQ()函数(这个函数在稍后的博客中将独立分析),函数结束时,控制转到ret_from_intr()(见后面博客)。


    三、中断请求队列的初始化

    从上面的分析可以看出,trap_init()是为特定的中断源初始化中断向量表项的,而init_IRQ()是为外设的通用中断门设置表项的,不过还没完成,也就是说,通用中断的服务程序为空,所以需要进一步的初始化,即本节分析的中断请求队列的初始化。由于通用中断是让多个中断源共用的,而且允许这种共用的结构在系统运行过程中动态地变化,所以在IDT的初始化阶段只是为每个中断向量(表项)准备下一个“中断请求队列”,从而形成一个中断请求队列数组,这就是irq_desc[],定义如下:

        /*
         * 主要是一些函数指针,用于该队列或者说该共用“中断通道”的控制(而并不是对具体中断源的服务)
         * 具体函数取决于所用的中断控制器。例如,enable和disable用来开启和关闭其所属通道,ack用于对中断控制器的响应
         * 而end则用于每次中断服务返回前夕,这些函数都是在init_IRQ()中调用pre_intr_init_hook()设置好的
         */
        struct hw_interrupt_type {
            const char * typename;
            unsigned int (*startup)(unsigned int irq);
            void (*shutdown)(unsigned int irq);
            void (*enable)(unsigned int irq);
            void (*disable)(unsigned int irq);
            void (*ack)(unsigned int irq);
            void (*end)(unsigned int irq);
            void (*set_affinity)(unsigned int irq, cpumask_t dest);
        };
    
        typedef struct hw_interrupt_type  hw_irq_controller;
    
        typedef struct irq_desc {
            /*
             * 指向PIC对象,服务于IRQ线
             */
            hw_irq_controller *handler;
            /*
             * PIC方法所使用的数据
             */
            void *handler_data;
            /*
             * 用来维持一个由中断服务程序描述项构成的单链队列
             */
            struct irqaction *action;   /* IRQ action list */
            unsigned int status;        /* IRQ status */
            unsigned int depth;     /* nested irq disables */
    
            /* 下面两项在诊断时使用 */
            unsigned int irq_count;     /* For detecting broken interrupts */
            unsigned int irqs_unhandled;
            spinlock_t lock;
        } ____cacheline_aligned irq_desc_t;
    
        extern irq_desc_t irq_desc [NR_IRQS];
    
        struct irqaction {
            /*
             * 最主要的就是函数指针handle,指向具体的中断服务程序
             */
            irqreturn_t (*handler)(int, void *, struct pt_regs *);
            unsigned long flags;
            cpumask_t mask;
    
            /* I/O设备名和私有字段 */
            const char *name;
            void *dev_id;
    
            struct irqaction *next;
            int irq;
            struct proc_dir_entry *dir;
        };

    真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_irq()向系统“登记”,挂入某个中断请求队列以后才会发生,该函数如下:

        /*
         * 参数irq为中断请求队列的序号,也就是人们通常说的“中断请求号”,对应于中断控制器中的一个通道,
         * 有时候要在接口卡上通过微型开关或跳线来设置,注意与中断向量的区别
         * 在内核中,设备驱动程序一般都要通过request_irq()向系统登记其中断服务程序
         */
        int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
             unsigned long irq_flags, const char * devname, void *dev_id)
        {
            unsigned long retval;
            struct irqaction *action;
    
            if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
                (irq_flags & SA_SHIRQ && !dev_id))
                return -EINVAL;
    
            action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
            if (!action)
                return -ENOMEM;
    
            action->handler = handler;
            action->flags = irq_flags;
            cpus_clear(action->mask);
            action->name = devname;
            action->next = NULL;
            action->dev_id = dev_id;
    
            retval = setup_irq(irq, action);
    
            if (retval)
                kfree(action);
            return retval;
        }
    

    在分配并设置了一个irqaction数据结构action后,便调用setup_irq(),将其链入相应的中断请求队列中,函数如下:

        int setup_irq(unsigned int irq, struct irqaction *new)
        {
            int shared = 0;
            struct irqaction *old, **p;
            unsigned long flags;
            struct irqdesc *desc;
    
            if (new->flags & SA_SAMPLE_RANDOM) {
                /*
                 * 为中断请求队列初始化一个数据结构,用来记录该中断的时序
                 */
                rand_initialize_irq(irq);
            }
    
            /*
             * The following block of code has to be executed atomically
             */
            desc = irq_desc + irq;
            spin_lock_irqsave(&irq_controller_lock, flags);
            p = &desc->action;
            if ((old = *p) != NULL) {
                /* 
                 * 检查是否允许共用一个中断通道,只有在新加入的结构以及队列中的第一个结构都允许共用时才将其链入队列的尾部
                 */
                if (!(old->flags & new->flags & SA_SHIRQ)) {
                    spin_unlock_irqrestore(&irq_controller_lock, flags);
                    return -EBUSY;
                }
    
                /* add new interrupt at end of irq queue */
                do {
                    p = &old->next;
                    old = *p;
                } while (old);
                shared = 1;
            }
    
            *p = new;
    
            if (!shared) {
                desc->probing = 0;
                desc->running = 0;
                desc->pending = 0;
                desc->disable_depth = 1;
                if (!desc->noautoenable) {
                    desc->disable_depth = 0;
                    desc->chip->unmask(irq);
                }
            }
    
            spin_unlock_irqrestore(&irq_controller_lock, flags);
            return 0;
        }
    
    展开全文
  • Linux内核在初始化阶段完成了对页式

    Linux内核在初始化阶段完成了对页式虚存管理的初始化以后,便调用trap_int()和init_IRQ()两个函数对中断机制的初始化;


    (1)trap_init()主要是对一些系统保留的中断向量的初始化(包括保留专用的IDT表项以及系统调用所用的陷阱门),而init_IRQ()主要是针对外设中断向量的初始化(设置外设的通用中断门);

    (2)在trap_init()中,程序首先设置中断向量开头的19个陷阱门,用于异常处理的,其中中断向量14就是页面异常保留而用的,发生页面映射及访问过程中的发生问题,就会以14为中断向量的异常;然后就是系统调用向量的初始化;其实set_trap_gate()和set_intr_gate()是设置中断门,当中断是由外部中断产生的或CPU异常引起的,总能进入该门,而软件产生的“INT n”总会被拒之门外,因为用户空间的CPL为3,而中断门的CPL为0,,故应该set_trap_gate()使用“INT n”来降低门的DPL,即陷阱门;用set_system_gate()也是设置陷阱门的,因为系统调用也是可中断的,set_call_gate()是linux为了保持与其他UNIX族系统调用使用了改门而设置使用的,本身它并不使用的,上述最终都是调用_set_gate()(使用汇编写的,来设置IDT表项的);

    (3)init_IRQ()中,首先对PC的中断控制器8259A进行初始化,并且初始化一个结构数组irq_desc[];irq_desc[]是因为不足256个向量是不够用的,因此涉及到某些向量的共享,因此在linux限制每一个中断源都必须独占一个中断向量是不现实的;因此linux为每一个中断向量设置一个队列,而根据每一个中断源所产生的中断向量,将其中断服务程序挂到相应的队列中去;首先执行与中断向量对应的一段总服务程序,根据具体的中断向量所对应的一段总服务程序,根据具体中断源的设备号在其所属队列中找到特定的服务程序,因此需要这样一个结构数组;

    (4)init_IRQ()中,紧接着设置NR_IRQS个中断向量的IDT表项,跳过已设置的系统调用向量0x80;函数指针interrupt[]中有16个指针调转函数,其实它们只是中断向量相关的数值不一样,都调用共同的函数;

    (5)init_IRQ()中,要对时钟进行初始化,但是空的,因为要等到对进程调度初始化完毕后,再能让时钟跳动;因为一旦时钟开始跳动,那么进程调度也就随之开始了;

    展开全文
  • 一,中断的定义 二 ,中断处理的过程 三,8086/8088CPU可以处理256种不同类型的终端 四,中断服务程序的设计方法 五中断向量表的建立
  •  第一步就是进行中断向量表的设置。在ARM11中,中断向量表叫做异常向量表。  ARM11共有10种异常,这个在ARM11的datasheet中有。   这里说明一下: 异常 说明 详细说明 Reset 复位
  • //在××it.c中实现这些函数 ,中断就能自动调用了 EXPORT __Vectors __Vectors DCD __initial_sp ; Top of Stack //Cotex-M 要求此处为堆栈顶部地址 DCD Reset_Handler ; Reset Handler DCD NMIException ; NMI ...
  • 1、中断号定义 在stm32f10x.h中定义枚举类型IRQn中,定义了各个中断...CM3的中有一个强大而方便的NVIC,它是属于Cortex内核的器件,中断向量表中60个中断都由它来处理。NVIC是Cortex-M3核心的一部分,关于它的资料...
  • DSP c674x中断向量表的配置

    千次阅读 2018-03-07 21:59:20
    转自:http://www.cnblogs.com/chenchenluo/archive/2012/11/27/2790898.html1. 编写中断服务例程在.c源文件中编写ISR函数c_intXX,用于中断处理,如:...2.初始化中断向量表,并在内存段中的中断向量表中配置好对...
  • ARM中断向量表的简单分析

    千次阅读 2018-01-10 20:38:43
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,...
  • 中断向量表和中断描述符表IDT

    千次阅读 2013-03-28 23:22:20
    因此为了让CPU由中断号查找到对应的中断向量,就需要在内存中建立一张查询表,即中断向量表(在32位保护模式下该表称为中断描述符表)。80x86微机支持256个中断,对应每个中断需要安排一个中断服务程序。在80x86实...
  • BIOS 中断向量表

    2019-04-04 22:02:10
    在bochs虚拟器调试中输入info ivt可以查看BIOS中断向量表 具体说明如下: 中断 描述 INT 00h CPU:除零错,或商不合法时触发 INT 01h CPU:单步陷阱,TF标记为打开状态时,每条指令执行后...
  • STM32中断向量表的偏移量设置方法

    千次阅读 2020-09-28 09:35:13
    总结一下在IAP升级中APP程序中断向量表的偏移 讲解中断偏移之前先看一下程序的启动流程 STM32F4 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此 地址开始写入。此外 STM32F4 是基于...
  • 汇编:中断向量表

    千次阅读 2019-01-13 17:36:28
    中断向量表维护的就是处理相应错误的程序地址。 0号处理中断信息的程序地址 1号处理中断信息的程序地址 2号处理中断信息的程序地址   0: 0*4地址的内容 = IP 0: 0*4+2地址的内容 = CS   1: 1*4地址的内容 ...
  • 28335之中断(2)中断向量表

    千次阅读 2018-05-26 15:11:03
    在每个例子的main函数里边都会初始化中断向量表,什么是向量表?InitPieVectTable()初始化函数可以在DSP280x_PieVect.c找到void InitPieVectTable(void) { int16 i; Uint32 *Source = (void *) &amp;...
  • 至于为什么会提到中断向量表,主要是因为我自己在学习嵌入式Linux开发的过程中,好像学到的所有开始阶段都是要通过汇编完成的,好像没有汇编程序,整套系统就无法运行,那作为一套完整的系统,究竟从上电开始程序...
  • 【STM32】NVIC中断优先级管理(中断向量表

    万次阅读 多人点赞 2018-04-08 19:55:22
    Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核...
  • ARM要求中断向量表必须防放置在从0x00000000地址开始的连续32字节的空间内。ARM9定义的中断向量在向量表中的地址如...中断向量表程序设计如下: CODE32 AREA Startup,CODE,READONLY; /* 异常向量表 */Vectors 
  • 摘要:通常32位ARM嵌入式系统的中断向量表是在程序编译前设置好的,每次编写中断程序都要改C程序的汇编启动代码,相当繁琐。本文给出一种配置ARM中断向量表新方法。该方法比通常方法仅增加一条指令执行时间,简便...
  • DM6437 中断向量表配置

    千次阅读 2009-09-29 09:32:00
    1. 编写中断服务例程 在.c源文件中编写ISR函数c_intXX,用于...初始化中断向量表,并在内存段中的中断向量表中配置好对应的中断向量 首先是把中断向量表定位到某一内存段中,我们可以在cmd文件中配置中断向量表的内存映
  • ARM的中断向量表,及DCD伪指令

    千次阅读 2016-08-06 01:10:08
    在32位ARM系统中,一般都是在中断向量表中放置一条分支指令或PC寄存器加载指令,实现程序跳转到中 断服务例程的功能。例如:  IRQEntry B HandleIRQ ;跳转范围较小  LDR PC,=HandleFIQ  ;LDR伪指令等效生成1条...
  • 核心初始化---异常向量表、svc模式、关闭看门狗、关闭中断、关闭mmu
  • ARM Cortex-M系列之中断向量表

    千次阅读 2020-04-22 14:13:48
    ARM Cortex-M架构的芯片的中断向量表(Interrupt Vector Table)前16位的中断由ARM核设定。16位以后的中断为芯片厂商自行定义。ARM Cortex-M架构芯片一般带有片上闪存(flash)。ARM Cortex-M手册规定在片上闪存起始...
  • ARM系统中断向量表的动态配置

    千次阅读 2012-11-26 22:36:33
    通常情况下32位ARM嵌入式系统的中断向量表程序编译前设置好的。在编写32位ARM 嵌入式系统的中断服务程序、设置和修改ARM体系结构的中断向量表时,常感到相当麻烦,不得不修改汇编代码,对不喜欢使用汇编代码编程的...
  • 9.2 中断向量表的结构

    千次阅读 2019-09-20 21:02:56
    9.2 中断向量表的结构 我现在已经知道了,在运算的时候 一旦遇到了异常情况,就翻到第一页的第一行开始写的这些操作的指示,开始往下执行。开始往下执行,这就能解决问题了。但是问题在于这段操作,解决的是我那个...
  • ARM 中断向量表

    千次阅读 2012-05-22 16:13:36
    以前,我一直很疑惑这个“ARM异常、中断以及他们的向量表”是怎么回事,他们到底是怎么实现的,没有想到今天偶然看到(ARM System Developer's Guide: Designing and Optimizing System Software的ARM异常、中断以及...
  • 关于多核ECU的中断向量表,热心网友问了一个很好的问题 今天研究了一下,先说结论: 每个core都有自己单独的memory去存储自己的中断向量表,而且每个core的中断优先级是相互独立的。也就是说,每个core都可以有...
  • ARM Linux 中断向量表建立流程

    千次阅读 2012-09-27 09:18:02
    一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,126
精华内容 4,850
关键字:

中断向量表的初始化程序