一、芯片上电启动流程
芯片上电解复位后执行第一段程序的执行地址指向0x00000000或0xffff0000,这段程序被称为Bootrom loader,该段程序在芯片制造过程中固化到其内部的ROM空间,只读不可修改
1、启动片内bootrom程序后,根据设置的启动模式,决定从什么介质启动,sd-card或emmc;所有的存储设备的bootloader都无法校验通过,则会自动进入MaskRom模式
2、初始化ddr
3、bootloader完整代码到DDR内存中并运行
二、U-BOOT启动流程
RK平台的U-Boot 启动流程如下,仅列出一些重要步骤,参考rk文档
start.s
// 汇编环境
=> IRQ/FIQ/lowlevel/vbar/errata/cp15/gic
=> _main
=> stack
// ARM架构相关的lowlevel初始化
// 准备好C环境需要的栈
//【第一阶段】C环境初始化,发起一系列的函数调用
=> board_init_f: init_sequence_f[]
initf_malloc
arch_cpu_init // SoC的lowlevel初始化
serial_init // 串口初始化
dram_init // 获取ddr容量信息
reserve_mmu // 从ddr末尾开始往低地址reserve内存
reserve_video
reserve_uboot
reserve_malloc
reserve_global_data
reserve_fdt
reserve_stacks
dram_init_banksize
sysmem_init
setup_reloc // 确定U-Boot自身要reloc的地址
// 汇编环境
=> relocate_code // 汇编实现U-Boot代码的relocation
// 第二阶段:C环境初始化,发起一系列的函数调用
=> board_init_r: init_sequence_r[]
initr_caches // 使能MMU和I/Dcache
initr_malloc
bidram_initr
sysmem_initr
initr_of_live // 初始化of_live
initr_dm // 初始化dm框架
board_init // 平台初始化,最核心部分
board_debug_uart_init // 串口iomux、clk配置
init_kernel_dtb // 初始化dtb
clks_probe // 初始化系统频率
regulators_enable_boot_on // 初始化系统电源
io_domain_init //io-domain初始化
set_armclk_rate
dvfs_init
rk_board_init
console_init_r
board_late_init // 平台late初始化
rockchip_set_ethaddr // 设置mac地址
rockchip_set_serialno // 设置serialno
setup_boot_mode // 解析reboot XX命令
charge_display
rockchip_show_logo // 显示开机logo
soc_clk_dump // 打印clk tree
rk_board_late_init
boot_jump_linux // uboot 跳转到linux内核
run_main_loop // 进入命令行模式,或执行启动命令
uboot的启动过程分为BL1和BL2两个阶段,BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现;BL2阶段主要是对外部设备如网卡、Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现。
1、BL1阶段
uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:
(1) 指定uboot的入口。在链接脚本uboot.lds中指定uboot的入口为start.S中的_start。
(2)设置异常向量(exception vector)
(3)关闭IRQ、FIQ,设置SVC模式
(4)关闭L1 cache、设置L2 cache、关闭MMU
(5)根据OM引脚确定启动方式
(6)在SoC内部SRAM中设置栈
(7)lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)
(8)设置开发板供电锁存
(9)设置SDRAM中的栈
(10)将uboot从SD卡拷贝到SDRAM中
(11)设置并开启MMU
(12)通过对SDRAM整体使用规划,在SDRAM中合适的地方设置栈
(13)清除bss段,远跳转到start_armboot执行,BL1阶段执行完
2、BL2阶段
start_armboot函数位于lib_arm/board.c中,是C语言开始的函数,也是BL2阶段代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,BL2阶段的主要功能如下:
(1)规划uboot的内存使用
(2)遍历调用函数指针数组init_sequence中的初始化函数
(3)初始化uboot的堆管理器mem_malloc_init
(4)初始化SD/MMC控制器mmc_initialize
(5)环境变量重定位env_relocate
(6)将环境变量中网卡地址赋值给全局变量的开发板变量
(7)开发板硬件设备的初始化devices_init
(8)跳转表jumptable_init
(9)控制台初始化console_init_r
(10)网卡芯片初始化eth_initialize
(11)uboot进入主循环main_loop
三、kernel启动流程
启动代码位置:init/main.c
asmlinkage __visible void __init start_kernel(void){
char *command_line;
char *after_dashes;
// 设置任务栈结束魔术数,用于栈溢出检测
set_task_stack_end_magic(&init_task);
//设置处理器 ID
smp_setup_processor_id();
//debug 初始化
debug_objects_early_init();
//cgroup 初始化, cgroup 用于控制 Linux 系统资源
cgroup_init_early();
//关闭当前 CPU 中断
local_irq_disable();
early_boot_irqs_disabled = true;
//CPU 初始化
boot_cpu_init();
//页地址相关的初始化
page_address_init();
pr_notice("%s", linux_banner);
// 架构相关的初始化,此函数会解析uboot传递进来的参数,读取并解析dtb内容,初始化内存等
setup_arch(&command_line);
//初始化内存相关
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
//如果只是 SMP(多核 CPU)的话,此函数用于获取 * CPU 核心数量, CPU 数量保存在变量
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
build_all_zonelists(NULL); //建立系统内存页区(zone)链表
page_alloc_init();
......
trap_init(); //完成对系统保留中断向量的初始化
mm_init(); //内存管理初始化
......
init_IRQ(); //中断初始化
tick_init(); //tick初始化
rcu_init_nohz();
init_timers(); //初始化定时器
hrtimers_init();
softirq_init(); //软中断初始化
timekeeping_init();
.......
early_boot_irqs_disabled = false;
local_irq_enable(); //中断使能
kmem_cache_init_late(); //slab 初始化, slab 是 Linux 内存分配器
console_init(); //console init
.......
kmemleak_init(); //kmemleak 初始化, kmemleak 用于检查内存泄漏
proc_caches_init();
uts_ns_init();
buffer_init(); //初始化缓冲区
.......
rest_init(); //初始化第一个init进程
prevent_tail_call_optimization();
}
start kernel具体工作:
1) 调用setup_arch()函数进行与体系结构相关的第一个初始化工作;对不同的体系结构来说该函数有不同的定义。
2) 创建异常向量表和初始化中断处理函数;
3) 初始化系统核心进程调度器和时钟中断处理机制;
4) 初始化串口控制台(console_init);
5) 创建和初始化系统cache,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFile System)及页缓存。
6) 初始化内存管理,检测内存大小及被内核占用的内存情况;
7) 初始化系统的进程间通信机制(IPC); 当以上所有的初始化工作结束后,start_kernel()函数会调用rest_init()函数来进行最后的初始化,包括创建系统的第一个进程-init进程来结束内核的启动。
8)挂载根文件系统并启动initLinux内核启动的下一过程是启动第一个进程init,但必须以根文件系统为载体,所以在启动init之前,还要挂载根文件系统
根文件系统至少包括以下目录:
/etc/:存储重要的配置文件。
/bin/:存储常用且开机时必须用到的执行文件。
/sbin/:存储着开机过程中所需的系统执行文件。
/lib/:存储/bin/及/sbin/的执行文件所需的链接库,以及Linux的内核模块。
/dev/:存储设备文件。
注:五大目录必须存储在根文件系统上,缺一不可。
四、android init进程
init进程是android系统中第一个进程,它完成下面这些事情:
1、设置和挂载文件系统
2、启动和管理属性服务property service
3、解析init.rc脚本,根据解析内容启动系统需要的基础服务
第一阶段:
Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的,因此建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
第二阶段:
android的启动流程可以概括如下图