ARM Linux源码分析之内核和异常的初始化过程
2015-03-22 13:45
489 查看
中断系统的初始化时由start_kernel调用setup_arch进行平台体系(处理器芯片)相关的初始化,然后复制中断向量表到内存中并对irq进行初始化:
代码[2]:复制中断向量表到内存地址CONFIG_VECTORS_BASE
代码[3]: init_IRQ对irq进行初始化时,又调用了init_arch_irq对于具体平台体系的中断进行初始化。而init_arch_irq是在start_kernel里调用setup_arch函数设定的
stats per cpu),指向定义的二维数组中的对应的行。alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。
register)中V位(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)
由上面可得到cr_alignment为零,异常向量则在高端地址,CPU高端寻址。
那么是如何将cr_alignment配置为零的呢?
当在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处的代码:
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:
/* 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_HANDLER handle_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_alignment cr_alignment: .space 4 cr_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 .section ".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 needed 1: 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_alignment cr_alignment: .space 4 cr_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 内核移植及系统初始化过程分析
- Arm linux 内核移植及系统初始化过程分析
- 浅谈分析Arm linux 内核移植及系统初始化的过程(一)
- Arm linux 内核移植及系统初始化过程分析
- Arm linux 内核移植及系统初始化过程分析-非常好-不得不转
- nginx源码分析(2)——http模块的初始化过程
- Linux内核源码分析--内核启动命令行的传递过程(Linux-3.0 ARMv7)
- Arm linux 内核移植及系统初始化过程分析
- 分析内核初始化时根内存盘的加载过程
- Nginx源码分析-启动初始化过程(一)
- Nginx源码分析---Nginx启动初始化过程(一)
- Linux内核--网络协议栈深入分析(四)--套接字内核初始化和创建过程
- Nginx源码分析-启动初始化过程(一)
- struts ModuleConfig类加载初始化的过程以及RequestProcessor类源码分析
- Arm linux 内核移植及系统初始化过程分析
- Nginx源码分析-启动初始化过程(二)
- Nginx源码分析-启动初始化过程(二)
- 分析内核初始化时根内存盘的加载过程
- 分析内核初始化时根内存盘的加载过程
- Linux内核初始化过程的源码分析疑点记录+好书推荐(附下载)