tag 标签: linux内核启动

相关博文
  • 热度 21
    2012-3-31 16:15
    2739 次阅读|
    8 个评论
    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, DOMAIN_CLIENT))        mcr p15, 0, r5, c3, c0, 0             @ load domain access register        mcr p15, 0, r4, c2, c0, 0             @ load page table pointer        b     __turn_mmu_on ENDPROC(__enable_mmu)   __turn_mmu_on:        mov r0, r0        mcr p15, 0, r0, c1, c0, 0             @ write control reg        mrc p15, 0, r3, c0, c0, 0             @ read id reg        mov r3, r3        mov r3, r3        mov pc, r13 ENDPROC(__turn_mmu_on)               这段代码很简单,就是把一级页表的基地址放到CP15的c2中,然后打开MMU。执行到最后,把r13赋值给pc,就是跳转到__swtich_data处执行。 1.6 __mmap_switched()        我们可以在arch/arm/kernel/head-common.S找到__switch_data的定义: __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          可见标号__switch_data的值就等同于__mmap_switched()函数的指针地址。__mmap_switch()函数定义如下: __mmap_switched:        adr   r3, __switch_data + 4          ldmia       r3!, {r4, r5, r6, r7}        cmp r4, r5                           @ Copy data segment if needed 1:     cmpne     r5, r6        ldrne       fp, , #4        strne       fp, , #4        bne  1b          mov fp, #0                           @ Clear BSS (and zero fp) 1:     cmp r6, r7        strcc       fp, ,#4        bcc  1b          ldmia       r3, {r4, r5, r6, r7, sp}        str   r9,                   @ Save processor ID        str   r1,                   @ Save machine type        str   r2,                   @ Save atags pointer        bic   r4, r0, #CR_A               @ Clear 'A' bit        stmia       r7, {r0, r4}                   @ Save control register values        b     start_kernel ENDPROC(__mmap_switched)        这段代码很简单,就是拷贝数据到数据段;清BSS;然后保存处理器ID,机器类型和atag指针到内存的相应位置(因为接下来既要跳到c语言环境执行了,必须要把之前有意义的寄存器加以保存);跳转到start_kernel()函数,进入操作系统环境。   该系列文章列表: linux内核启动解析(一) linux内核启动解析(二) linux内核启动解析(三) linux内核启动解析(四) linux内核启动解析(五)
  • 热度 13
    2012-3-31 16:14
    2808 次阅读|
    2 个评论
    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, #0x4000 1:     str   r3, , #4        str   r3, , #4        str   r3, , #4        str   r3, , #4        teq   r0, r6        bne  1b          ldr   r7, @ 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,           @ identity mapping          /*         * Now setup the pagetables for our kernel direct         * mapped region.         */        add  r0, r4,  #(KERNEL_START 0xff000000) 18        str   r3, !        ldr   r6, =(KERNEL_END - 1)        add  r0, r0, #4        add  r6, r4, r6, lsr #18 1:     cmp r0, r6        add  r3, r3, #1 20        strls r3, , #4        bls   1b   #ifdef CONFIG_XIP_KERNEL        /*         * Map some ram to cover our .data and .bss areas.         */        orr   r3, r7, #(KERNEL_RAM_PADDR 0xff000000)        .if    (KERNEL_RAM_PADDR 0x00f00000)        orr   r3, r3, #(KERNEL_RAM_PADDR 0x00f00000)        .endif        add  r0, r4,  #(KERNEL_RAM_VADDR 0xff000000) 18        str   r3, !        ldr   r6, =(_end - 1)        add  r0, r0, #4        add  r6, r4, r6, lsr #18 1:     cmp r0, r6        add  r3, r3, #1 20        strls r3, , #4        bls   1b #endif          /*         * Then map first 1MB of ram in case it contains our boot params.         */        add  r0, r4, #PAGE_OFFSET 18        orr   r6, r7, #(PHYS_OFFSET 0xff000000)        .if    (PHYS_OFFSET 0x00f00000)        orr   r6, r6, #(PHYS_OFFSET 0x00f00000)        .endif        str   r6,   #ifdef CONFIG_DEBUG_LL        ldr   r7, @ 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,        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,        orr   r3, r3, r7 1:     str   r3, , #4        add  r3, r3, #1 20        teq   r0, r6        bne  1b #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)        /*         * If we're using the NetWinder or CATS, we also need to map         * in the 16550-type serial port for the debug messages         */        add  r0, r4, #0xff000000 18        orr   r3, r7, #0x7c000000        str   r3, #endif #ifdef CONFIG_ARCH_RPC          add  r0, r4, #0x02000000 18        orr   r3, r7, #0x02000000        str   r3,        add  r0, r4, #0xd8000000 18        str   r3, #endif #endif        mov pc, lr ENDPROC(__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为单位)填入到每一个一级页表描述符的地址。   linux内核启动解析(五)
  • 热度 8
    2012-3-31 16:13
    2947 次阅读|
    0 个评论
      1.2 __lookup_machine_type() 机器类型的查找代码如下: __lookup_machine_type:        adr   r3, 3b        ldmia       r3, {r4, r5, r6}        sub  r3, r3, r4               @ get offset between virtphys        add  r5, r5, r3               @ convert virt addresses to        add  r6, r6, r3               @ physical address space 1:     ldr   r3,        @ 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 machine 2:     mov pc, lr ENDPROC(__lookup_machine_type) 我们可以看到,这和处理器类型查找函数很类似,在这里只进行简单的解说。        .long       __proc_info_begin        .long       __proc_info_end 3:     .long       .        .long       __arch_info_begin        .long       __arch_info_end       __arch_info_begin和__arch_info_end在arch/arm/kernel/vlinux.lds.S中定义:               __arch_info_begin = .;                      *(.arch.info.init)               __arch_info_end = .; .arch.info.init段我们可以找到在arch/arm/include/asm/mach/arch.h中有引用: #define MACHINE_START(_type,_name)                  \ static const struct machine_desc __mach_desc_##_type    \  __used                                            \  __attribute__((__section__(".arch.info.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,                    @ is first tag ATAG_CORE?        subs r5, r5, #ATAG_CORE_SIZE        bne  1f        ldr   r5,        ldr   r6, =ATAG_CORE        cmp r5, r6        bne  1f          mov pc, lr                            @ atag pointer is ok   1:     mov r2, #0        mov pc, lr ENDPROC(__vet_atags) atag是bootloader传递给linux内核的参数列表。这个参数列表是以tag的列表形式来表示的。这个列表起始位置的tag是ATAG_CORE,用来表示这是一个有效的tag列表。如果起始tag不是ATAG_CORE,就认为bootloader没有传递tag参数给内核。以下是tag值的定义和描述,以及tag结构的定义。   Tag name Value Size Description ATAG_NONE 0x00000000 2 Empty tag used to end list ATAG_CORE 0x54410001 5 (2 if empty) First tag used to start list ATAG_MEM 0x54410002 4 Describes a physical area of memory ATAG_VIDEOTEXT 0x54410003 5 Describes a VGA text display ATAG_RAMDISK 0x54410004 5 Describes how the ramdisk will be used in kernel ATAG_INITRD2 0x54420005 4 Describes where the compressed ramdisk image is placed in memory ATAG_SERIAL 0x54410006 4 64 bit board serial number ATAG_REVISION 0x54410007 3 32 bit board revision number ATAG_VIDEOLFB 0x54410008 8 Initial values for vesafb-type framebuffers ATAG_CMDLINE 0x54410009 2 + ((length_of_cmdline + 3) / 4) Command line to pass to kernel   struct 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_initrd      initrd;               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。   linux内核启动解析(四) linux内核启动解析(五)  
  • 热度 15
    2012-3-31 16:12
    3842 次阅读|
    2 个评论
    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   := 0x00008000 TEXT_OFFSET := $(textofs-y)   结合arch/arm/kernel/head.S,会发现如下代码: .section ".text.head", "ax" 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=cupid … ENDPROC(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 virtphys        add  r5, r5, r3               @ convert virt addresses to        add  r6, r6, r3               @ physical address space 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 processor 2:     mov pc, lr ENDPROC(__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_end 3:     .long       .        .long       __arch_info_begin        .long       __arch_info_end 我们接着往下看。 ldmda      r3, {r5 - r7}        sub  r3, r3, r7               @ get offset between virtphys        add  r5, r5, r3               @ convert virt addresses to        add  r6, r6, r3               @ physical address space 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 processor 2:     mov pc, lr ldmada 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 = .;                      *(.proc.info.init)               __proc_info_end = .; 这说明在__proc_info_begin和__proc_info_end之间的是所有的.proc.info.init段。我们可以在arch/arm/mm/proc_*.S中找到相应的.proc.info.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 processor 2:     mov pc, lr 最后一句是跳出__lookup_processor_type函数。跳出之后会对处理器id是否有效做一个判断;如果不是有效的处理器,就进行相应的错误处理;如果是有效的处理器,就进行机器类型查找:        movs      r10, r5                         @ invalid processor (r5=0)?        beq  __error_p                     @ yes, error 'p' bl     __lookup_machine_type        @ r5=machinfo linux内核启动解析(三)
  • 热度 27
    2012-3-31 16:09
    7219 次阅读|
    14 个评论
    1 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()。 4) __create_page_tables(),创建页表。 5) __enable_mmu(),使能MMU。 6) __mmap_switched(),拷贝数据,清BBS。 7) start_kernel(),进入真正的内核初始化函数。 8) smp_setup_processor_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(); 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();                                                                         该系列文章列表: linux内核启动解析(一) linux内核启动解析(二) linux内核启动解析(三) linux内核启动解析(四) linux内核启动解析(五)