概述
一、kernel启动之前的准备动作
在kernel启动之前的准备都是由bootloader来完成。所以不管是什么bootloader,例如uboot、LK、superboot等等,都需要实现以下准备动作。这里指说明概念,不涉及代码。
我们在project X项目中使用的bootloader是uboot,具体代码参考第四节“kernel准备动作在uboot中的实现”
1、kernel镜像加载到ddr的相应位置
kernel镜像一般会存在于存储设备上,比如FLASHEMMCSDCARD.
因此,需要先将kernel镜像加载到RAM的位置上,CPU才可以去访问到kernel。
具体实现方法由bootloader决定,可以是自动复制,也可以是根据bootloader cmdline模式下输入的命令来是否复制。
但是注意,加载的位置是有要求的,一般是加载到物理RAM偏移0x8000的位置,也就是要在前面预留出32K的RAM。kernel会从加载的位置上开始解压,而kernel前面的32K空闲RAM中,16K作为boot params,16K作为临时页表
这些在后续kernel启动流程的文章中会说明。
例如,s5pv210的物理RAM的起始地址是0x20000000,那么kernel的加载地址就应该是0x20008000。
2、硬件要求
根据arch/arm/kernel/head.S的stext(kernel的入口函数)的注释头
/*
* 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.
所以有如下要求
* MMU = off
MMU用来处理物理地址到虚拟内存地址的映射,因此需要软件上需要先配置其映射表(也就是后续文章会说明的页表)。MMU关闭的情况下,CPU寻址的地址都是物理地址,也就是不需要经过转化直接访问相应的硬件。一旦打开之后,CPU寻址的所有地址都是虚拟地址,都会经过MMU映射到真正的物理地址上,即使你在代码中访问的是一个物理地址,也会被当作虚拟内存地址使用。
而映射表是由kernel自己创建的,因此,在创建映射表之前kernel访问的地址都是物理地址,所以必须保证MMU是关闭状态。
* D-cache = off
CACHE是CPU和内存之间的高速缓冲存储器,又分成数据缓冲器D-cache和指令缓冲器I-cache。具体意义这里不多说明。
数据Cache一定要关闭,否则可能kernel刚启动的过程中,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常 。
自己的理解是,假设打开MMU之前,cache上存了一个项“地址0x20000000、数据0xffff0000”,打开MMU之后,读取0x20000000地址上(虚拟地址)数据,但是此时会直接从cache中读到项“地址0x20000000、数据0xffff0000”,但实际上对应物理地址上的数据并不是这个,所以会导致读取的数据错误。
* r0 = 0
硬性规定r0寄存器为0,没有什么意义。
* r1 = machine nr
r1寄存器里面存放machine ID。
* r2 = atags or dtb pointer
r2寄存器里面存放atags或者dtb的地址指针。
3、跳转到kernel镜像入口的对应位置
bootloader需要通过设置PC指针到kernel的入口代码处(也就是kernel的加载位置)来实现kernel的跳转。
例如tiny210(s5pv210)的PC设置为0x20008000来实现到kernel的跳转。
二、kernel启动过程入口说明
1、kernel入口地址的指定
arch/arm/kernel/vmlinux.lds.S中
ENTRY(stext)
所以kernel启动的入口代码位于arch/arm/kernel/head.S。
这里也是我们后续要分析kernel启动流程的核心代码部分。
2、kernel入口地址
* 连接地址,通过System.map查看,可以看到stext的连接地址是0xc0008000
c0008000 T stext
加载地址,因为uboot把kernel加载到0x20008000的位置上,所以kernel的入口是0x20008000
根据连接脚本,stext是作为kernel的入口(上述一),所以stext就在kernel镜像的起始位置上,所以stext的加载地址应该0x20008000。
三、kernel启动的两个阶段说明
kernel在启动初期主要分成两个阶段。
1、第一阶段:从入口跳转到start_kernel之前的阶段。
对应代码arch/arm/kernel/head.S中stext的实现:
ENTRY(stext)
这个阶段主要由汇编语言实现。
这个阶段主要负责MMU打开之前的一些操作,以及打开MMU的操作。
由于这个阶段MMU还没有打开,并且kernel加载地址和连接地址并一致,所以需要使用位置无关设计。在运行过程中运行地址和加载地址一致(如果不明白的话建议先参考一下《[kernel 启动流程] 前篇——vmlinux.lds分析》)。
其主要流程如下:
* 设置为SVC模式,关闭所有中断
什么是SVC模式?为什么要设置成SVC模式?
如何关闭所有中断?为什么要关闭所有中断呢?
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9
参考[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断
* 获取CPU ID,提取相应的proc info
从哪里去提取proc_info?
proc_info里面存放了什么东西?这里面的数据结构应该是很重要所以才有必要在打开MMU前就去获取。
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
参考[kernel 启动流程] (第三章)第一阶段之——proc info的获取
* 验证tags或者dtb
如何验证dtb的有效性?
bl __vet_atags
参考[kernel 启动流程] (第四章)第一阶段之——dtb的验证
* 创建临时内核页表的页表项
页表项的作用?基础知识?和MMU之间的关系?
*要为哪些内存区域创建页表项?为什么?
bl __create_page_tables
参考[kernel 启动流程] (第五章)第一阶段之——临时内核页表的创建
* 配置r13寄存器,也就是设置打开MMU之后要跳转到的函数。
为什么要在打开MMU之前配置呢
ldr r13, =__mmap_switched @ address to jump to after
@ mmu has been enabled
使能MMU
如何使能MMU?
swapper_pg_dir变量?
ldr r12, [r10, #PROCINFO_INITFUNC]
add r12, r12, r10
ret r12
b __enable_mmu
参考[kernel 启动流程] (第六章)第一阶段之——打开MMU
* 跳转到start_kernel
如何跳转到start_kernel?
参考[kernel 启动流程] (第七章)第一阶段之——跳转到start_kernel
分析代码的时候可以结合上述疑问去进行分析。
后续分析启动代码的过程中会针对上述流程和疑问进行文档整理。
2、第二阶段:start_kernel开始的阶段。
这个阶段主要由C语言实现。
kernel启动流程中剩余的操作都是这里实现。
这里不多说,后续会有文章说明。
四、kernel准备动作在uboot中的实现
以下以tiny210为例:
1、kernel镜像加载到ddr的相应位置
在project-X 项目中tiny210的存储设备暂时设计为SDCARD,并且是自动将kernel加载到ddr上。
kernel镜像有传统的uImage方式和FIT方式。
这里以传统的uImage方式做说明。
具体实现如下:
u-boot/board/samsung/tiny210/board.c
#define KERNEL_TEXT_BASE 0x20008000
void copy_kernel_to_ddr(void)
{
u32 sdmmc_base_addr;
copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem)(*(u32*)CopySDMMCtoMem);
sdmmc_base_addr = *(u32 *)SDMMC_BASE;
if(sdmmc_base_addr == SDMMC_CH2_BASE_ADDR)
{
copy_bl2(2, MOVI_KERNEL_SDCARD_POS, MOVI_KERNEL_BLKCNT, (u32 *)KERNEL_TEXT_BASE, 0);
}
}
MOVI_KERNEL_SDCARD_POS表示kernel镜像在SDCARD上的位置。
KERNEL_TEXT_BASE表示要加载到RAM上的位置,注意,就是0x20008000。
通过copy_sd_mmc_to_mem来实现SDCARD到DDR上的复制动作。
2、MMU和D-cache的关闭
关于arm的MMU和D-cache都是有协处理器CP15来进行管理。
关于CP15的指令说明和寄存器请自行网上搜索资料。
C1寄存器的某些位 用于配置MMU中的一些操作,C1的0位控制禁止/使能MMU,简单示例如下:
MRC P15,0,R0,C1,0,0
ORR R0,#01
MCR P15,0,R0,C1,0,0
uboot中s5pv210关闭MMU和D-cache的位置在(其实默认就是关闭的)
u-boot/arch/arm/cpu/armv7/start.S
ENTRY(cpu_init_cp15)
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
3、r0 r1 r2寄存器的设置和kernel的跳转
u-boot/arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
unsigned long machid = gd->bd->bi_arch_number;//获取mechine_id
void (*kernel_entry)(int zero, int arch, uint params);
kernel_entry = (void (*)(int, int, uint))images->ep;//获取了要转的入口地址
debug("## Transferring control to Linux (at address %08lx)"
"...n", (ulong) kernel_entry);
r2 = (unsigned long)images->ft_addr;//获取dtb地址。
kernel_entry(0, machid, r2);
}
最后跳转到kernel_entry(0, machid, r2);,也就是0x20008000的位置,并且通过参数,将0传入到r0,machid传入到r1,dtb的地址传入到r2。最终就调用到kernel的入口去了。
在串口输出中会看到如下log:
## Transferring control to Linux (at address 20008000)...
————————————————
版权声明:本文为CSDN博主「ooonebook」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ooonebook/article/details/52710290
最后
以上就是温暖小土豆为你收集整理的[kernel 启动流程] (第一章)概述的全部内容,希望文章能够帮你解决[kernel 启动流程] (第一章)概述所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复