裸机启动过程
当我们生成bin文件的时候 bin文件的头部前4个字节是SP指针的值 接着四个字节是ResetHandler的值 所以当我们使用IAP的时候往往可以检查者8个字节来确定接受到的数据是否准确
上电后根据boot0和boot1的值决定从哪里启动(比如把0x8000000映射到0x00000000)
先取出sp指针的值 在把ResetHandler的地址赋给PC指针
执行ResetHandler函数 包括两部分 systemInit和 __main
systemInit初始化时钟等核心外设
__main函数:根据bin文件的信息 初始化堆/栈 然后将相应的段搬到内存里去
__main函数跳转main函数执行
FreeRTOS启动过程
- 裸机的启动过程
- 执行vTaskStartScheduler函数
- 创建空闲任务/定时器任务线程:(空闲任务线程很重要)
- 空闲任务的重要性
保证系统中一定有任务在运行
可以在空闲任务中回收一些资源(被删除的任务等等)
设置一些钩子函数
在空闲任务进入低功耗模式
- 空闲任务的重要性
- 设置一些必要的全局变量,包括时钟节拍计数器、临界区嵌套计数器,堆内存等
- 设置PendSV和sysTick的优先级为最低
- 使能SVC异常 通过SVC异常启动第一个任务
Uboot启动过程
裸机的启动之所以简单,和这两个硬件密不可分:SRAM和NORFLASH 我们不用去初始化这俩硬件,CPU直接通过地址就能访问到里面的数据并进行读写–但糟糕的是这俩硬件的成本太高了,我们的Linux启动看起来麻烦就是因为我们的启动是在DDR + NAND FLASH中的
- XIP启动—整个BootLoader都在NOR FLASH中
此时启动过程和裸机启动过程差不多- step1 : 初始化硬件:时钟/内存(SDRAM C运行需要的环境)/flash等等
- step2 : 把内核从flash 拷贝到内存SDRAM中
- step3 : 启动内核
- 非XIP启动 BootLoader在NAND FLASH中
BROM不够强大
- step1 : BROM程序执行(初始化时钟,中断控制器啥的),复制NAND FLASH的前4KB 到 内部的SRAM中
- step2 : 执行前4KB代码(SRAM中) 主要是核心硬件的初始化(SDRAM NAND FLASH)等 此时关闭MMU
- step3 : 这4KB代码最后需要把整个Uboot拷贝到SDRAM中(完成自举)
- step4 : Uboot此时现在都在SDRAM了 Uboot执行接下里的工作: 初始化其他硬件 把内核搬到SDRAM中
- step5 : 向内核传参并启动内核
BROM够强大
- step1: BROM程序根据Uboot提供的信息 直接初始化核心硬件(SDRAM FLASH)
- step2: BROM程序把Uboot拷贝到SDRAM
- step3: Uboot初始化其他硬件 并把内核搬到SDRAM中
- step4 : 向内核传参并启动内核
- Uboot与内核的传参
最后调用theKernel (0, machid, bd->bi_boot_params);启动内核。
第一个参数在R0中 第二个参数在R1中,第3个参数传递的就是大片传参tag的首地址
struct tag_header {
__u32 size; // 当前tag的总大小(单位:字)
__u32 tag; // 标记类型(如ATAG_CORE、ATAG_MEM等)
};
struct tag {
struct tag_header hdr;
union {
struct tag_core core; // 核心参数(如页大小、根设备)
struct tag_mem32 mem; // 内存信息
struct tag_cmdline cmdline; // 命令行参数(bootargs)
// 其他类型见下文
} u;
}
u-boot传递给内核的参数有很多个,如系统的根设备标志,页面大小,内存的起始地址和大小,RAMDISK的起始地址和大小,压缩的RAMDISK根文件系统的起始地址和大小等,而每个参数我们都是单独的采用一个struct tag来标识的,之前提到的参数标记如ATAG_MEM32,ATAG_INTRD等就是用来标识该tag结构是用来存放的哪种类型的参数
Linux内核启动过程
- 解压缩
- 创建临时页表并使能MMU
- start_kernel()函数–大量的初始化(解析设备树setup_machine_fdt()) | do_init_call()函数 初始化驱动
- 创建kernel_init进程和空白进程