-
2021-05-13 14:16:19
5.4.3 中断向量表
上节已指出CPU是根据中断号获取中断向量值,即对应中断服务程序的入口地址值。因此为了让CPU由中断号查找到对应的中断向量,就需要在内存中建立一张查询表,即中断向量表(在32位保护模式下该表称为中断描述符表)。80x86微机支持256个中断,对应每个中断需要安排一个中断服务程序。在80x86实模式运行方式下,每个中断向量由4字节组成。这4字节指明了一个中断服务程序的段值和段内偏移值。因此整个向量表的长度为1KB。当80x86微机启动时,ROM BIOS中的程序会在物理内存开始地址0x0000:0x0000处初始化并设置中断向量表,而各中断的默认中断服务程序则在BIOS中给出。由于中断向量表中的向量是按中断号顺序排列,因此给定一个中断号N,那么它对应的中断向量在内存中的位置就是0x0000:N×4,即对应的中断服务程序入口地址保存在物理内存0x0000:N×4位置处。
在BIOS执行初始化操作时,它设置了两个8259A芯片支持的16个硬件中断向量和BIOS提供的中断号为0x10~0x1f的中断调用功能向量等。对于实际没有使用的向量则填入临时的哑中断服务程序的地址。以后在系统引导加载操作系统时会根据实际需要修改某些中断向量的值。例如,对于DOS操作系统,它会重新设置中断0x20~0x2f的中断向量值。而对于Linux系统,除了在刚开始加载内核时需要用到BIOS提供的显示和磁盘读操作中断功能,在内核正常运行之前则会在setup.s程序中重新初始化8259A芯片并且在head.s程序中重新设置一张中断向量表(中断描述符表)。完全抛弃了BIOS所提供的中断服务功能。
当Intel CPU运行在32位保护模式下时,需要使用中断描述符表(Interrupt Descriptor Table,IDT)来管理中断或异常。IDT是Intel 8086~80186 CPU中使用的中断向量表的直接替代物。其作用也类似于中断向量表,只是其中每个中断描述符项中除了含有中断服务程序地址以外,还包含有关特权级和描述符类别等信息。Linux操作系统工作于80x86的保护模式下,因此它使用中断描述符表来设置和保存各中断的"向量"信息。
更多相关内容 -
单片机与DSP中的DSP中断向量表和中断子向量表
2020-11-13 07:29:32一个实用DSP程序除包括主程序和系统初始化程序以及存储器配置文件之外,还需要有中断向量表和中断子向量表程序,对于一个DSP控制器来说,中断的使用和管理是不可缺少的。在系统中,控制器的作用就是控制整个系统... -
中断之中断向量表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_gatestatic 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_gatestatic 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是什么东西。
原来在init_IRQ的最后一行调用的是native_init_IRQ函数!/* * 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> 。。。。。。。 。。。。。。。 };
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的知识。
-
DSP28335学习——中断向量表的初始化
2021-10-21 21:15:25F28335的中断源可分为片内外设中断源,如PWM、CAP、QEP、定时器等、片外中断源,外部中断输入引脚XINT1,XINT2引人的外部中断源等。 1、dsp28335三级中断机制 由于外设中断源有58个,而中断线只有12根,这就需要F...在F28335有很多资源,同样也有很多外设,这些外设与相关资源都有可能发布新的任务让内核来判断与处理。F28335的中断源可分为片内外设中断源,如PWM、CAP、QEP、定时器等、片外中断源,外部中断输入引脚XINT1,XINT2引人的外部中断源等。
1、dsp28335三级中断机制
由于外设中断源有58个,而中断线只有12根,这就需要F28335的外设中断扩展模块PIE来进行分配。DSP的外设中断扩展模块结构图如下图所示,F28335的中断采用的3级中断机制,第一级是CPU中断、第二级为PIE级中断、第三级为外设级中断。至于为什么这么设计:由于DSP内部集成了多种外设,每个外设都会产生一个或者多个外设级中断,但是由于CPU中断线有限无法处理所有的外设级中断,因此只能让出12根中断线交给PIE模块进行管理,将所有的外设中断分为了12组,并通过寄存器进行管理。而要实现一次中断响应,通过对下图分析可知,外设级中断要得到CPU的响应需要同时满足两个条件。:1、得到PIE模块的允许(由PIE模块的:PIEIER和PIEIFR寄存器控制分配,由PIEACK控制使能),2、得到CPU的允许(由CPU中断的:IFR和IER寄存器控制分批额,由INTM控制使能)
2、中断向量表的来源
由于每个中断组都有若干个外设中断来源,若这些中断同时到来,则必须要通过软件配置优先级,并且花费较长的CPU处理周期才不会引起冲突,为了解决这个问题,在DSP28335中采用PIE中断向量表来解决复用的问题,通过PIE中断向量表使得可能产生的所有中断都有一个各自独立的32位入口地址,并且提前规定好优先级,CPU的中断优先级是由高到低是INT1~INT12,PIE的中断优先级由高到低是INTx.1~INTx.8。中断向量表如下所示(就不全部放了),大家可以查手把手教学的104页。
3、中断控制寄存器的初始化
在知道了上述的结构后,每次运行程序之前为了保证稳定状态需要对中断的向量表进行初始化。在进行中断向量表初始化之前,需要进行中断控制寄存器的初始化。初始化函数如下所示,从代码中可以看到,对PieCtrlRegs.PIEIER1 ~PieCtrlRegs.PIEIER12的寄存器均进行了置0,从而保证上电后对所有寄存器的稳定状态。
void InitPieCtrl(void) { // Disable Interrupts at the CPU level: 关闭CPU级中断使能 DINT; // Disable the PIE 关闭PIE使能 PieCtrlRegs.PIECTRL.bit.ENPIE = 0; // Clear all PIEIER registers: 清除所有PIEIER寄存器值 PieCtrlRegs.PIEIER1.all = 0; PieCtrlRegs.PIEIER2.all = 0; PieCtrlRegs.PIEIER3.all = 0; PieCtrlRegs.PIEIER4.all = 0; PieCtrlRegs.PIEIER5.all = 0; PieCtrlRegs.PIEIER6.all = 0; PieCtrlRegs.PIEIER7.all = 0; PieCtrlRegs.PIEIER8.all = 0; PieCtrlRegs.PIEIER9.all = 0; PieCtrlRegs.PIEIER10.all = 0; PieCtrlRegs.PIEIER11.all = 0; PieCtrlRegs.PIEIER12.all = 0; // Clear all PIEIFR registers: 清除所有PIEIFR寄存器值 PieCtrlRegs.PIEIFR1.all = 0; PieCtrlRegs.PIEIFR2.all = 0; PieCtrlRegs.PIEIFR3.all = 0; PieCtrlRegs.PIEIFR4.all = 0; PieCtrlRegs.PIEIFR5.all = 0; PieCtrlRegs.PIEIFR6.all = 0; PieCtrlRegs.PIEIFR7.all = 0; PieCtrlRegs.PIEIFR8.all = 0; PieCtrlRegs.PIEIFR9.all = 0; PieCtrlRegs.PIEIFR10.all = 0; PieCtrlRegs.PIEIFR11.all = 0; PieCtrlRegs.PIEIFR12.all = 0; }
4、中断控向量表的初始化
在经过中断若干控制寄存器的初始化后,进行中断向量表的初始化,也即为所有的外设级中断分配地址,具体代码如下所示,代码中指针就是将PieVectTableInit 中的地址指向 PieVectTable中。这里不禁想问了,为什么是128个而不是上面说的8*12 = 96个。这是因为DSP中还存在保留的13位未用中断以及19个非外设中断,总共加起来就是128个。在将地址分配给中断向量表后,就可以方便的通过代码对中断进行设计了,也方便了我们的编程。
void InitPieVectTable(void) { int16 i; Uint32 *Source = (void *) &PieVectTableInit; Uint32 *Dest = (void *) &PieVectTable; EALLOW; for(i=0; i < 128; i++) *Dest++ = *Source++; EDIS; // Enable the PIE Vector Table 使能中断向量表 PieCtrlRegs.PIECTRL.bit.ENPIE = 1; }
-
Linux内核-中断-中断向量表和中断请求队列的初始化
2017-02-08 14:35:01一、中断中断通常被定义为一个事件,该事件改变处理器执行的指令...另一种是由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()则主要是用于外设的中断,这两个函数源码如下:
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,÷_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)
系统初始化时,在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; }
-
关于C语言中DSP5402中断向量表的配置问题
2020-08-11 22:46:06在MCU/ARM/DSP中,都有1个启动过程,这里主要设计复位...复位及中断的入口具有多个间隔固定的程序空间,它们可以是单独的跳转地址及函数指针,如ARM的中断向量表。也可是一块小的固定程序空间,如51的n*8+3和DSP5402的4字。 -
中断技术之中断的定义,中断服务程序的设计方法,中断处理的过程以及中断向量表的建立
2019-11-24 15:17:43一,中断的定义 二 ,中断处理的过程 三,8086/8088CPU可以处理256种不同类型的终端 四,中断服务程序的设计方法 五中断向量表的建立 -
ARM嵌入式系统中断向量表的动态配置
2020-10-19 21:12:48ARM嵌入式系统中断向量表的动态配置,通常32位ARM嵌入式系统的中断向量表是在程序编译前设置好的,每次编写中断程序都要改C程序的汇编启动代码,相当繁琐。本文给出一种配置ARM中断向量表新方法。该方法比通常方法仅... -
嵌入式系统/ARM技术中的ARM嵌入式系统中断向量表的动态配置
2020-12-10 06:51:24摘要:通常32位ARM嵌入式系统的中断向量表是在程序编译前设置好的,每次编写中断程序都要改C程序的汇编启动代码,相当繁琐。本文给出一种配置ARM中断向量表新方法。该方法比通常方法仅增加一条指令执行时间,简便... -
Cortex-M处理器中断向量表
2021-12-31 17:06:40系统复位后中断向量表始终是在0x00000000地址。 系统启动后可以对中断向量表进行重定向: SCB->VTOR = vector_addr; -
【中断】异常和中断的关系、异常向量表和中断向量表的关系
2021-04-27 23:47:49异常向量表和中断向量表的关系 什么是异常? 异常:因为内部或者外部的一些事件,导致处理器停下正在处理的工作,转而去处理这些发生的事件。 当正常的程序执行流程发生暂时的停止时,称之为异常,例如处理一个... -
【STM32】NVIC中断优先级管理(中断向量表)
2018-04-08 19:55:22Cortex-M3内核支持256个中断,其中包含了16个内核中断(异常)和240个外部中断,并且具有256级的可编程中断设置。但是,STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核... -
嵌入式杂谈之中断向量表
2020-04-05 10:00:32至于为什么会提到中断向量表,主要是因为我自己在学习嵌入式Linux开发的过程中,好像学到的所有开始阶段都是要通过汇编完成的,好像没有汇编程序,整套系统就无法运行,那作为一套完整的系统,究竟从上电开始程序... -
中断向量表存储在哪里 产生中断后如何跳转到中服务函数的入口地址处
2022-01-18 23:59:041、中断向量表实际上就是存放在Code区(也就是STM32内部的Flash区)从0x00000000地址开始的一个数组,数组的成员为4个字节,而且这些数组在启动文件的时候已经初始化好。 2、STM32根据内核和外设中断优先级,同一... -
ARM Cortex-M系列之中断向量表
2020-04-22 14:13:48ARM Cortex-M架构的芯片的中断向量表(Interrupt Vector Table)前16位的中断由ARM核设定。16位以后的中断为芯片厂商自行定义。ARM Cortex-M架构芯片一般带有片上闪存(flash)。ARM Cortex-M手册规定在片上闪存起始... -
基于S3C6410的ARM11学习(三) 核心初始化之设置中断向量表
2016-02-22 14:33:51第一步就是进行中断向量表的设置。在ARM11中,中断向量表叫做异常向量表。 ARM11共有10种异常,这个在ARM11的datasheet中有。 这里说明一下: 异常 说明 详细说明 Reset 复位 -
STM32F0芯片IAP实现之中断向量表重映射(没有中断向量表偏移寄存器SCB->VTOR的应对方法)
2021-03-04 17:32:23问题背景 项目使用STM32F030,需要通过IAP进行固件升级,在FLASH里面要烧录两份代码:一个Boot loader,一个用户应用...但在STM32F0xx系列以Cortex-M0为内核的单片机中却怎么也找不到这个设置中断向量表的寄存器,用 -
TC3XX 多核ECU的中断向量表解疑
2020-11-26 05:20:58关于多核ECU的中断向量表,热心网友问了一个很好的问题 今天研究了一下,先说结论: 每个core都有自己单独的memory去存储自己的中断向量表,而且每个core的中断优先级是相互独立的。也就是说,每个core都可以有... -
DSP中断向量表和中断子向量表
2010-06-26 15:08:36详细介绍了DSP中断向量表的编写。以及中断子向量表 -
STM32的中断向量表是干什么的?到底有什么用?它放在哪里?
2020-05-27 10:32:401、中断向量表实际上就是存放在Code区(也就是STM32内部的Flash区)从0x00000000地址开始的一个数组,数组的成员为4个字节,而且这些数组在启动文件的时候已经初始化好。 2、STM32根据内核和外设中断优先级,同一... -
9.2 中断向量表的结构
2019-09-20 21:02:569.2 中断向量表的结构 我现在已经知道了,在运算的时候 一旦遇到了异常情况,就翻到第一页的第一行开始写的这些操作的指示,开始往下执行。开始往下执行,这就能解决问题了。但是问题在于这段操作,解决的是我那个... -
将中断向量表定位到RAM中,从RAM中引导执行中断服务
2020-04-27 11:53:05最近在解决一个问题,看到一篇代码,将中断向量表定位到RAM中,代码所在的文章在这里: https://www.silabs.com/community/mcu/32-bit/knowledge-base.entry.html/2017/05/09/emu_e110_-_potential-i2Pn 大家都... -
Linux内核中断处理机制--初始化
2021-10-22 19:58:19(2)init_IRQ才是中断的初始化。 2、trap_init都干了啥呢? void __init trap_init(void) { .... 把向量表拷贝到ffff0000地址 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); ..... -
ARM中断向量表的简单分析
2018-01-10 20:38:43一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xffff0000~0xffff001c中,知道这是怎么设置的么?开始看到的时候真的有点奇怪,... -
STM32中断向量表的偏移量设置方法
2020-09-28 09:35:13总结一下在IAP升级中APP程序的中断向量表的偏移 讲解中断偏移之前先看一下程序的启动流程 STM32F4 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此 地址开始写入。此外 STM32F4 是基于... -
ARM-A7(IMX6UL)启动过程及中断向量表理解
2022-01-21 15:34:31以下是自己近期学习的理解, 1.一般ARM处理器都是从地址0x00000000开始执行指令,imx6ul的0x...3.启动程序首先要配置中断向量表,即按照手册的中断向量表的顺序完成中断向量表定义,汇编语言每条指令占用4个字节的地址空 -
linux中断向量的初始化.doc
2022-06-24 13:25:14linux中断向量的初始化.doc -
DSP c674x中断向量表的配置
2018-03-07 21:59:20转自:http://www.cnblogs.com/chenchenluo/archive/2012/11/27/2790898.html1. 编写中断服务例程在.c源文件中编写ISR函数c_intXX,用于中断处理,如:...2.初始化中断向量表,并在内存段中的中断向量表中配置好对...