步骤 1

关闭中断、进入 SVC 模式

  1. ENTRY(stext)
  2. THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.
  3. THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
  4. THUMB( .thumb ) @ switch to Thumb now.
  5. THUMB(1: )
  6. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 关中断、进入 SVC 模式

步骤 2

查找指定处理器类型的 proc_info

  1. mrc p15, 0, r9, c0, c0 @ 取出处理器 ID 放入寄存器 r9
  2. bl __lookup_processor_type @ 查找处理器类型 r5=procinfo r9=cpuid
  3. |
  4. |-->/* 找到匹配 proc_info 则返回,否则将 r5 清零 */
  5. | __CPUINIT
  6. | __lookup_processor_type:
  7. | adr r3, __lookup_processor_type_data
  8. | |
  9. | |-->.align 2
  10. | | .type __lookup_processor_type_data, %object
  11. | | __lookup_processor_type_data:
  12. | | .long .
  13. | | .long __proc_info_begin
  14. | | .long __proc_info_end
  15. | | .size __lookup_processor_type_data, . - __lookup_processor_type_data
  16. | ldmia r3, {r4 - r6} @ r4=当前数据地址、r5=处理器数据起始地址、r6=结束地址
  17. | sub r3, r3, r4 @ 计算出运行地址和链接地址间的偏移
  18. | add r5, r5, r3 @ 修正 r5
  19. | add r6, r6, r3 @ 修正 r6
  20. | 1: ldmia r5, {r3, r4}
  21. | and r4, r4, r9
  22. | teq r3, r4
  23. | beq 2f @ 如果相等则匹配成功
  24. | add r5, r5, #PROC_INFO_SZ @ 开始指向下一个处理器数据
  25. | cmp r5, r6
  26. | blo 1b @ 如果还有数据则循环查找
  27. | mov r5, #0 @ 未找到时将 r5 清零
  28. | 2: mov pc, lr @ 返回
  29. | ENDPROC(__lookup_processor_type)
  30. movs r10, r5 @ 使用 r5 改变标志位
  31. THUMB( it eq )
  32. beq __error_p @ 如果相等则没找到
  33. #ifndef CONFIG_XIP_KERNEL
  34. adr r3, 2f @ r3=运行地址
  35. ldmia r3, {r4, r8} @ r4=链接地址(虚拟地址)、r8=页偏移
  36. sub r4, r3, r4 @ 运行地址与链接地址间的差值
  37. /*
  38. * 内核被解压到 物理地址+text_offset 处,即 0x40008000,也是当前的运行地址
  39. * 而内核在编译时被链接到 page_offset+text_offset 处,即 0xc0008000
  40. * 因此 r4=r3-r4 记录的是内核实际存放的物理地址和运行时的虚拟地址间的偏移
  41. * 即 r4=phys-page_offset
  42. * 所以 r8 = r4+r8 = phys-page_offset+page_offset = phys,即物理地址的起始地址
  43. */
  44. add r8, r8, r4 @ 物理地址的起始地址
  45. #else
  46. ldr r8, =PHYS_OFFSET @ always constant in this case
  47. #endif
  48. #ifndef CONFIG_XIP_KERNEL
  49. 2: .long .
  50. .long PAGE_OFFSET
  51. #endif

步骤 3

