ARM Linux源码分析之内核和异常的初始化过程

中断系统的初始化时由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;

我们首先去了象鼻山,那里景色秀丽神奇,

ARM Linux源码分析之内核和异常的初始化过程

相关文章:

你感兴趣的文章:

标签云: