内核启动所用函数如下:

与移植U-Boot 的过程相似,在移植Linux 之前,先了解它的启动过程。Linux 的过程可以分为两部分:架构/开发板相关的引导过程、后续的通用启动过程。对于uImage、zImage ,它们首先进行自解压得到vmlinux ,然后执行 vmlinux 开始“正常的”启动流程。

引导阶段通常使用汇编语言编写,它首先检查内核是否支持当前架构的处理器,然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel函数作准备了。这主要分如下两个步骤:

1)-- 连接内核时使用的虚拟地址,所以要设置页表、使能MMU;

2)调用C 函数 start_kernel 之前的常规工作,包括复制数据段、清除BSS段、调用start_kernel 函数。

第二阶段的关键代码主要使用C语言编写。它进行内核初始化的全部工作,最后调用 rest_init 函数启动init 过程,创建系统第一个进程:init 进程。在第二阶段,仍有部分架构/开发板相关的代码,比如重新设置页表、设置系统时钟、初始化串口等。

下面是详细解析:

一、第一阶段

与Uboot 一样,我们在连接文件中查看函数入口点,内核编译完成后会在arch/arm/kernel/下生成 vmlinux.lds 文件,打开:

stext 在 linux/arch/arm/kernel/head.S 中被定义,做为函数入口点,linux/arch/arm/kernel/head.S是linux内核映像解压后执行的第一个文件

代码只是部分,但可以看到这一阶段究竟做了些什么:

a -- 设定为SVC模式,关闭IRQ、FIQ;

b -- 确定CPU的ID号,判定其是否有效;

c -- 确定machine的ID号,检查合法性;

d -- 检查bootloader传入的参数列表atags的合法性

e -- 创建初始页表

下面对上面遇到的程序段展开分析:

a -- 确保处于SVC模式

这没什么好讲的,就是设置CPSR 模式位,并屏蔽中断;

b -- 检查CPU ID 是否匹配

获取ID并放到 r9 寄存器中,调用_lookup_processor_type 函数, 函数主要用来判定内核是否和当前的CPU匹配,如果不匹配,r5寄存器的值应为0,此时会调用 _error_p函数,它用来打印错误信息,即内核和当前的CPU不匹配,此时内核时不能启动的;如果两者匹配,会返回一个描述处理器结构的地址(在r5寄存器中),然后调用下面的函数。

下面看_lookup_processor_type 函数,在arch/arm/kernel/head-common.S 中定义:

上面的代码其实就是一个地址转换过程,因为在判定CPU架构时未开启系统的MMU功能,所以均使用物理地址,而内核代码在连接时是以虚拟地址来实现的,因此要想用proc_info_list 结构体,就要先找到proc_info_list 结构的物理地址,这样必须使用上面的转换代码。

proc_info_list 结构体很重要。在Linux 内核映像中定义了很多个proc_info_list 结构,该结构表示的是内核所支持的CPU架构,这部分下面会讲到,先分析上面的代码:

153 行:r3 存储的是_lookup_processor_type_data 的物理地址 ;

155 行:得到虚拟地址和物理地址之间的offset;

156 - 157 行:利用offset,将 r5 和 r6 中保存的虚拟地址转变为物理地址,主要是获得_proc_info_begin 及_proc_info_end 的物理地址,分别放到r5 和 r6 中;

159 行:r9 中存放的是先前读出的 processor ID,此处屏蔽不需要的位;

160 行:查看代码和CPU硬件是否匹配,如果匹配就返回,此时 r5 存放的是该CPU类型对应的结构体_proc_info_list 的基地址 ;不成功,则查看下一个 proc_info_list 结构体;

163行:如果直到 _proc_info_end ,都没有匹配,则定为未知CPU,向 r5 赋 0,然后返回 ;

下面来看看 proc_info_list 结构体 ,这个结构体在 arch/arm/include/asm/procinfo.h 中定义:

对于 Cortex-A9 来说,其结构体在文件 arch/arm/mm/proc-v7.S 中初始化

.section ".proc.info.init"表明了该结构在编译后存放的位置。在链接文件arch/arm/kernel/vmlinux.lds中:

__proc_info_begin = .;

*(.proc.info.init)

__proc_info_end = .;

上面两个变量 _proc_info_begin  与 _proc_info_end 用于计算 proc_info_list 结构的物理地址。

