Bootloader的两个阶段
1.第一阶段
(1. 硬件设备初始化。(关闭WATCHDOG、关中断、设置CPU的速度和时钟频率(不是必须)、RAM初始化等)
(2. 为加载Bootloader的第二阶段代码准备RAM空间。(不是必须)
(3. 复制Bootloader的第二阶段代码到RAM空间中。
(4. 设置好栈。
(5. 跳转到第二阶段代码的C入口点。
2.第二阶段:
(1. 初始化本阶段要使用到的硬件设备。
(2. 检测系统内存映射。
(3. 将内核映象和根文件映象从Flash上读到RAM空间中。
(4. 为内核设置启动参数。
(5. 调用内核。
将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核前,下列条件要满足:
(1. CPU寄存器的设置
.R0=0
.R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见linux/arch/arm/tools/mach-types.
.R2=启动参数标记列表在RAM中起始基地址。
(2. CPU工作模式
.必须禁止中断(IRQs和FIQs)
.CPU必须为SVC模式。
(3. Cache和MMU的设置
.MMU必须关闭
.指令Cache可以打开也可以关闭。
.数据Cache必须关闭。
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。传递方法是:Bootloader将参数放在某个约定的地方后,再启动内核,内核启动后从这个地方获得参数。既要约定好参数存放的地址,还要规定参数的结构。Linux2.4.x以后的内核都期望以标记列表的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以ATAG_CORE开始,以标记ATAG_NONE结束。标记的数据结构为tag,它由一个tag_header结构和一个联合组成。tag_header结构表示标记的类型及长度。
数据结构tag和tag_header定义在uboot/include/asm-arm/setup.h , kernel/arch/arm/include/asm/setup.h中。
设置标记的文件uboot/lib_arm/bootm.c
分析配置过程:
// uboot/Makefile:
mx6q_sabresd_config \
mx6q_sabresd_android_config \
mx6q_sabresd_mfg_config \
mx6q_sabresd_iram_config : unconfig
@[ -z "$(findstring iram_,$@)" ] || \
{ echo "TEXT_BASE = 0x00907000" >$(obj)board/freescale/mx6q_sabresd/config.tmp ; \
echo "... with iram configuration" ; \
}
@$(MKCONFIG) $(@:_config=) arm arm_cortexa8 mx6q_sabresd freescale mx6
分析:
mkconfig mx6q_sabresd_android_config arm arm_cortexa8 mx6q_sabresd freescale mx6
$0 $1 $2 $3 $4 $5 $6
分析文件uboot/mkconfig 最后生成include/config.mk, include/config.h文件。
<pre name="code" class="cpp">// include/config.mk
ARCH = arm
CPU = arm_cortexa8
BOARD = mx6q_sabresd
VENDOR = freescale
SOC = mx6
分析编译过程:
分析文件Makefile
uboot/cpu/arm_cortexa8/start.S
链接地址:board/freescale/mx6q_sabresd/u-boot.lds + TEXT_BASE(board/freescale/mx6q_sabresd/config.mk)
从链接文件board/freescale/mx6q_sabresd/u-boot.lds可以看出uboot的入口点是start.S
源码分析之第一阶段:
uboot/cpu/arm_cortexa8/start.S
uboot/lib_arm/board.c (start_armboot (void))
源码分析第二阶段:(从FLASH读出内核,启动)
uboot/lib_arm/board.c (start_armboot (void))
调用main_loop (common/main.c)
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checki ng */
# endif
# ifndef CONFIG_SYS_HUSH_PARSER
run_command (s, 0);
bootcmd ???
uboot命令
uboot/common/main.c(int run_command (const char *cmd, int flag))
比如bootm, common/cmd_bootm.c (U_BOOT_CMD)
include/command.h (#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help})
// board/freescale/mx6q_sabresd/u-boot.lds
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
uboot启动内核
uboot/common/main.c (main_loop (void))
从哪读,读到哪? 分区
uImage = 头部 + 真正的内核
lib_arm/bootm.c (int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images))
theKernel (0, machid, bd->bi_boot_params);
kernel 分析
Linux的启动过程可以分为两部分:架构/开发板相关的引导过程,后续的通用启动过程。
引导阶段通常使用汇编语言编写,它首先检查内核是否支持当前架构的处理器,然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel函数作准备了,主要分如下两个步骤。
1.连接内核时使用的虚拟地址,所以要设置页表,使能MMU。
2.调用C函数start_kernel之前的常规工作,包括复制数据段,清除BSS段,调用start_kernel函数。
第二阶段的关键代码主要使用C语言编写。它进行内核初始化的全部工作,最后调用rest_init函数启动init过程,创建系统第一个进程:init进程。第二阶段,仍有部分架构/开发板相关的代码,如setup_arch函数用于进行架构/开发板相关的设置。
ARM架构处理器上Linux内核vmlinux的启动过程。
配置文件 arch/arm/configs/*_defconfig , 生成.config文件。
以CONFIG_TOUCHSCREEN_GOODIX为例:
<pre name="code" class="cpp"><pre name="code" class="cpp">
配置项: CONFIG_TOUCHSCREEN_GOODIX=y
1.Makefile drivers/input/touchscreen/Makefile
obj-$(CONFIG_TOUCHSCREEN_GOODIX) += gt9xx.o gt9xx_update.o goodix_tool.o
2.autoconf.h include/generated/autoconf.h
#define CONFIG_TOUCHSCREEN_GOODIX 1
3.auto.conf include/config/auto.conf
4.include/config/tristate.conf ??????????????
5.arch/arm/configs/imx6_android_defconfig
make uImage时, .config文件自动生成auto.conf(用于源码)和autoconf.h(用于顶层的Makefile)
Makefile分析
makefile文件说明手册(kernel/Documentation/kbuild/makefiles.txt)
1.子目录下的Makefile: obj-y += obj-m +=
2.make uImage => arch/arm/Makefile
uImage依赖vmlinux
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
init-y := $(patsubst %/, %/built-in.o, $(init-y)) // = init/built-in.o
core-y := $(patsubst %/, %/built-in.o, $(core-y)) // = usr/built-in.o
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) // = drivers/buit-in.o sound/built-in.o firmware/built-in.o
net-y := $(patsubst %/, %/built-in.o, $(net-y)) // = net/built-in.o
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
链接脚本:arch/arm/kernel/vmlinux.lds
第一个文件: arch/arm/kernel/head.S arch/arm/kernel/head-common.S
(1. __lookup_processor_type 确定内核是否支持该架构
(2. __create_page_tables 建立一级页表
(3. __enable_mmu 使能MMU
(4. __mmap_switched 调用start_kernel
init/main.c的start_kernel函数
(1. printk(KERN_NOTICE "%s", linux_banner); 输出linux版本信息
(2. setup_arch 设置与体系结构相关的环境 (arch/arm/kernel/setup.c)
(3. console_init 初始化控制台
(4. rest_init 启动init过程
配置文件arch/arm/configs/imx6_android_defconfig
内核中对于每种支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,它定义了开发板相关的一些属性及函数,比如机器类型ID、起始I/O物理地址、Bootloader传入的参数的地址、中断初始化函数、I/O映射函数等。
// arch/arm/mach-mx6/board-mx6q_sabresd.c
MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.boot_params = MX6_PHYS_OFFSET + 0x100,
.fixup = fixup_mxc_board,
.map_io = mx6_map_io,
.init_irq = mx6_init_irq,
.init_machine = mx6_sabresd_board_init,
.timer = &mx6_sabresd_timer,
.reserve = mx6q_sabresd_reserve,
MACHINE_END