检查 bootloader 传递的启动参数是否有效

  1. /*
  2. * r1 = machine no, r2 = atags or dtb,
  3. * r8 = phys_offset, r9 = cpuid, r10 = procinfo
  4. */
  5. bl __vet_atags
  6. |
  7. +-->/* Returns:
  8. | * r2 either valid atags pointer, valid dtb pointer, or zero
  9. | * r5, r6 corrupted
  10. | */
  11. | __vet_atags:
  12. | tst r2, #0x3 @ 判断 atags 是否 4 字节对齐
  13. | bne 1f
  14. |
  15. | ldr r5, [r2, #0]
  16. | #ifdef CONFIG_OF_FLATTREE @ 配置此项时支持设备树
  17. | ldr r6, =OF_DT_MAGIC @ 判断是否是 DTB 数据
  18. | cmp r5, r6
  19. | beq 2f
  20. | #endif
  21. | cmp r5, #ATAG_CORE_SIZE @ 判断第一个 atags 参数的大小是否是与 ATAG_CORE 相同
  22. | cmpne r5, #ATAG_CORE_SIZE_EMPTY
  23. | bne 1f
  24. | ldr r5, [r2, #4]
  25. | ldr r6, =ATAG_CORE @ 再判断该参数是不是 ATAG_CORE 节点
  26. | cmp r5, r6
  27. | bne 1f
  28. |
  29. | 2: mov pc, lr @ 所传递参数合法,正常返回
  30. |
  31. | 1: mov r2, #0
  32. | mov pc, lr
  33. | ENDPROC(__vet_atags)

步骤 4

当前内核镜像在内存中的布局

  1. // 物理内存中的布局
  2. _____________________________________________
  3. | | | |
  4. | | | |
  5. | | 段描述符 | kernel image |
  6. | | | |
  7. |______|__________|__________________________|
  8. 0x4000_0000 0x4000_8000
  9. // 虚拟内存中的布局
  10. _____________________________________________
  11. | | | |
  12. | | | |
  13. | | 段描述符 | kernel image |
  14. | | | |
  15. |______|__________|__________________________|
  16. 0xc000_0000 0xc000_8000

内核建立内核空间临时的线性映射,采用一级映射,也就是 section 模式,每个section 为 1MB.

  1. #ifdef CONFIG_SMP_ON_UP
  2. bl __fixup_smp @ 自旋锁在 SMP UP 上的相关修正
  3. @ arch/arm/include/asm::ALT_SMP
  4. #endif
  5. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
  6. bl __fixup_pv_table @ 物理地址和虚拟地址间的偏移修正等
  7. @ arch/arm/include/asm::pv_stub
  8. #endif
  9. bl __create_page_tables
  10. |
  11. +-->/* r8 = phys_offset, r9 = cpuid, r10 = procinfo
  12. | *
  13. | * Returns:
  14. | * r0, r3, r5-r7 corrupted
  15. | * r4 = physical page table address
  16. | */
  17. | __create_page_tables:
  18. | pgtbl r4, r8 @ 将页表起始物理地址放入 r4
  19. | |
  20. | +-->.macro pgtbl, rd, phys
  21. | | add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
  22. | | .endm
  23. |
  24. | @ 对页表区域进行清零
  25. | mov r0, r4
  26. | mov r3, #0
  27. | add r6, r0, #PG_DIR_SIZE
  28. | 1: str r3, [r0], #4
  29. | str r3, [r0], #4
  30. | str r3, [r0], #4
  31. | str r3, [r0], #4
  32. | teq r0, r6
  33. | bne 1b
  34. |
  35. | ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
  36. |
  37. | @ 创建临时的线性映射
  38. | @ 页表项格式:一级页表入口值[31:20] MMUFLAGS[19:0]
  39. | adr r0, __turn_mmu_on_loc
  40. | ldmia r0, {r3, r5, r6}@ 得到函数的物理地址
  41. | sub r0, r0, r3 @ virt->phys offset
  42. | add r5, r5, r0 @ phys __turn_mmu_on
  43. | add r6, r6, r0 @ phys __turn_mmu_on_end
  44. | mov r5, r5, lsr #SECTION_SHIFT @ 得到一级页表入口值
  45. | mov r6, r6, lsr #SECTION_SHIFT
  46. |
  47. | 1: orr r3, r7, r5, lsl #SECTION_SHIFT @ 一级段描述符
  48. | str r3, [r4, r5, lsl #PMD_ORDER] @ 将 r3 中存放的段描述符放入对应的物理地址中
  49. | cmp r5, r6
  50. | addlo r5, r5, #1 @ 下一个段描述符
  51. | blo 1b
  52. |
  53. | @ 设置映射页表
  54. | mov r3, pc
  55. | mov r3, r3, lsr #SECTION_SHIFT @ 得到当前执行程序的段描述符编号
  56. | orr r3, r7, r3, lsl #SECTION_SHIFT @ 合成段描述符
  57. | @ kernel_start=0xc000_8000, section_shift=20, pmd_order=2
  58. | @ 以下两行其实是在计算段描述符的入口地址
  59. | @ 因为要回写到 r0 中,因此拆分来写的
  60. | add r0, r4, #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
  61. | str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
  62. | ldr r6, =(KERNEL_END - 1) @ 内核(包括数据段)的最后一个字节位置
  63. | add r0, r0, #1 << PMD_ORDER @ 下一个段描述符存放的物理地址
  64. | add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER) @ 内核需要的最后一个段描述符存放的物理地址
  65. | 1: cmp r0, r6
  66. | @ 内核对自身进行了线性映射,将自身物理内存所在段直接放入页表中
  67. | add r3, r3, #1 << SECTION_SHIFT @ 下一个段描述符,只需要增加段基址即可
  68. | strls r3, [r0], #1 << PMD_ORDER @ 写入到物理内存对应的页表中
  69. | bls 1b
  70. |
  71. | @ atags 所在段写到页表中
  72. | mov r0, r2, lsr #SECTION_SHIFT @ atags 段编号
  73. | movs r0, r0, lsl #SECTION_SHIFT @ 如果 r0 为零则赋值为 r8,即没有指定 atags 的情况
  74. | moveq r0, r8
  75. | sub r3, r0, r8 @ 段内偏移量
  76. | add r3, r3, #PAGE_OFFSET @ 转化成虚拟地址
  77. | add r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER) @ 得到该段描述符存放的物理地址
  78. | orr r6, r7, r0 @ 合成段描述
  79. | str r6, [r3] @ 写入物理内存中
  80. |
  81. | mov pc, lr
  82. | ENDPROC(__create_page_tables)
  83. /*
  84. * r10 = base of xxx_proc_info structure selected by __lookup_processor_type
  85. * On return, the CPU will be ready for the MMU to be turned on,
  86. * r0 = CPU control register value.
  87. */
  88. /*
  89. * 以下代码流程
  90. * 1. 设置v7核心,主要涉及SMP,准备MMU硬件配置,I/D cache,TLB,涉及协处理的配置
  91. * --> arch/arm/mm/proc-v7.S::__v7_setup
  92. * 2. 配置MMU,设置内存访问权限,并激活MMU
  93. * --> arch/arm/kernel/head.S::__enable_mmu
  94. * 3. 将数据段复制到内存中,清理bss段,将processor ID,machine ID,atags 指针保存到指定变量中
  95. * --> arch/arm/kernel/head-common.S::__mmap_switched
  96. * 4. __mmap_switched 最后进入C语言函数start_kernel,至此终于走出了汇编代码,进入C语言的天堂
  97. * --> init/main.c::start_kernel
  98. */
  99. @ 因为跳转到该函数时,MMU已激活,故这里使用的是虚拟地址,而不是物理地址
  100. ldr r13, =__mmap_switched @ address to jump to after
  101. @ mmu has been enabled
  102. adr lr, BSYM(1f) @ return (PIC) address
  103. mov r8, r4 @ set TTBR1 to swapper_pg_dir
  104. ARM( add pc, r10, #PROCINFO_INITFUNC )
  105. THUMB( add r12, r10, #PROCINFO_INITFUNC )
  106. THUMB( mov pc, r12 )
  107. 1: b __enable_mmu

关键宏定义

  1. ::arch/arm/kernel/vmlinux.ld.S
  2. . = PAGE_OFFSET + TEXT_OFFSET
  3. ::arcm/arm/kernel/head.S
  4. /*
  5. * swapper_pg_dir is the virtual address of the initial page table.
  6. * We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we must
  7. * make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
  8. * the least significant 16 bits to be 0x8000, but we could probably
  9. * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
  10. */
  11. #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
  12. #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
  13. #error KERNEL_RAM_VADDR must start at 0xXXXX8000
  14. #endif
  15. #ifdef CONFIG_ARM_LPAE
  16. /* LPAE requires an additional page for the PGD */
  17. #define PG_DIR_SIZE 0x5000
  18. #define PMD_ORDER 3
  19. #else
  20. #define PG_DIR_SIZE 0x4000
  21. #define PMD_ORDER 2
  22. #endif
  23. .globl swapper_pg_dir
  24. .equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
  25. .macro pgtbl, rd, phys
  26. add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
  27. .endm
  28. #ifdef CONFIG_XIP_KERNEL
  29. #define KERNEL_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
  30. #define KERNEL_END _edata_loc
  31. #else
  32. #define KERNEL_START KERNEL_RAM_VADDR
  33. #define KERNEL_END _end
  34. #endif

调用 start_kernel的更多相关文章

  1. linux启动过程分析

    参考:http://blog.chinaunix.net/uid-26495963-id-3066282.html http://www.comptechdoc.org/os/linux/startu ...

  2. Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)

    启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...

  3. Linux启动过程详解

    Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...

  4. Linux与Windows xp操作系统启动过程

    Linux启动过程: 第一步,加载BIOS,当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备 ...

  5. [转载] Linux启动过程详解-《别怕Linux编程》之八

    本原创文章属于<Linux大棚>博客,博客地址为http://roclinux.cn.文章作者为rocrocket.为了防止某些网站的恶性转载,特在每篇文章前加入此信息,还望读者体谅. = ...

  6. linux开机启动

    开机过程指的是从打开计算机电源直到LINUX显示用户登录画面的全过程.分析LINUX开机过程也是深入了解LINUX核心工作原理的一个很好的途径. 启动第一步--加载BIOS 当你打开计算机电源,计算机 ...

  7. system(linux) power on note

    读詹荣开文档摘 BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader Boot Loader执行全 ...

  8. Linux的启动过程

    Linux的启动过程,也就是Linux的引导流程,这部分主要是理论知识. Linux的开机启动过程 1.1第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...

  9. Linux启动过程详解 (转)

    启动第一步--加载BIOS当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬盘 ...

随机推荐

  1. 外媒解读Web安全核心PKI的四大致命问题

    Web安全的立足根基在于复杂的PKI部署体系,但实际生活中得到正确部署的比例却非常有限,而且这一切都将随着摩尔定律的滚滚洪流灰飞烟灭. 我个人算是PKI(即公共密钥基础设施)的忠实拥护者.我热爱数学与 ...

  2. Muduo网络库实战(一):安装和配置

    1. 参考资料 <Muduo_网络库使用手册> 2. 实战记录 1) muduo依赖项安装 centos安装cmake命令:# yum install cmake centos安装libb ...

  3. Codeforces Round #622 (Div. 2) 1313 B Different Rules

    B. Different Rules Nikolay has only recently started in competitive programming, but already qualifi ...

  4. 图论--网络流--最大流 洛谷P4722(hlpp)

    题目描述 给定 nn 个点,mm 条有向边,给定每条边的容量,求从点 ss 到点 tt 的最大流. 输入格式 第一行包含四个正整数nn.mm.ss.tt,用空格分隔,分别表示点的个数.有向边的个数.源 ...

  5. 2019 ICPC 南京网络赛 H-Holy Grail

    As the current heir of a wizarding family with a long history,unfortunately, you find yourself force ...

  6. mock-server 之 mock 接口测试

    1.mock 介绍 mock 除了用在单元测试过程中,还有一个用途,当前端开发在开发页面的时候,需要服务端提供 API 接口,此时服务端没开发完成,或者说没搭建测试环境,这个时候前端开发会自己 moc ...

  7. 用纯css、JavaScript、jQuery简单的轮播图

    完成一个可以自动切换或点击数字的轮播图 HTML代码只需要一个div 包含着一个图片和一个列表,我们主要的思路就是通过点击相应的数字,改变图片的 路径. 有4张图片都在img文件夹里,名称为  img ...

  8. java基础篇 之 final关键字

    ​ final,字面上是最终的意思,通常来说,我们用它来作为修饰符的时候,都是代表"这是无法改变的"的意思.不想改变可能出与两种理由:设计或效率.由于这两个原因相差甚远,所以我们在 ...

  9. etcd环境安装与使用

    etcd简介 etcd是开源的.高可用的分布式key-value存储系统,可用于配置共享和服务的注册和发现,它专注于: 简单:定义清晰.面向用户的API(gRPC) 安全:可选的客户端TLS证书自动认 ...

  10. js中刷新页面的方式总结

    1.window.onload / document.onload 2.history.go(num): (1)num为参数,num为正表示前进几个页面,类似于history.forward(): ( ...