如果CPU ID匹配,在编译内核文件时,会编译 proc-v7.S 这个文件,可以在arch/arm/mm/Makefile 中看到这个文件

c -- 检测 机器ID是否匹配

主要用到_lookup_machine_type 函数,其与_lookup_processor_type 函数实现代码很相似,这里不予阐述;

d -- 检查bootloader传入的参数列表atags的合法性



        _vet_atags 函数用于检测参数列表atags的合法性

内核参数链表的格式和说明可以从内核源代码目录树中的 中找到,参数链表必须以ATAG_CORE 开始,以ATAG_NONE结束。这里的 ATAG_CORE,ATAG_NONE是各个参数的标记,本身是一个32位值,例如:ATAG_CORE=0x54410001。其它的参数标记还包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各个参数结构体构成了参数链表。参数结构体的定义如下:

[cpp] view plaincopy
  1. struct tag {
  2. struct  tag_header  hdr;
  3. union {
  4. struct tag_core  core;
  5. struct tag_mem32   mem;
  6. struct tag_videotext videotext;
  7. struct tag_ramdisk  ramdisk;
  8. struct tag_initrd     initrd;
  9. struct tag_serialnr     serialnr;
  10. struct tag_revision  revision;
  11. struct tag_videolfb  videolfb;
  12. struct tag_cmdline  cmdline;
  13. struct tag_acorn       acorn;
  14. struct tag_memclk    memclk;
  15. } u;
  16. };

参数结构体包括两个部分,一个是 tag_header结构体,一个是u联合体。

tag_header结构体的定义如下: 
   struct tag_header { 
                 u32 size;   
                 u32 tag; 
};

其中 size:表示整个 tag 结构体的大小(用字的个数来表示,而不是字节的个数),等于tag_header的大小加上 u联合体的大小,例如,参数结构体 ATAG_CORE 的 size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通过函数 tag_size(struct * tag_xxx)来获得每个参数结构体的 size。其中 tag:表示整个 tag 结构体的标记,如:ATAG_CORE等。

