当按下Android设备电源键时发生了什么

当按下Android设备电源键时发生了什么Android系统启动(1)-Bootloader启动流程 分析按下电源键后发生了什么,Bootloader如何启动内核等

Android系统启动(1)-Bootloader启动流程

前言

本文讲述手机按下电源键后如何启动内核的流程,后续会继续分析启动内核之后的流程。

主要分析:

  1. 按下电源键后发生了什么?
  2. BootRom
  3. Bootloader(init.S, main.c)

按下电源键后到启动内核的主要流程图:

当按下Android设备电源键时发生了什么

按下电源键

当我们按下手机电源后,此时手机主板上的引导芯片会从BootROM里面拿到bootloader程序,然后把bootloader程序加载到RAM中开始运行,开始初始化cpu…

BootRom

Bootrom(或Boot ROM)是嵌入处理器芯片内的一小块掩模ROM或写保护闪存。它包含处理器在上电或复位时执行的第一个代码。

掩模ROM

掩模ROM又是什么鬼?

掩模ROM是一种只读存储器,或ROM,在生产过程中被屏蔽。"掩模"是指集成电路的一部分,它是一种用来处理数据的薄电子电路,上面覆盖着被称为光掩模的不透明板。这些板包含透明膜或孔,允许某些区域的光同时阻挡其他区域的光创造独特的图案。

掩模ROM无法使用户更改存储在其中的数据,即使断电也能够保留数据,要想在只读存储器中存入或改变数据,必须具备特定的条件。

RAM与ROM区别

RAMROM的区别: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的引导程序的源码。

image.png

引导程序执行流程:

  1. init.s初始化堆栈,清零BBS段,调用main.c的_main()函数;
  2. 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函数主要做了以下几件事:

  1. 初始化时钟
  2. 初始化主板
  3. 初始化控制台
  4. 创建linux标签
  5. 调用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主要做了以下几件事:

  1. 读取boot头部
  2. 读取内核(kernel)地址
  3. 读取 ramdisk
  4. 最后调用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);
}

总结

  1. 手机按下电源键后,引导芯片在bootrom获取到引导程序,把引导程序加载到内存执行。
  2. Bootloade引导程序执行流程,首先执行汇编阶段init.S,通过blx指令执行到c阶段的_main函数,然后从boot.img获取到内核的地址,最后调用boot_linux进入到kernel的entry入口,启动内核。
  3. 看源码推荐 Android Code Search

预告

Kernel内核的启动流程

今天的文章当按下Android设备电源键时发生了什么分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/20727.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注