全志平台linux启动流程分析
一、BROM阶段
机器上电之后会执行固化在BROM里面的一段引导程序,这个程序会依次遍历所有支持的启动介质,直到找到第一个支持的。目前支持的启动介质是sd/mmc卡、nand和spinor。当程序初始化启动介质成功后,就从固定位置读入Bootloader的Boot0到SRAM,然后跳到SRAM执行。
下面展示了BROM的执行流程
二、Bootloader阶段
Bootloader是全志平台上从小系统一直沿用下来的内核加载器,在这里的主要职责是加载U-Boot到DRAM。
Bootloader分为两个部分,分别是Boot0和Boot1。
Boot0:初始化DRAM,加载Boot1到DRAM;
Boot1:调频,加载U-Boot到DRAM;
为什么Bootloader要划分成Boot0和Boot1两个部分?因为在Bootloader阶段,使用的SRAM大小是32KB,除去C运行环境需要的栈空间,可用的空间在24KB左右,这点不足以载入整个Bootloader。因此,需要将Bootloader划分成两个部分,尽可能将繁重的任务放在Boot1执行,这个情况类似于Linux系统中断执行环境的上半部和下半部。
1. boot0执行过程
Boot1会进行一次系统调频,将CPU的频率调到用户在sys_config1.fex target段配置的boot_clock。
如何在Boot1让机器进入升级模式?
(1)按住power键,再按任意键3下;
(2)接上串口启动,进入Boot1后在键盘输入2;
如何替换Bootloader分区的内容?
接上串口启动,进入Boot1后在键盘输入1,USB会挂载Bootloader分区到PC上,卷标是“Volume”,替换掉相关的文件之后重启机器即可生效。Boot1会检测低电关机,以及插入火牛开机的情况进入关机程序。后者需要在sys_config1.fex里配置。
三、u-boot阶段
概括地说,U-Boot引导内核分为两个阶段,第一阶段负责重定位U-Boot到最高地址,第二阶段才是真正的引导内核。
1. 第一阶段
在第一阶段会关闭I/D cache和MMU,因此,整个U-Boot是直接运行在物理地址上。
2. 第二阶段
U-Boot第二阶段有一个完整、宽松的C环境,不再受制于栈空间,各个平台可以在这个阶段完成一些复杂的操作,以求达到定制的目的。
a. board init
执行平台相关的初始化,这些比较复杂,不适宜在第一阶段完成。这部分的功能在board/allwinner目录下实现。
b. device init
主要是根据用户的编译配置选择初始化对应的存储设备。这个地方可以改进,比如根据用户的配置文件
sys_config1.fex选择初始化对应的存储设备。这样可以做到一份u-boot.bin适应不同的存储设备。
c. env relocate
初始化环境变量。
d. board later init
初始化fastboot和android recovery,可能修改bootcmd,影响引导流程。
e. main loop
U-Boot的主程序,负责引导内核以及处理用户的命令请求。这部分的功能在common目录下实现。
四、内核启动
在我们平台上使用的是非压缩的内核(bImage),因此内核的启动省去了自解压的过程。内核的链接依赖链接脚本vmlinux.lds,我们平台使用了ARM的内核,对应的链接脚本是arch/arm/kernel/vmlinux.lds。
1. 调用__lookup_processor_type@head-common.S查找处理器信息
2. 调用__create_page_tables@head.S为内核自身创建页表
3. 调用处理器底层初始化函数__v7_setup@arch/arm/mm/proc-v7.S,初始化 MMU,Cache,TLB
4. 调用__enable_mmu@head.S使能MMU
5. 调用__mmap_switched@head-common.S重定位数据段,清零BSS,然后跳转到C函数入口
start_kernel@init/main.c,start_kernel()函数是内核初始化 C 语言部分的主体。这个函数完成系统底层基本机
制,包括处理器、存储管理系统、进程管理系统、中断机制、定时机制等的初始化工作。由于这个函数过于复
杂,这里仅阐述关键点。
6. 调用setup_arch完成架构相关的初始化
7. 调用pidhash_init初始化pid hast机制
8. 调用sched_init初始化调度器
9. 调用init_IRQ初始化中断机制
10. 调用softirq_init完成软中断初始化
11. 调用local_irq_enable打开中断
12. 调用console_init完成控制台初始化
13. 调用rest_init
由上可知,rest_init函数创建了两个内核线程,分别是kernel_init和kthreadd。kernel_init函数将完成设备驱动
程序的初始化,并调用init_post函数启动用户空间的init进程。kthreadd的作用是管理调度其他的内核线程。
14. 调用do_basic_setup函数初始化设备,完成外设及其驱动程序(直接编译进内核的模块)的加载和初始化。
a. cpuset_init_smp@init/cpuset.c
创建cpuset工作队列,它的作用是控制每个程序在哪个核心执行,对于多核的CPU。
b. usermodehelper_init@init/kmod.c
创建khelper工作队列,它的作用是指定用户空间的程序路径和环境变量,最终运行user space的程序。
c. init_tmpfs@mm/shmem.c
初始化tmpfs。
d. driver_init@driver/base/init.c
初始化设备模型。
e. init_irq_proc@kernel/irq/proc.c
初始化/proc/irq。
f. do_ctors@init/main.c
调用所有构造函数。
g. do_initcalls
初始化子系统。注意,.initcall.init节所保存的函数地址有一定的优先级,越前面的函数优先级越高,会被先调
用。include/linux/init.h定义若干的宏协助内核模块添加它们的初始化函数到.initcall.init节。如需控制同一优先
级的初始化函数执行顺序,可以通过修改模块的Makefile调动编译链接顺序。
15. 调用init_post函数启动用户空间的init进程。在Android系统中,init进程在system/core/init目录下实现。
init_post函数会调用run_init_process 执行ramdisk_execute_command,在run_init_process中,
kernel_execve负责创建用户空间的init进程。
版权声明:本文为博主原创文章,未经博主允许不得转载。