中断系统的初始化时由start_kernel调用setup_arch进行平台体系(处理器芯片)相关的初始化,然后复制中断向量表到内存中并对irq进行初始化:
/* init/main.c */asmlinkage void __init start_kernel(void){ …… [1]setup_arch(&command_line); …… [2]trap_init(); …… [3]init_IRQ(); ……}
代码[1]:进入setup_arch()函数中并调用了early_irq_init()函数 代码[2]:复制中断向量表到内存地址CONFIG_VECTORS_BASE
代码[3]: init_IRQ对irq进行初始化时,又调用了init_arch_irq对于具体平台体系的中断进行初始化。而init_arch_irq是在start_kernel里调用setup_arch函数设定的
1.分析setch_arch()函数中调用的early_irq_init()函数:
void __init setup_arch(char **cmdline_p){#ifdef CONFIG_MULTI_IRQ_HANDLERhandle_arch_irq = mdesc->handle_irq;#endif......early_trap_init();if (mdesc->init_early)mdesc->init_early();}---------------------------------------------------分割线int __init early_irq_init(void){ struct irq_desc *desc; int count; int i; init_irq_default_affinity(); printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS); desc = irq_desc; count = ARRAY_SIZE(irq_desc); for (i = 0; i < count; i++) { desc[i].irq = i; alloc_desc_masks(&desc[i], 0, true); init_desc_masks(&desc[i]); desc[i].kstat_irqs = kstat_irqs_all[i]; } return arch_early_irq_init();}
该函数主要工作即为初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素,它主要设置数组中每一个成员的中断号,使得数组中每一个元素的kstat_irqs字段(irq stats per cpu),指向定义的二维数组中的对应的行。alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。2. trap_init()拷贝中断向量表到高地址,并让cpu发生中断时在高端寻址调用trap_init()前,先列出相关的中断向量表:
/*_ arch/arm/kernel/entry-armv.S */中断向量表_vectors_start: swi SYS_ERROR0 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset b vector_fiq + stubs_offset .globl __vectors_end__vectors_end:
2.1进行中断向量表的拷贝:
/* linux/arch/arm/kernel/traps.c */void __init trap_init(void){ unsigned long vectors = CONFIG_VECTORS_BASE; extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; extern char __kuser_helper_start[], __kuser_helper_end[]; int kuser_sz = __kuser_helper_end - __kuser_helper_start; /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); /* * Copy signal return handlers into the vector page, and * set sigreturn to be a pointer to these. */ memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, sizeof(sigreturn_codes)); flush_icache_range(vectors, vectors + PAGE_SIZE); modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
2.2如何保证cpu在中断的时候高端寻址CONFIG_VECTORS_BASE是一个宏,用来获取ARM异常向量的地址;该宏在/arch/arm/configs/s3c2410_defconfig中定义:
<span style="white-space:pre"></span>CONFIG_VECTORS_BASE=0xffff0000
另外在include/arch/asm-arm/system.h也有相关代码保证CPU在高端寻址。
/* include/arch/asm-arm/system.h*/#define CPU_ARCH_ARMv5 4 #define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */ 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_high() (cr_alignment & CR_V)#else#define vectors_high() (0)#endif-------------------------------------------------------------------分割线/*arch/arm/kernel/entry-armv.S—找到cr_alignment的定义*/ .globl cr_alignment .globl cr_no_alignmentcr_alignment: .space 4cr_no_alignment: .space 4
对于ARMv4以下的版本,这个地址固定为0;ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual) 由上面可得到cr_alignment为零,异常向量则在高端地址,CPU高端寻址。 那么是如何将cr_alignment配置为零的呢?
/*linux/arch/arm/kernel/head.S—当内核启动时,进入head.S文件*/ENTRY(stext) msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a' bl __vet_atags bl __create_page_tables //创建arm启动临时使用的前4M页表 ldr r13, __switch_data @ address to jump to after //99行 @ mmu has been enabled adr lr, __enable_mmu @ return (PIC) address add pc, r10, #PROCINFO_INITFUNC //102行 …… …… …… …… …… …… ……__turn_mmu_on: mov r0, r0 //填充armv4中的三级流水线:mov r0,r0 对//应一个nop,所以对应2个nop和一个mov pc,lr刚好三个"无用"操作 mcr p15, 0, r0, c1, c0, 0 @ write control reg //193行 mrc p15, 0, r3, c0, c0, 0 @ read id reg …… …… …… …… …… …… ……mov pc, lr //327行在s3c2410平台中,它将跳转到arch/arm/mm/proc-arm920.S中执行__arm920 _setup函数。即第102行“add pc, r10, #PROCINFO_INITFUNC”: 执行b _arm920_setup? linux/arch/arm/mm/proc-arm920.S: MMU functions for ARM920.div ".proc.info.init", #alloc, #execinstr .type __arm920_proc_info,#object__arm920_proc_info: .long 0x41009200 .long 0xff00fff0 .long PMD_TYPE_SECT | \ PMD_SECT_BUFFERABLE | \ PMD_SECT_CACHEABLE | \ PMD_BIT4 | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ .long PMD_TYPE_SECT | \ PMD_BIT4 | \ PMD_SECT_AP_WRITE | \ PMD_SECT_AP_READ b __arm920_setup // add pc, r10, #PROCINFO_INITFUNC”:将执行b _arm920_setup .long cpu_arch_name .long cpu_elf_name .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB .long cpu_arm920_name .long arm920_processor_functions …… …… …… …… …… …… …… .size __arm920_proc_info, . - __arm920_proc_info …… …… …… …… …… …… …… …… …… …… …… …… …… …… …….type __arm920_setup, #function__arm920_setup: mov r0, #0 mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4#ifdef CONFIG_MMU mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4#endif adr r5, arm920_crval ldmia r5, {r5, r6} mrc p15, 0, r0, c1, c0 @ get control register v4 bic r0, r0, r5 orr r0, r0, r6 mov pc, lr .size __arm920_setup, . - __arm920_setup
当在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到linux/arch/arm/kernel/head.S文件中的第99行,在lr寄存器中放置__switch_data中的数据__mmap_switched,第327行程序会跳转到__mmap_switched处。在__turn_mmu_on:后,第193,194行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。 接下来再来看一下跳转到__mmap_switched处的代码:
/* linux/arch/arm/kernel/head-common.S */.type __switch_data, %object__switch_data: .long __mmap_switched .long __data_loc @ r4 .long __data_start @ r5 .long __bss_start @ r6 .long _end @ r7 .long processor_id @ r4 .long __machine_arch_type @ r5 .long __atags_pointer @ r6 .long cr_alignment @ r7 .long init_thread_union + THREAD_START_SP @ sp/* * The following fragment of code is executed with the MMU on in MMU mode, * and uses absolute addresses; this is not position independent. * * r0 = cp#15 control register * r1 = machine ID * r2 = atags pointer * r9 = processor ID */.type __mmap_switched, %function__mmap_switched: //40行 adr r3, __switch_data + 4 //41行 ldmia r3!, {r4, r5, r6, r7} //43行 cmp r4, r5 @ Copy data segment if needed1: cmpne r5, r6 ldrne fp, [r4], #4 strne fp, [r5], #4 bne 1b mov fp, #0 @ Clear BSS (and zero fp)1: cmp r6, r7 strcc fp, [r6],#4 bcc 1b //53行 ldmia r3, {r4, r5, r6, r7, sp} // sp ~ (init_task_union)+8192 str r9, [r4] @ Save processor ID str r1, [r5] @ Save machine type str r2, [r6] @ Save atags pointer bic r4, r0, #CR_A @ Clear 'A' bit stmia r7, {r0, r4} @ Save control register values //60行 b start_kernel // 进入内核C程序
41~43行的结果是:r6=__bss_start,r7=__end,…,r7=cr_alignment,..,这里r7保存的是cr_alignment变量的地址。 到了60行,由于之前r0保存的是cp15的control register(c1)的值,这里把r0的值写入r7指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。 让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
/*linux/arch/arm/kernel/entry-armv.S *//*============================================================ * Address exception handler *----------------------------------------------------------------------------- * These aren't too critical. * (they're not supposed to happen, and won't happen in 32-bit data mode). */ vector_addrexcptn: b vector_addrexcptn /* * We group all the following data together to optimise * for CPUs with separate I & D caches. */ .align 5 .LCvswi: .word vector_swi .globl __stubs_end__stubs_end: .equ stubs_offset, __vectors_start + 0x200 - __stubs_start .globl __vectors_start__vectors_start: swi SYS_ERROR0 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset b vector_fiq + stubs_offset .globl __vectors_end__vectors_end: .data .globl cr_alignment .globl cr_no_alignmentcr_alignment: .space 4cr_no_alignment: .space 4
当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARM中b指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。2.3 init_IRQ()
/* linux/arch/arm/kernel/irq.c */void __init init_IRQ(void){ int irq; for (irq = 0; irq < NR_IRQS; irq++) // NR_IRQS代表中断数目 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个//irq_desc结构,组成了一个数组。#ifdef CONFIG_SMP bad_irq_desc.affinity = CPU_MASK_ALL; bad_irq_desc.cpu = smp_processor_id();#endif init_arch_irq();}
/* include/linux/irq.h */ * struct irq_desc - interrupt descriptor * * @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()] * @chip: low level interrupt hardware access * @msi_desc: MSI descriptor * @handler_data: per-IRQ data for the irq_chip methods * @chip_data: platform-specific per-chip private data for the chip * methods, to allow shared chip implementations * @action: the irq action chain * @status: status information * @depth: disable-depth, for nested irq_disable() calls * @wake_depth: enable depth, for multiple set_irq_wake() callers * @irq_count: stats field to detect stalled irqs * @irqs_unhandled: stats field for spurious unhandled interrupts * @last_unhandled: aging timer for unhandled count * @lock: locking for SMP * @affinity: IRQ affinity on SMP * @cpu: cpu index useful for balancing * @pending_mask: pending rebalanced interrupts * @dir: /proc/irq/ procfs entry * @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP * @name: flow handler name for /proc/interrupts output */struct irq_desc { irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */ unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned int irqs_unhandled; unsigned long last_unhandled; /* Aging timer for unhandled count */ spinlock_t lock;#ifdef CONFIG_SMP cpumask_t affinity; unsigned int cpu;#endif#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE) cpumask_t pending_mask;#endif#ifdef CONFIG_PROC_FS struct proc_dir_entry *dir;#endif const char *name;} ____cacheline_internodealigned_in_smp;
我们首先去了象鼻山,那里景色秀丽神奇,