linux内核head.S分析

1.1内核链接脚本分析出内核第一条指令

链接文件怎么读可以参考下面的文章

https://blog.csdn.net/zhjica/article/details/52995536

路径:arch/arm/kernel/vmlinux.lds.S 部分内容如下

OUTPUT_ARCH(arm) 输出文件的架构
ENTRY(stext) 指的是执行的第一条指令的地址,这个是定义在head.S里面
 
SECTIONS
{
///DISCARD/ 表示这个段内的内容不会出现在输出文件中
    /DISCARD/ : {
    ...
    }
 
#ifdef CONFIG_XIP_KERNEL //这里没有定义
    . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
    . = PAGE_OFFSET + TEXT_OFFSET;//所以起始地址就是这个
    //PAGE_OFFSET 实际上指的内核空间的起始地址,对于32位的ARM来说,如果我们定义内核空间为1个G的话,这个PAGE_OFFSET = 0xc0000000,
    TEXT_OFFSET 实际上是在arc/arm/Makefile中定义的 = 0x00008000
#endif
    .head.text : {
        _text = .;
        HEAD_TEXT
    }

arc/arm/Makefile

textofs-y    := 0x00008000
TEXT_OFFSET := $(textofs-y)
 

所以我们查看编译好的system.map的stext就是卫浴0xc0008000地址处,也就是物理地址向上32KB的地方就是内核代码段开始的地方

20200613182541

1.2head.s的分析

根据上面的分析内核执行的第一条代码应该是在head.S中的stext处

  1. 启动内核必须要满足下面的一些条件

    bootloader必须保证MMU是关闭的,必须保证DATA_CASH 是关闭的

arc/arm/kernel/head.S 部分代码

/*
* Kernel startup entry point.
* ---------------------------
*
 * This is normally called from the decompressor code.  The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
    .arm
 
    __HEAD
ENTRY(stext)
 ARM_BE8(setend    be )            @ ensure we are in BE8 mode
 
 THUMB(    adr    r9, BSYM(1f)    )    @ Kernel is always entered in ARM.
 THUMB(    bx    r9        )    @ If this is a Thumb-2 kernel,
 THUMB(    .thumb            )    @ switch to Thumb now.
 THUMB(1:            )
 
#ifdef CONFIG_ARM_VIRT_EXT
    bl    __hyp_stub_install
#endif
#跳转到SVC模式,关闭所有中断,内核刚启动的时候有关于中断的状态还没有准备好,这个时候来中断可能会让程序崩溃
#safe_svcmode_maskall位于/arch/arm/include/asm/assembler.h中
    @ ensure svc mode and all interrupts masked
    safe_svcmode_maskall r9
 
  1. 接下来看一个比较重要的函数 bl __create_page_tables

    内核在打开MMU的过程中会创建一个虚拟地址等于物理地址的映射用于过度过程。

__create_page_tables:
    pgtbl    r4, r8                @ page table address 在下面会有分析

先分析一下r4和r8分别代表什么

#ifndef CONFIG_XIP_KERNEL
    adr    r3, 2f                                                      r3==>运行时的pc+标号2的偏移地址 = 比如当前运行的地址是0x6000_0000 那么 r3 = 标号2的相对位置 r3 = 0x6000807c
    ldmia    r3, {r4, r8}                                            r4 == > 标号2绝对位置 0xc000_807c r8 = 0xc000_0000
    sub    r4, r3, r4            @ (PHYS_OFFSET - PAGE_OFFSET)            r4 = 0x6000_807c - 0xc0000_807c  = -0x6000_0000链接地址和运行地址的偏差
    add    r8, r8, r4            @ PHYS_OFFSET                            r8 = 0xc000_0000 + (-0x6000_0000) =>0x6000_0000
#else
    ldr    r8, =PLAT_PHYS_OFFSET        @ always constant in this case
#endif
标号2在下面定义了
 
#ifndef CONFIG_XIP_KERNEL
2:    .long    .
    .long    PAGE_OFFSET
#endif
 
 ​    /*.macro    pgtbl, rd, phys
​    add    \rd, \phys, #TEXT_OFFSET  //rd =0x60000000+0x8000 32KB
​    sub    \rd, \rd, #PG_DIR_SIZE    //rd=0x60008000-0x4000 16KB
​    .endm
​    所以 r4 = 0x60004000
​    //对0x60008000~0x60004000这段空间进行清零

  根据上面计算得到 ,这里其实就是运行时的物理地址
  r8 = 0x6000_0000

接着分析 __create_page_tables

  •    __create_page_tables:
        pgtbl    r4, r8                @ page table address 在下面会有分析
       * Clear the swapper page table
         /
             mov    r0, r4               ==>r0 = 0x60004000
             mov    r3, #0                ==>r3 = 0
             add    r6, r0, #PG_DIR_SIZE ==>r6 = 0x60008000
         1:  str    r3, [r0], #4            ==>0写入0x60004000 并且 r0= r0 + 4
             str    r3, [r0], #4            ==>0写入0x60004004 并且 r0= r0 + 4
             str    r3, [r0], #4            ==>0写入0x60004008 并且 r0= r0 + 4
             str    r3, [r0], #4            ==>0写入0x6000400c 并且 r0= r0 + 4
             teq    r0, r6
             bne    1b
             //把MMU的标志位加载到R7寄存器中
             ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags  r7 = 0xc0e
         接下来就是创建虚拟地址和物理地址相等的映射关系
             /*
            * Create identity mapping to cater for __enable_mmu.
              This identity mapping will be removed by paging_init().
                  */
                  //下面这一块代码就是把打开MMU(__turn_mmu_on_loc),这一个代码段一一映射到内核空间上,物理地址==虚拟地址。内核在刚开始的时候还没有开始内存管理,是使用段的内存管理方式,ARM的段大概是1M的空间大小
                  首先将打开MMU那段代码的section的起始地址放到R5,结束地址放到R6
                 adr    r0, __turn_mmu_on_loc //相对地址  r0 = 0x60008130
                 ldmia    r0, {r3, r5, r6}                                      r3 = __turn_mmu_on_loc链接地址 r5 =  __turn_mmu_on链接地址 r6= __turn_mmu_on_end虚拟地址(连接后的地址)
                 sub    r0, r0, r3            @ virt->phys offset               r0 = 相对位置 -链接位置 = 地址偏差 = -0x6000_0000
                 add    r5, r5, r0            @ phys __turn_mmu_on            r5 = r5 + __turn_mmu_on链接地址 = __turn_mmu_on物理地址
                 add    r6, r6, r0            @ phys __turn_mmu_on_end        r6 = r6 + __turn_mmu_on_end = __turn_mmu_on_end物理地址
                 mov    r5, r5, lsr #SECTION_SHIFT                            r5右移1M = (这里的SECTION_SHIFT为1M大小) r5= 0x60b
                 mov    r6, r6, lsr #SECTION_SHIFT                            r6右移1M (1M对齐)						r6= 0x60b
    
    1:    orr    r3, r7, r5, lsl #SECTION_SHIFT    @ flags + kernel base  获得First-level descriptor 一级页表描述符 r3 = 0x60b00c0e
        str    r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping 将0x60b00c0e写入0x6000_582C
        cmp    r5, r6
        addlo    r5, r5, #1            @ next section
        blo    1b
    

下图就描述了ARMv11中虚拟地址到物理地址的转换过程。这里就是虚拟地址到物理地址一一对应的关系。首先一级页表的基地址放在0x60004000这个地方。

在内核刚开始阶段是段映射的方式,只有一级页表。下图是turn_mmu这一段代码和物理地址一一映射的分析图。

20200619102925

接下来是内核image代码段映射到物理地址上。
    /*

   * Map our RAM from the start to the end of the kernel .bss section.
     /
         add    r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
         ldr    r6, =(_end - 1)
         orr    r3, r8, r7
         add    r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
     1:    str    r3, [r0], #1 << PMD_ORDER
         add    r3, r3, #1 << SECTION_SHIFT
         cmp    r0, r6
         bls    1b

下面图片就是分析内核代码虚拟地址到物理地址转换的过程

20200619105138

接下来是映射启动参数

/*
 * Then map boot params address in r2 if specified.
 * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
 */
mov	r0, r2, lsr #SECTION_SHIFT
movs	r0, r0, lsl #SECTION_SHIFT
subne	r3, r0, r8
addne	r3, r3, #PAGE_OFFSET
addne	r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
orrne	r6, r7, r0
strne	r6, [r3], #1 << PMD_ORDER
addne	r6, r6, #1 << SECTION_SHIFT
strne	r6, [r3]

映射关系如下如所示

20200619111506

创建完临时页表之后 会把__mmap_switched 函数指针存在r13中

    ldr    r13, =__mmap_switched        @ address to jump to after

之后就会 b __enable_mmu

在这个函数中最后会跳转到R13,也就是__mmap_switched函数中

在这个函数中会跳转到start_kernel中。

__mmap_switched在arc/arm/kernel/head-common.S这个文件中定义

arc/arm/kernel/head-common.S

 
    .align    2
    .type    __mmap_switched_data, %object
__mmap_switched_data:
    .long    __data_loc            @ r4
    .long    _sdata                @ r5
    .long    __bss_start            @ r6
    .long    _end                @ r7
    .long    processor_id            @ r4
    .long    __machine_arch_type        @ r5
    .long    __atags_pointer            @ r6
#ifdef CONFIG_CPU_CP15
    .long    cr_alignment            @ r7
#else
    .long    0                @ r7
#endif
    .long    init_thread_union + THREAD_START_SP @ sp
    .size    __mmap_switched_data, . - __mmap_switched_data
 
 
__mmap_switched:
//__mmap_switched_data 把上面很多值装到相应寄存器中
    adr    r3, __mmap_switched_data
 
    ldmia    r3!, {r4, r5, r6, r7}
    cmp    r4, r5                @ Copy data segment if needed
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b
//清零BSS段
    mov    fp, #0                @ Clear BSS (and zero fp)
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b
 
 ARM(    ldmia    r3, {r4, r5, r6, r7, sp})
 THUMB(    ldmia    r3, {r4, r5, r6, r7}    )
 THUMB(    ldr    sp, [r3, #16]        )
    str    r9, [r4]            @ Save processor ID
    str    r1, [r5]            @ Save machine type
    str    r2, [r6]            @ Save atags pointer
    cmp    r7, #0
    strne    r0, [r7]            @ Save control register values
    //接下来就会跳转到start_kernel函数
    b    start_kernel
ENDPROC(__mmap_switched)

至此程序会跳转到start_kernel处。

1.3内核为什么在一开始不用C,而是先用汇编语言再跳转至C程序

C语言的执行需要有堆栈。使用汇编语言设置C程序的堆栈,也就是SP,然后才可以跳转到C语言处执行

如果觉得对你有帮助,可以关注微信公众号 死磕linux 获取更多精彩内容。
20200614190721

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值