Android系统启动(1)-Bootloader启动流程
前言
本文讲述手机按下电源键后如何启动内核的流程,后续会继续分析启动内核之后的流程。
主要分析:
- 按下电源键后发生了什么?
- BootRom
- Bootloader(init.S, main.c)
按下电源键后到启动内核的主要流程图:
按下电源键
当我们按下手机电源后,此时手机主板上的引导芯片会从BootROM里面拿到bootloader程序,然后把bootloader程序加载到RAM中开始运行,开始初始化cpu…
BootRom
Bootrom(或Boot ROM)是嵌入处理器芯片内的一小块掩模ROM
或写保护闪存。它包含处理器在上电或复位时执行的第一个代码。
掩模ROM
掩模ROM又是什么鬼?
掩模ROM是一种
只读存储器
,或ROM,在生产过程中被屏蔽。"掩模"
是指集成电路的一部分,它是一种用来处理数据的薄电子电路,上面覆盖着被称为光掩模的不透明板。这些板包含透明膜或孔,允许某些区域的光同时阻挡其他区域的光创造独特的图案。
掩模ROM无法使用户更改存储在其中的数据,即使断电也能够保留数据,要想在只读存储器中存入或改变数据,必须具备特定的条件。
RAM与ROM区别
RAM
和ROM
的区别:ROM和RAM都是一种存储技术,只是两者原理不同,RAM为随机存储,掉电不会保存数据,而ROM可以在掉电的情况下,依然保存原有的数据。ROM和RAM指的都是半导体存储器。
Bootloader
Bootloader是什么?
Bootloader
是在Android系统开始运行前的一个小程序,也称为引导程序
,它是运行的第一个程序,主要负责将系统拉起。
Bootloader
是OEM厂商(华为、小米和三星等)或者运营商加锁和限制的地方,一旦Bootloader锁上,你就无法在手机上安装其它操作系统。
Bootloade执行过程
当Bootloader程序被加载到RAM后,它做了什么呢?
- bootloader程序分两个阶段执行。
- 第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;
- 第二个阶段,引导程序设置网络、内存等等。
源码分析
Android引导程序可以在\bootable\bootloader\legacy\usbloader
找到。
因为在后面的版本,Android源码已经找不到这个路径,我只能找到4.0的引导程序的源码。
引导程序执行流程:
init.s
初始化堆栈,清零BBS段,调用main.c的_main()函数;main.c
初始化硬件(闹钟、主板、键盘、控制台),创建linux标签。
init.S(汇编阶段代码)
当汇编代码执行完后,可以看到,最后执行了blx r4
这个汇编指令,这个指令代表从ARM指令集跳转到指令中指定的目标地址
。
// bootable/bootloader/legacy/usbloader/init.S
.global irq_glue
.global irq_vector_table
#include <boot/arm.h>
v_reset:
b start
v_undefined:
b .
v_swi:
b .
v_prefetch_abt:
b .
v_data_abt:
b .
v_reserved:
b .
v_irq:
b .
v_fiq:
b .
start:
ldr r5, =0xfffff000
ands r4, pc, r5
beq already_at_zero
/* we're not loaded at 0 -- relocate us back down to where we belong */
mov r5, #0
ldr r6, =BOOTLOADER_END
1: ldr r7, [r4], #4
str r7, [r5], #4
cmp r5, r6
bne 1b
mov pc, #0
already_at_zero:
/* save registers for main() */
mov r7, r0
mov r8, r1
mov r9, r2
mov r10, r3
// 初始化堆栈
ldr r0, =BOOTLOADER_STACK
msr cpsr_c, #(PSR_I | PSR_F | PSR_SVC)
mov sp, r0
// 清零BBS段
ldr r1, =BOOTLOADER_BSS
ldr r2, =BOOTLOADER_END
mov r0, #0
1: str r0, [r1], #4
cmp r1, r2
ble 1b
bl periph_2gb_open
/* restore registers for main() */
mov r0, r7
mov r1, r8
mov r2, r9
mov r3, r10
ldr r4, =_main // 把main.c的_main地址存储到r4
blx r4 // 执行r4地址程序->即main.c的_main函数
b .
BLX指令
BLX
指令从ARM 指令集跳转到指令中所指定的目标地址
,并将处理器的工作状态有ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。
main.c(C阶段代码)
从汇编指令blx r4
跳转到了main.c的_main函数
_main函数主要做了以下几件事:
- 初始化时钟
- 初始化主板
- 初始化控制台
- 创建linux标签
- 调用
boot_linux_from_flash
加载内核(重点)
//bootable/bootloader/legacy/usbloader/main.c
int _main(unsigned zero, unsigned type, unsigned tags)
{
const char *cmdline = 0;
int n;
arm11_clock_init(); // 初始化时钟
// 省略代码...
board_init(); // 初始化键盘
keypad_init(); // 初始化主板
console_init(); // 初始化控制台
dprintf_set_putc(uart_putc);
if(linux_tags == 0) {
/* generate atags containing partitions * from the bootloader, etc */
linux_tags = ADDR_TAGS;
create_atags(linux_tags, 0, 0, 0); // 创建linux标签
}
if (cmdline) {
char *sn = strstr(cmdline, SERIALNO_STR);
if (sn) {
char *s = serialno;
sn += SERIALNO_LEN;
while (*sn && (*sn != ' ') && ((s - serialno) < 31)) {
*s++ = *sn++;
}
*s++ = 0;
}
}
// 省略代码...
flash_dump_ptn();
flash_init();
/* scan the keyboard a bit */
for(n = 0; n < 50; n++) {
boot_poll();
}
if (boot_from_flash) {
cprintf("\n ** BOOTING LINUX FROM FLASH **\n");
// 加载内核
boot_linux_from_flash();
}
usbloader_init();
for(;;) {
usb_poll();
}
return 0;
}
boot_linux_from_flash加载内核
boot_linux_from_flash主要做了以下几件事:
- 读取boot头部
- 读取内核(kernel)地址
- 读取 ramdisk
- 最后调用
boot_linux
启动内核
// 主要是内核的加载过程,我们的 boot.img 包含:kernel 头、kernel、ramdisk、
// second stage(可以没有)。
int boot_linux_from_flash(void)
{
boot_img_hdr *hdr = (void*) raw_header;
unsigned n;
ptentry *p;
unsigned offset = 0;
const char *cmdline;
if((p = flash_find_ptn("boot")) == 0) {
cprintf("NO BOOT PARTITION\n");
return -1;
}
// 1.读取boot 头部
if(flash_read(p, offset, raw_header, 2048)) {
cprintf("CANNOT READ BOOT IMAGE HEADER\n");
return -1;
}
offset += 2048;
// 2.读取 内核
if(memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
cprintf("INVALID BOOT IMAGE HEADER\n");
return -1;
}
n = (hdr->kernel_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
if(flash_read(p, offset, (void*) hdr->kernel_addr, n)) {
cprintf("CANNOT READ KERNEL IMAGE\n");
return -1;
}
offset += n;
// 3.读取 ramdisk
n = (hdr->ramdisk_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
if(flash_read(p, offset, (void*) hdr->ramdisk_addr, n)) {
cprintf("CANNOT READ RAMDISK IMAGE\n");
return -1;
}
offset += n;
create_atags(ADDR_TAGS, cmdline,
hdr->ramdisk_addr, hdr->ramdisk_size);
// 4.启动内核
// 在boot_linux 中entry(0,machtype,tags);从kernel加载在内核中的地址开始运行了。
boot_linux(hdr->kernel_addr); //
return 0;
boot_linux
进入内核的entry入口,启动kernel内核
static void boot_linux(unsigned kaddr)
{
// 此处定义了内核入口函数entry(),将kaddr地址传给函数指针
void (*entry)(unsigned,unsigned,unsigned) = (void*) kaddr;
entry(0, board_machtype(), ADDR_TAGS);
}
总结
- 手机按下电源键后,引导芯片在bootrom获取到引导程序,把引导程序加载到内存执行。
- Bootloade引导程序执行流程,首先执行汇编阶段
init.S
,通过blx
指令执行到c阶段的_main函数,然后从boot.img
获取到内核的地址,最后调用boot_linux
进入到kernel的entry入口,启动内核。 - 看源码推荐
Android Code Search
预告
Kernel内核的启动流程
今天的文章当按下Android设备电源键时发生了什么分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20727.html