[cpp] view plaincopy
  1. __vet_atags:
  2. tst r2, #0x3 //r2指向该参数链表的起始位置,此处判断它是否字对齐
  3. bne 1f
  4. ldr r5, [r2, #0] //获取第一个tag结构的size
  5. //#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 判断该tag的长度是否合法
  6. subs r5, r5, #ATAG_CORE_SIZE
  7. bne 1f
  8. ldr r5, [r2, #4]  //获取第一个tag结构体的标记,
  9. ldr r6, =ATAG_CORE
  10. cmp r5, r6 //判断第一个tag结构体的标记是不是ATAG_CORE
  11. bne 1f
  12. mov pc, lr //正常退出
  13. 1: mov r2, #0
  14. mov pc, lr  //参数连表不正确
  15. ENDPROC(__vet_atags)

e -- 创建初始页表


其在下面被执行:

下面是详细分析:

[cpp] view plaincopy
  1. /*
  2. * Setup the initial page tables.  We only setup the barest
  3. * amount which are required to get the kernel running, which
  4. * generally means mapping in the kernel code.
  5. *
  6. * r8 = phys_offset, r9 = cpuid, r10 = procinfo
  7. *
  8. * Returns:
  9. *  r0, r3, r5-r7 corrupted
  10. *  r4 = page table (see ARCH_PGD_SHIFT in asm/memory.h)
  11. */
  12. __create_page_tables:
  13. pgtbl   r4, r8              @ page table address
  14. /*
  15. * Clear the swapper page table
  16. */
  17. mov r0, r4
  18. mov r3, #0
  19. add r6, r0, #PG_DIR_SIZE
  20. 1:  str r3, [r0], #4
  21. str r3, [r0], #4
  22. str r3, [r0], #4
  23. str r3, [r0], #4
  24. teq r0, r6
  25. bne 1b
  26. #ifdef CONFIG_ARM_LPAE
  27. /*
  28. * Build the PGD table (first level) to point to the PMD table. A PGD
  29. * entry is 64-bit wide.
  30. */
  31. mov r0, r4
  32. add r3, r4, #0x1000         @ first PMD table address
  33. orr r3, r3, #3          @ PGD block type
  34. mov r6, #4              @ PTRS_PER_PGD
  35. mov r7, #1 << (55 - 32)       @ L_PGD_SWAPPER
  36. 1:
  37. #ifdef CONFIG_CPU_ENDIAN_BE8
  38. str r7, [r0], #4            @ set top PGD entry bits
  39. str r3, [r0], #4            @ set bottom PGD entry bits
  40. #else
  41. str r3, [r0], #4            @ set bottom PGD entry bits
  42. str r7, [r0], #4            @ set top PGD entry bits
  43. #endif
  44. add r3, r3, #0x1000         @ next PMD table
  45. subs    r6, r6, #1
  46. bne 1b
  47. add r4, r4, #0x1000         @ point to the PMD tables
  48. #ifdef CONFIG_CPU_ENDIAN_BE8
  49. add r4, r4, #4          @ we only write the bottom word
  50. #endif
  51. #endif
  52. ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
  53. /*
  54. * Create identity mapping to cater for __enable_mmu.
  55. * This identity mapping will be removed by paging_init().
  56. */
  57. adr r0, __turn_mmu_on_loc
  58. ldmia   r0, {r3, r5, r6}
  59. sub r0, r0, r3          @ virt->phys offset
  60. add r5, r5, r0          @ phys __turn_mmu_on
  61. add r6, r6, r0          @ phys __turn_mmu_on_end
  62. mov r5, r5, lsr #SECTION_SHIFT
  63. mov r6, r6, lsr #SECTION_SHIFT
  64. 1:  orr r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel base
  65. str r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
  66. cmp r5, r6
  67. addlo   r5, r5, #1          @ next section
  68. blo 1b
  69. /*
  70. * Map our RAM from the start to the end of the kernel .bss section.
  71. */
  72. add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
  73. ldr r6, =(_end - 1)
  74. orr r3, r8, r7
  75. add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
  76. 1:  str r3, [r0], #1 << PMD_ORDER
  77. add r3, r3, #1 << SECTION_SHIFT
  78. cmp r0, r6
  79. bls 1b
  80. #ifdef CONFIG_XIP_KERNEL
  81. /*
  82. * Map the kernel image separately as it is not located in RAM.
  83. */
  84. #define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
  85. mov r3, pc
  86. mov r3, r3, lsr #SECTION_SHIFT
  87. orr r3, r7, r3, lsl #SECTION_SHIFT
  88. add r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
  89. str r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
  90. ldr r6, =(_edata_loc - 1)
  91. add r0, r0, #1 << PMD_ORDER
  92. add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
  93. 1:  cmp r0, r6
  94. add r3, r3, #1 << SECTION_SHIFT
  95. strls   r3, [r0], #1 << PMD_ORDER
  96. bls 1b
  97. #endif
  98. /*
  99. * Then map boot params address in r2 if specified.
  100. * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
  101. */
  102. mov r0, r2, lsr #SECTION_SHIFT
  103. movs    r0, r0, lsl #SECTION_SHIFT
  104. subne   r3, r0, r8
  105. addne   r3, r3, #PAGE_OFFSET
  106. addne   r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
  107. orrne   r6, r7, r0
  108. strne   r6, [r3], #1 << PMD_ORDER
  109. addne   r6, r6, #1 << SECTION_SHIFT
  110. strne   r6, [r3]
  111. #if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
  112. sub r4, r4, #4          @ Fixup page table pointer
  113. @ for 64-bit descriptors
  114. #endif
  115. #ifdef CONFIG_DEBUG_LL
  116. #if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
  117. /*
  118. * Map in IO space for serial debugging.
  119. * This allows debug messages to be output
  120. * via a serial console before paging_init.
  121. */
  122. addruart r7, r3, r0
  123. mov r3, r3, lsr #SECTION_SHIFT
  124. mov r3, r3, lsl #PMD_ORDER
  125. add r0, r4, r3
  126. mov r3, r7, lsr #SECTION_SHIFT
  127. ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
  128. orr r3, r7, r3, lsl #SECTION_SHIFT
  129. #ifdef CONFIG_ARM_LPAE
  130. mov r7, #1 << (54 - 32)       @ XN
  131. #ifdef CONFIG_CPU_ENDIAN_BE8
  132. str r7, [r0], #4
  133. str r3, [r0], #4
  134. #else
  135. str r3, [r0], #4
  136. str r7, [r0], #4
  137. #endif
  138. #else
  139. orr r3, r3, #PMD_SECT_XN
  140. str r3, [r0], #4
  141. #endif
  142. #else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
  143. /* we don't need any serial debugging mappings */
  144. ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
  145. #endif
  146. #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
  147. /*
  148. * If we're using the NetWinder or CATS, we also need to map
  149. * in the 16550-type serial port for the debug messages
  150. */
  151. add r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
  152. orr r3, r7, #0x7c000000
  153. str r3, [r0]
  154. #endif
  155. #ifdef CONFIG_ARCH_RPC
  156. /*
  157. * Map in screen at 0x02000000 & SCREEN2_BASE
  158. * Similar reasons here - for debug.  This is
  159. * only for Acorn RiscPC architectures.
  160. */
  161. add r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
  162. orr r3, r7, #0x02000000
  163. str r3, [r0]
  164. add r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
  165. str r3, [r0]
  166. #endif
  167. #endif
  168. #ifdef CONFIG_ARM_LPAE
  169. sub r4, r4, #0x1000     @ point to the PGD table
  170. mov r4, r4, lsr #ARCH_PGD_SHIFT
  171. #endif
  172. mov pc, lr
  173. ENDPROC(__create_page_tables)

f -- 使能MMU,跳转到start_kernel

文件linux/arch/arm/kernel/head.S中

文件linux/arch/arm/kernel/head.S中

在前面有过这样的指令操作ldr r13, __switch_data ,

mov pc, r13 就是将跳转到__switch_data处。

在文件linux/arch/arm/kernel/head-common.S中:

[cpp] view plaincopy
  1. .type __switch_data, %object  //定义一个对象
  2. __switch_data:
  3. .long __mmap_switched  //由此可知上面程序将跳转到该程序段处。
  4. .long __data_loc @ r4
  5. .long _data @ r5
  6. .long __bss_start @ r6
  7. .long _end @ r7
  8. .long processor_id @ r4
  9. .long __machine_arch_type @ r5
  10. .long __atags_pointer @ r6
  11. .long cr_alignment @ r7
  12. .long init_thread_union + THREAD_START_SP @ sp
  13. . = PAGE_OFFSET + TEXT_OFFSET;
  14. #else
  15. . = ALIGN(THREAD_SIZE);
  16. __data_loc = .;
  17. #endif
  18. .data : AT(__data_loc) {  //此处数据存储在上面__data_loc处。
  19. _data = .;
  20. *(.data.init_task)
  21. …………………………
  22. .bss : {
  23. __bss_start = .;
  24. *(.bss)
  25. *(COMMON)
  26. _end = .;
  27. }
  28. ………………………………
  29. init_thread_union 是 init进程的基地址. 在 arch/arm/kernel/init_task.c 中:
  30. 00033: union thread_union init_thread_union
  31. 00034:         __attribute__((__section__(".init.task"))) =
  32. 00035:                 { INIT_THREAD_INFO(init_task) };
  33. 对照 vmlnux.lds.S 中,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的
  34. */
  35. __mmap_switched:
  36. adr r3, __switch_data + 4
  37. ldmia r3!, {r4, r5, r6, r7}
  38. cmp r4, r5 @ Copy data segment if needed
  39. 1: cmpne r5, r6  //将 __data_loc处数据搬移到_data处
  40. ldrne fp, [r4], #4
  41. strne fp, [r5], #4
  42. bne 1b
  43. mov fp, #0 //清除BSS段内容
  44. 1: cmp r6, r7
  45. strcc fp, [r6],#4
  46. bcc 1b
  47. ldmia r3, {r4, r5, r6, r7, sp}
  48. str r9, [r4] @ Save processor ID
  49. str r1, [r5] @ Save machine type
  50. str r2, [r6] @ Save atags pointer
  51. bic r4, r0, #CR_A @ Clear 'A' bit
  52. stmia r7, {r0, r4} @ Save control register values
  53. b start_kernel  //程序跳转到函数start_kernel进入C语言部分。
  54. ENDPROC(__mmap_switched)

Exynos4412 内核移植(三)—— 内核启动过程分析相关推荐

  1. linux-uboot 移植三 uboot启动内核过程

    1.uboot启动内核的几种方式 uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux.常用的跟 boot 有关的命令有: bootz. b ...

  2. Windows内核编程(三)-内核驱动运行与调试

    内核驱动运行与调试 驱动的运行 驱动的运行通过服务来实现. 微软规定,驱动文件必须经过微软的数字签名后,才可以运行在64位系统上,如果把没有经过签名的驱动直接放在64位操作系统中运行,结果是驱动加载失 ...

  3. (三)内核移植--从零开始自制linux掌上电脑(F1C200S)<嵌入式项目>

    目录 一.bootloader.kernel.rootfs联系 二.内核移植 1. 内核源码获取 2. 内核配置与编译

  4. 嵌入式linux内核移植

    获取linux内核 移植linux内核首先我们需要先获内核源码,可以从linux官网下载:https://www.kernel.org,但是一般做法是 根据cpu类型从它们的半导体厂商网站获取,因为半 ...

  5. 十七.linux开发之Kernel移植——内核的启动过程分析

    有道云笔记地址: 详情看这里链接,记录太多,就不一一排版了. http://note.youdao.com/noteshare?id=e68caf68c259bc3491dd7f8c4ac4bb99& ...

  6. 【正点原子Linux连载】第三十七章 Linux内核移植 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  7. Exynos4412 内核移植(六)—— 设备树解析

    一.描述 ARM Device Tree起源于OpenFirmware (OF),在过去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相 ...

  8. 【移植Linux 3.4.2内核第三步】从0制作支持新内核的文件系统

    学习交流加 个人qq: 1126137994 个人微信: liu1126137994 学习交流资源分享qq群: 962535112 上一篇文章,我们修改了内核代码改了系统的分区,但是最后启动,发现虽然 ...

  9. linux运行reggen文件,Linux启动问题——记Virtex-II Pro(PPC405)Linux内核移植

    最近又开始重新拿起以前做东西--试着将Linux内核移植在Virtex-II Pro的开发板上,不过用了很新的内核2.6.29和2.6.33的版本. 基本的平台信息 # Target Board:  ...

  10. Exynos4412 内核移植(五)—— 驱动的移植

    以移植自己制作的驱动,学习内核移植中的驱动移植,及 驱动程序的动态编译和静态编译 硬件环境: Linux 内核版本:Linux 3.14 主机:Ubuntu 12.04发行版 目标机:FS4412平台 ...

最新文章

  1. 2021年大数据Spark(一):框架概述
  2. ListView的高级使用
  3. c#split方法拆分为数据_Dexplot:基于pandas,比matplotlib更简单的数据可视化和数据分析工具...
  4. 又一位巨星因“新冠”陨落,07年图灵奖得主Edmund Clarke,享年75岁
  5. Opencv--Mat类型ROI的设置
  6. javascript:URL编解码和父子窗口交互
  7. 用命令行查看mysql,利用命令行查看Mysql数据库
  8. 字符串函数sprintf / sprintf_s 容易出错的地方
  9. Jeecg Boot 2.2.1 版本发布,基于SpringBoot的低代码平台
  10. ypid编码是什么_国家药品供应保障综合管理信息平台YPID编码规则与应用
  11. VMware-ESXi-6.7.0许可证
  12. 从iRedMail 创建web服务学习Nginx
  13. 网页磁贴模板_文本磁贴(登录)模板 (HTML)
  14. wegame开dnf正在连接服务器,DNF安装wegame后显示无网络连接状态解决办法
  15. html style
  16. 用java设计一个三角形类_利用Java类库中的Point类设计一个三角形类,编程求三角形面积...
  17. 获取执行程序的原路径(绝对路径)
  18. 流式保护器在文物建筑物内的电气防火应用
  19. Java简单项目 水果摊
  20. 名词解释 算法的有限性_数据结构复习之【数据结构和算法概念】

热门文章

  1. jQuery 入门教程(5): 显示/隐藏内容
  2. 线性表 - 数据结构和算法06
  3. 都是大人物,看看你认识几个。
  4. apache代理IIS的80端口实现共存
  5. FlexViewer2.3中拉帘Widget下载
  6. Memory-Associated Differential Learning论文及代码解读
  7. Java中怎么把文本追加到已经存在的文件
  8. 蓝牙调试工具如何使用_使用此有价值的工具改进您的蓝牙项目:第2部分!
  9. ios集成firebase_如何使用Firebase将Google Login集成到Ionic应用程序中
  10. ubuntu 16.04 挂载新硬盘