=5内核启动流程(函数列表)(未标记函数作用).docx_第1页
=5内核启动流程(函数列表)(未标记函数作用).docx_第2页
=5内核启动流程(函数列表)(未标记函数作用).docx_第3页
=5内核启动流程(函数列表)(未标记函数作用).docx_第4页
=5内核启动流程(函数列表)(未标记函数作用).docx_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

linux内核启动过程分析嵌入式linux系统从软件角度来看可分为四部分:bootloader,linux内核,文件系统和应用程序。在这里我选取的内核版本是linux2.6.28,硬件平台选择smdk6410。Bootloader是系统启动或复位后首先被执行的代码,它的主要作用是初始化处理器,初始化ram,初始化相应的外设(uart,usb等等),下载 内核映像(或文件系统)到ram相应的位置,然后跳转到内核下载地址 c0008000,将控制权交给linux内核。Linux内核下载到ram中的映像一般是zImage。这是压缩版本的内核,首先要进行解压操作。调用decompress_kernel()(位于 arch/arm/boot/compressed/misc.c)进行解压缩操作,然后再次跳到c0008000,进行真正的内核初始化操作。我们重点放在讲解内核映像解压之后linux内核的启动过程。内核初始化启动过程如下:1) _lookup_processor_type()查找处理器类型。2) _lookup_machine_type()查找机器类型。3) _vet_atags()。函数实现的就是判断r2是否是有效的tag列表指针,如果不是,就将零指针赋值给r2。4) _create_page_tables()创建页表。5) _enable_mmu(),使能MMU。6) _mmap_switched(),拷贝数据,清BBS。7) start_kernel(),进入真正的内核初始化函数。【开始进入】8) smp_setup_processor_id();设备处理器的ID号。9) unwind_init();10)lockdep_init();11) debug_objects_early_init();12) cgroup_init_early();13) local_irq_disable();【关闭中断】14) early_boot_irqs_off();15) early_init_irq_lock_class();16) lock_kernel();【锁定内核】17) tick_init();18) boot_cpu_init();【CPU启动初始化】19) page_address_init();【页表地址初始化】20) setup_arch(&command_line);21) mm_init_owner(&init_mm, &init_task);22) setup_command_line(command_line);23) unwind_setup();24) setup_per_cpu_areas();25) setup_nr_cpu_ids();26) smp_prepare_boot_cpu();27) sched_init();28) preempt_disable();29) build_all_zonelists();30) page_alloc_init();31) parse_early_param();32) sort_main_extable();33) trap_init();34) rcu_init();35) init_IRQ();【中断初始化】36) pidhash_init();37) init_timers();【定时器初始化】38) hrtimers_init();39) softirq_init();【软中断初始化】40) timekeeping_init();41) time_init();【时间初始化】42) sched_clock_init();43) profile_init();44) early_boot_irqs_on();45) local_irq_enable();【本地中断使能】46) console_init();【控制台初始化】【是不是从此开始对外打印信息?】47) lockdep_info();48) locking_selftest();49) vmalloc_init();50) vfs_caches_init_early();51) cpuset_init_early();52) page_cgroup_init();53) mem_init();【内存初始化】54) enable_debug_pagealloc();55) cpu_hotplug_init();56) kmem_cache_init();57) debug_objects_mem_init();58) idr_init_cache();59) setup_per_cpu_pageset();60) numa_policy_init();61) if (late_time_init)62) late_time_init();63) calibrate_delay();64) pidmap_init();65) pgtable_cache_init();66) prio_tree_init();67) anon_vma_init();68) thread_info_cache_init();69) fork_init(num_physpages);70) proc_caches_init();71) buffer_init();【缓冲器初始化】72) key_init();73) security_init();74) vfs_caches_init(num_physpages);【虚拟文件系统缓存初始化】75) radix_tree_init();76) signals_init();77) page_writeback_init();78) proc_root_init();79) cgroup_init();80) cpuset_init();81) taskstats_init_early();82) delayacct_init();83) check_bugs();84) acpi_early_init();85) ftrace_init();86) rest_init();【最后的初始化操作】87) cpu_idle();【函数会被调用来使系统处于闲置(idle)状态并等待用户程序的执行。至此,整个Linux内核启动完毕!】1.1 _lookup_processor_type()话说内核映像解压后,又跳到c0008000这个地址。这个地址指向内核代码的什么地方,我们肯定很想知道。在arch/arm/kernel/vmlinux.lds.S中,可以发现这样的代码:SECTIONS#ifdef CONFIG_XIP_KERNEL . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);#else . = PAGE_OFFSET + TEXT_OFFSET;#endif .text.head : _stext = .; _sinittext = .; *(.text.head) 一般内核都不配置成XIP方式的,所以这段脚本等同于:SECTIONS . = PAGE_OFFSET + TEXT_OFFSET; .text.head : _stext = .; _sinittext = .; *(.text.head) 这段脚本告诉我们SECTIONS的起始地址是 .text.head的起始地址_stext,且_stext= PAGE_OFFSET + TEXT_OFFSET;PAGE_OFSET在.config文件中设置:PAGE_OFFSET=0xC0000000;TEXT_OFFSET在架构目录下的Makefile文件中设置:textofs-y := 0x00008000TEXT_OFFSET := $(textofs-y)结合arch/arm/kernel/head.S,会发现如下代码:.section .text.head, axENTRY(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=cupidENDPROC(stext)第一句代码的意思是表示下面的内容都属于.text.head 段的,”ax”表示这段内容是可分配且可执行的(allocable and executable) 。所以c0008000处放的代码就是stext的入口地址。接下来的msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE就是把fiq_mask(快速中断屏蔽位)和irq_mask(快速中断屏蔽位)都置位,同时把处理器模式设置为svc模式。这就是要告诉闲杂人等不要来 打扰,这里要办重要的事。cpsr_c 代表当前状态寄存器;鉴于当前状态寄存器的重要性,arm特意开发了msr指令,专门用来设置当前状态寄存器。mrc p15, 0, r9, c0, c0是将协处理器cp15 c0的值赋值到r9中。接下来的bl _lookup_processor_type是长跳转到_lookup_processor_type,查看processor ID是否被内核支持。_lookup_processor_type: adr r3, 3f ldmda r3, r5 - r7 sub r3, r3, r7 get offset between virt&phys add r5, r5, r3 convert virt addresses to add r6, r6, r3 physical address space1: ldmia r5, r3, r4 value, mask and r4, r4, r9 mask wanted bits teq r3, r4 beq 2f add r5, r5, #PROC_INFO_SZ sizeof(proc_info_list) cmp r5, r6 blo 1b mov r5, #0 unknown processor2: mov pc, lrENDPROC(_lookup_processor_type)adr是条伪指令,作用就是把标号为3位置的地址赋值给r3寄存器。3后面加f是表示这是个长距离(far)的标号。有同学可能就要问了,ldr也能起到 这个作用,为什么不用ldr?首先 ldr r3, 3f取的是标号3这个地址的内容,而不是地址本身;其次,可以用ldr r3,=3f来取地址本身,但这是一个绝对地址;而adr取得的是相对地址。如果要保证程序在任何内存都能运行,就必须保证代码是地址无关的,也就是 PIC(position independent code)。显然adr伪指令很对PIC的胃口,它的取相对地址方式符合PIC的设定。 .long _proc_info_begin .long _proc_info_end3: .long . .long _arch_info_begin .long _arch_info_end我们接着往下看。ldmda r3, r5 - r7 sub r3, r3, r7 get offset between virt&phys add r5, r5, r3 convert virt addresses to add r6, r6, r3 physical address space1: ldmia r5, r3, r4 value, mask and r4, r4, r9 mask wanted bits teq r3, r4 beq 2f add r5, r5, #PROC_INFO_SZ sizeof(proc_info_list) cmp r5, r6 blo 1b mov r5, #0 unknown processor2: mov pc, lrldmada r3,(r5-r7) 是把标签3所指的地址的内容(也就是标签3的虚拟地址)赋值给r7,把比标签3所指的地址小4的地址的内容(也就是_proc_info_end)赋值 给r6,把比标签3所指的地址小8的地址的内容(_proc_info_begin)赋值给r5。这里的虚拟地址是线性逻辑地址,它和物理地址之间有着 一一映射关系。因为_proc_info_begin 和_proc_info_end都是虚拟地址,此时我们MMU还没有打开,就必须要使用物理地址。这就需要我们先把它们转换为物理地址。接下来的三句代 码就是完成这样的工作。_proc_info_begin和_proc_info_end是在vlinux.lds.S中定义的。_proc_info_begin = .; *(..init) _proc_info_end = .;这说明在_proc_info_begin和_proc_info_end之间的是所有的..init段。我们可以在 arch/arm/mm/proc_*.S中找到相应的..init段。Smdk6410属于armv6,我们可以在 proc_v6.S找到armv6处理器的id和id掩码。之后的代码就是把处理器的id和id掩码赋值到r3,r4中;把r9与处理器掩码做与操作,然后与处理器id(r3)比较,看是否相等;如不相等,就取下一个处理器id进行比较;如果到最后都没有处理器id相符,就将r5赋值为0:1: ldmia r5, r3, r4 value, mask and r4, r4, r9 mask wanted bits teq r3, r4 beq 2f add r5, r5, #PROC_INFO_SZ sizeof(proc_info_list) cmp r5, r6 blo 1b mov r5, #0 unknown processor2: mov pc, lr最后一句是跳出_lookup_processor_type函数。跳出之后会对处理器id是否有效做一个判断;如果不是有效的处理器,就进行相应的错误处理;如果是有效的处理器,就进行机器类型查找: movs r10, r5 invalid processor (r5=0)? beq _error_p yes, error pbl _lookup_machine_type r5=machinfo1.2 _lookup_machine_type()机器类型的查找代码如下:_lookup_machine_type: adr r3, 3b ldmia r3, r4, r5, r6 sub r3, r3, r4 get offset between virt&phys add r5, r5, r3 convert virt addresses to add r6, r6, r3 physical address space1: ldr r3, r5, #MACHINFO_TYPE get machine type teq r3, r1 matches loader number? beq 2f found add r5, r5, #SIZEOF_MACHINE_DESC next machine_desc cmp r5, r6 blo 1b mov r5, #0 unknown machine2: mov pc, lrENDPROC(_lookup_machine_type)我们可以看到,这和处理器类型查找函数很类似,在这里只进行简单的解说。 .long _proc_info_begin .long _proc_info_end3: .long . .long _arch_info_begin .long _arch_info_end_arch_info_begin和_arch_info_end在arch/arm/kernel/vlinux.lds.S中定义: _arch_info_begin = .; *(..init) _arch_info_end = .;..init段我们可以找到在arch/arm/include/asm/mach/arch.h中有引用:#define MACHINE_START(_type,_name) static const struct machine_desc _mach_desc_#_type _used _attribute_(_section_(..init) = .nr = MACH_TYPE_#_type, .name = _name,#define MACHINE_END ;我们可以在arch/arm/mach-*.c文件中找到一系列关于MACHINE_START所定义的结构。1.3 _vet_atags()函数代码如下:_vet_atags: tst r2, #0x3 aligned? bne 1f ldr r5, r2, #0 is first tag ATAG_CORE? subs r5, r5, #ATAG_CORE_SIZE bne 1f ldr r5, r2, #4 ldr r6, =ATAG_CORE cmp r5, r6 bne 1f mov pc, lr atag pointer is ok1: mov r2, #0 mov pc, lrENDPROC(_vet_atags)atag是bootloader传递给linux内核的参数列表。这个参数列表是以tag的列表形式来表示的。这个列表起始位置的tag是ATAG_CORE,用来表示这是一个有效的tag列表。如果起始tag不是ATAG_CORE,就认为bootloader没有传递tag参数给内核。 以下是tag值的定义和描述,以及tag结构的定义。Tag nameValueSizeDescriptionATAG_NONE0x000000002Empty tag used to end listATAG_CORE0x544100015 (2 if empty)First tag used to start listATAG_MEM0x544100024Describes a physical area of memoryATAG_VIDEOTEXT0x544100035Describes a VGA text displayATAG_RAMDISK0x544100045Describes how the ramdisk will be used in kernelATAG_INITRD20x544200054Describes where the compressed ramdisk image is placed in memoryATAG_SERIAL0x54410006464 bit board serial numberATAG_REVISION0x54410007332 bit board revision numberATAG_VIDEOLFB0x544100088Initial values for vesafb-type framebuffersATAG_CMDLINE0x544100092 + (length_of_cmdline + 3) / 4)Command line to pass to kernelstruct tag_header _u32 size; _u32 tag;struct tag struct tag_header hdr; union struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrdinitrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; struct tag_acorn acorn; struct tag_memclk memclk; u; _vet_atags()函数实现的就是判断r2是否是有效的tag列表指针,如果不是,就将零指针赋值给r2。1.4 _create_page_tables() _create_page_tables()函数同样也是位于arch/arm/kernel/head.S中,代码如下:_create_page_tables: pgtbl r4 page table address /* * Clear the 16K level 1 swapper page table */ mov r0, r4 mov r3, #0 add r6, r0, #0x40001: str r3, r0, #4 str r3, r0, #4 str r3, r0, #4 str r3, r0, #4 teq r0, r6 bne 1b ldr r7, r10, #PROCINFO_MM_MMUFLAGS mm_mmuflags /* * Create identity mapping for first MB of kernel to * cater for the MMU enable. This identity mapping * will be removed by paging_init(). We use our current program * counter to determine corresponding section base address. */ mov r6, pc, lsr #20 start of kernel section orr r3, r7, r6, lsl #20 flags + kernel base str r3, r4, r6, lsl #2 identity mapping /* * Now setup the pagetables for our kernel direct * mapped region. */ add r0, r4, #(KERNEL_START & 0xff000000) 18 str r3, r0, #(KERNEL_START & 0x00f00000) 18! ldr r6, =(KERNEL_END - 1) add r0, r0, #4 add r6, r4, r6, lsr #181: cmp r0, r6 add r3, r3, #1 18 str r3, r0, #(KERNEL_RAM_VADDR & 0x00f00000) 18! ldr r6, =(_end - 1) add r0, r0, #4 add r6, r4, r6, lsr #181: cmp r0, r6 add r3, r3, #1 18 orr r6, r7, #(PHYS_OFFSET & 0xff000000) .if (PHYS_OFFSET & 0x00f00000) orr r6, r6, #(PHYS_OFFSET & 0x00f00000) .endif str r6, r0#ifdef CONFIG_DEBUG_LL ldr r7, r10, #PROCINFO_IO_MMUFLAGS io_mmuflags /* * Map in IO space for serial debugging. * This allows debug messages to be output * via a serial console before paging_init. */ ldr r3, r8, #MACHINFO_PGOFFIO add r0, r4, r3 rsb r3, r3, #0x4000 PTRS_PER_PGD*sizeof(long) cmp r3, #0x0800 limit to 512MB movhi r3, #0x0800 add r6, r0, r3 ldr r3, r8, #MACHINFO_PHYSIO orr r3, r3, r71: str r3, r0, #4 add r3, r3, #1 18 orr r3, r7, #0x7c000000 str r3, r0#endif#ifdef CONFIG_ARCH_RPC add r0, r4, #0x02000000 18 orr r3, r7, #0x02000000 str r3, r0 add r0, r4, #0xd8000000 18 str r3, r0#endif#endif mov pc, lrENDPROC(_create_page_tables)这段代码是用来建立一级页表的。这个初始页表是给接下来要运行的kernel代码用的。因为内核代码用的都是虚拟地址,在使用之前我们必须要建立MMU。 这里的MMU只需要建立的页表能识别内核代码这部分的虚拟地址就够了,也就是从KERNEL_START到KERNEL_END部分。#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000#error KERNEL_RAM_VADDR must start at 0xXXXX8000#endif .globl swapper_pg_dir .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000 .macro pgtbl, rd ldr rd, =(KERNEL_RAM_PADDR - 0x4000) .endm#ifdef CONFIG_XIP_KERNEL#define KERNEL_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)#define KERNEL_END _edata_loc#else#define KERNEL_START KERNEL_RAM_VADDR#define KERNEL_END _end#endif从上述代码我们可以看出,KERNEL_START就是c0008000,KERNEL_END等于_end。_end我们可以从vmlinux.lds.S中找到踪迹。另外需要强调的是,这里建立的MMU 页表是一级页表的,是以1M为单位的;二级页表(4K)不是在这里建立的。Arm一级页表的转换关系如下: 从上图可以看出,一级页表描述符的内容是物理地址段(物理地址前12位)和一些MMU管理位合成的;一级页表描述符的地址是由页表地址基地址(31-14位)和虚拟地址前12位(31-20)合成的,它的最后两位都是零,满足32位地址对齐的方式。建立一级页表的过程就是将每一个一级页表描述符(1M为单位)填入到每一个一级页表描述符的地址。1.5 _enable_mmu() 在建好一页表之后,后面有几句这样的代码: ldr r13, _switch_data address to jump to after mmu has been enabled adr lr, _enable_mmu return (PIC) address add pc, r10, #PROCINFO_INITFUNC 最后一句是跳转到处理器初始化函数执行。我们的处理器是armv6,所以处理器初始化函数可在arch/arm/mm/pro_v6.S中找到:ENTRY(cpu_v6_proc_init) mov pc, lr OK,到这里就知道,目的就是跳转到_enable_mmu()函数执行。至于r13,另有他用,在_enable_mmu()函数的最后可以看到。 建立好一级页表后,这时我们就可以打开MMU,就可以放心大胆地使用虚拟地址了。使能MMU的代码如下:_enable_mmu:#ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #CR_A#else bic r0, r0, #CR_A#endif#ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C#endif#ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z#endif#ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I#endif mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | domain_val(DOMAIN_IO, DOMA

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论