http://www.07net01.com/2016/11/1721675.html

记得当初学linux时候,bootloader 代码相对来说还比较简单,主要几个汇编文件加上几个C文件,编译一个uboot就ok了。做Android驱动后,发现Android专门做了一个目录bootable来实现boot等相关功能。功能也比较多,所以就准备来研究一下这一部分。今天就先研究一下LK,LK全称为Little Kernel,是AP模块bootloader中实现的一个微型系统。

boot架构

首先来了解一下bootable代码的目录结构,其下主要有三个子目录,如下:

#bootable
-bootloader/LK-app            #功能实现,如 adb 命令-arch           #CPU架构-dev            #设备驱动-include      #头文件-kernel        #主文件,main.c-lib             #库文件-platform    #平台文件,如:msm8916-projiect     #mk文件-make       #mk文件-scripts      #脚本文件-target        #目标设备文件AndroidBoot.mkmakefie
-recovery #由lk启动,主要用来更新主系统(即我们平时使用的Android系统)
-diskinstaller #打包镜像

LK流程分析

在LK的链接文件ssystem-onesegment.ld 或 system-twosegment.ld (位于bootable/bootloadler/lk/arch/arm/,此文件用来指定代码的内存分布等)中,LK指定lk/arch/crt0.s中的_start函数为入口函数,crt.s主要初始化CPU,然后长跳转(bl)到lk/kernel/main.c中kmain函数,初始化lk系统,接着初始化boot,跳转到kernel。接下来按照此流程来分析一下。

kmain()

与 boot 启动初始化相关函数为 arch_early_init、 platform_early_init 、bootstrap2/bootstrap_nandwrite,这些函数比较重要,待会儿再详解,如下:

void kmain(void)
{thread_init_early(); // 初始化化lk线程上下文arch_early_init(); // 架构初始化,如关闭cache,使能mmuplatform_early_init(); // 平台早期初始化target_early_init(); //目标设备早期初始化bs_set_timestamp(BS_BL_START);call_constructors(); //静态构造函数初始化heap_init(); // 堆初始化thread_init(); // 初始化线程dpc_init();  //lk系统控制器初始化timer_init(); //kernel时钟初始化#if (!ENABLE_NANDWRITE)thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE)); // 创建一个线程初始化系统exit_critical_section(); //使能中断thread_become_idle(); //本线程切换为idle线程
#elsebootstrap_nandwrite();
#endif
}

arch_early_init()

因为高通平台用的arm架构,所以文件路径为:\bootable\bootloader\lk\arch\arm\arch.c.

void arch_early_init(void)
{arch_disable_cache(UCACHE); //关闭cacheset_vector_base(MEMBASE); // 设置异常向量基地址arm_mmu_init(); //初始化mmuarch_enable_cache(UCACHE); //打开cache/* enable cp10 and cp11 */__asm__ volatile("mrc    p15, 0, %0, c1, c0, 2" : "=r" (val));val |= (3<<22)|(3<<20);__asm__ volatile("mcr p15, 0, %0, c1, c0, 2" :: "r" (val));/* set enable bit in fpexc(中断相关寄存器) */__asm__ volatile("mrc  p10, 7, %0, c8, c0, 0" : "=r" (val));val |= (1<<30);__asm__ volatile("mcr  p10, 7, %0, c8, c0, 0" :: "r" (val));/* enable the cycle count register */__asm__ volatile("mrc    p15, 0, %0, c9, c12, 0" : "=r" (en));en &= ~(1<<3); /* cycle count every cycle */en |= 1; /* enable all performance counters */__asm__ volatile("mcr   p15, 0, %0, c9, c12, 0" :: "r" (en));/* enable cycle counter */en = (1<<31);__asm__ volatile("mcr    p15, 0, %0, c9, c12, 1" :: "r" (en));
}

platform_early_init

每个平台的初始化不一样,我使用的msm8916,路径为:\bootable\bootloader\lk\platform\msm8916\platform.c,如下:

void platform_early_init(void)
{board_init(); //主板初始化platform_clock_init(); //时钟初始化qgic_init();qtimer_init();
}

bootstrap2

此函数由kmain中创建的线程调用,路径为:bootable\bootloader\lk\kernel\main.c。

static int bootstrap2(void *arg)
{arch_init(); //架构初始化bio_init();fs_init();platform_init(); //平台初始化, 主要初始化系统时钟,超频等target_init(); //目标设备初始化,主要初始化flash,整合分区表等apps_init(); // 应用功能初始化,调用aboot_init,加载kernel等
}

aboot_init

此函数由上apps_init函数调用,路径: bootable\bootloader\lk\app\aboot\aboot.c.

/* Setup page size information for nv storage */if (target_is_emmc_boot()){page_size = mmc_page_size();page_mask = page_size - 1;}else{page_size = flash_page_size();page_mask = page_size - 1;}read_device_info(&device); //读取设备信息target_display_init(device.display_panel); // splash屏初始化target_serialno((unsigned char *) sn_buf); //设置串口号memset(display_panel_buf, '\0', MAX_PANEL_BUF_SIZE); //初始化显存if (is_user_force_reset()) // 检查关机原因,如果强制重启则正常启动goto normal_boot;if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN)) // 如果按下音量上下键{reboot_device(DLOAD); //重启进入紧急下载    if (!pm8x41_ponpon_pwrkey()) {//如果按下power键和音量键,进入fastboot模式,这很可能为自定义,Android源码没有boot_into_fastboot = true;}/*检查重启模式,并进入相应模式*/reboot_mode = check_reboot_mode();hard_reboot_mode = check_hard_reboot_mode();if (reboot_mode == RECOVERY_MODE ||hard_reboot_mode == RECOVERY_HARD_RESET_MODE) {boot_into_recovery = 1;} else if(reboot_mode == FASTBOOT_MODE ||hard_reboot_mode == FASTBOOT_HARD_RESET_MODE) {boot_into_fastboot = true;} else if(reboot_mode == ALARM_BOOT ||hard_reboot_mode == RTC_HARD_RESET_MODE) {boot_reason_alarm = true;}}normal_boot:if (!boot_into_fastboot){if (target_is_emmc_boot()){if(emmc_recovery_init()) //emmc_recovery初始化set_tamper_fuse_cmd();set_tamper_flag(device.is_tampered);boot_linux_from_mmc();}else {recovery_init();  // recovery模式初始化set_tamper_flag(device.is_tampered);}boot_linux_from_flash(); //从Flash中加载启动内核}/*不应该执行到这儿,只有没能正常启动时才会执行到这*/aboot_fastboot_register_commands(); // 注册fastboot命令,partition_dump(); //dump(即保存)分区表调试信息fastboot_init(target_get_scratch_address(), target_get_max_flash_size()); //初始化并进入fastboot
}

boot_linux_from_flash

路径: bootable\bootloader\lk\app\aboot\aboot.c。此函数实现内核的加载,boot镜像boot.img由如下几部分构成:kernel头、kernel、ramdisk(虚拟磁盘)、second stage(可以没有)。

if (target_is_emmc_boot()) {  // 如果目标设备是emmc boot(内嵌boot)hdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR; // 获取emmc boot镜像首地址goto continue_boot; // 继续启动}ptable = flash_get_ptable(); // 获取分区表if(!boot_into_recovery) //非recovery模式{ptn = ptable_find(ptable, "boot"); //获取boot分区}else{ptn = ptable_find(ptable, "recovery"); // 获取recovery分区}if (flash_read(ptn, offset, buf, page_size)) //获取boot镜像if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) //校验boot头if (hdr->page_size != page_size) //校验boot页大小/** update the kernel/ramdisk/tags address if the boot image header* has default values, these default values come from mkbootimg when* the boot image is flashed using fastboot flash:raw*/update_ker_tags_rdisk_addr(hdr); //读取boot image头,如有默认值则更新kernel、ramdisk、tag地址/* Get virtual addresses since the hdr saves physical addresses. *//* 根据物理地址获取虚拟地址 */hdr->kernel_addr = VA((addr_t)(hdr->kernel_addr));hdr->ramdisk_addr = VA((addr_t)(hdr->ramdisk_addr));hdr->tags_addr = VA((addr_t)(hdr->tags_addr));kernel_actual  = ROUND_TO_PAGE(hdr->kernel_size,  page_mask); //获取kernel实际地址ramdisk_actual = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask); // 获取ramdisk实际地址/* Check if the addresses in the header are valid. *//* 检查镜像头中的地址有效性*/if (check_aboot_addr_range_overlap(hdr->kernel_addr, kernel_actual) ||check_aboot_addr_range_overlap(hdr->ramdisk_addr, ramdisk_actual))
#ifndef DEVICE_TREE // 设备树,即dts\dtsi文件if (check_aboot_addr_range_overlap(hdr->tags_addr, MAX_TAGS_SIZE)) //检查设备树地址是否与aboot地址重合
#endif/* Authenticate Kernel *//* 鉴定内核 */if(target_use_signed_kernel() && (!device.is_unlocked)) //如果用签名kernel并且设备未被锁{image_addr = (unsigned char *)target_get_scratch_address();offset = 0;#if DEVICE_TREEdt_actual = ROUND_TO_PAGE(hdr->dt_size, page_mask);imagesize_actual = (page_size + kernel_actual + ramdisk_actual + dt_actual);//获取镜像实际地址if (check_aboot_addr_range_overlap(hdr->tags_addr, hdr->dt_size)) //检查设备树地址是否与aboot地址重合
#elseimagesize_actual = (page_size + kernel_actual + ramdisk_actual);
#endifbs_set_timestamp(BS_KERNEL_LOAD_START); // 开始加载boot镜像/* Read image without signature *//* 读取没有签名的镜像*/if (flash_read(ptn, offset, (void *)image_addr, imagesize_actual))bs_set_timestamp(BS_KERNEL_LOAD_DONE); //boot镜像加载完成offset = imagesize_actual;/* Read signature *//* 获取boot镜像签名 */if (flash_read(ptn, offset, (void *)(image_addr + offset), page_size))verify_signed_bootimg(image_addr, imagesize_actual);/* Move kernel and ramdisk to correct address *//* 移动kernel和ramdisk到正确地址*/memmove((void*) hdr->kernel_addr, (char *)(image_addr + page_size), hdr->kernel_size);memmove((void*) hdr->ramdisk_addr, (char *)(image_addr + page_size + kernel_actual), hdr->ramdisk_size);
#if DEVICE_TREE/* Validate and Read device device tree in the "tags_add *//* 校验并获取设备树*/if (check_aboot_addr_range_overlap(hdr->tags_addr, dt_entry.size))memmove((void*) hdr->tags_addr, (char *)(image_addr + page_size + kernel_actual + ramdisk_actual), hdr->dt_size);
#endif/* Make sure everything from scratch address is read before next step!*/if(device.is_tampered){write_device_info_flash(&device);}
#if USE_PCOM_SECBOOTset_tamper_flag(device.is_tampered);
#endif}else{offset = page_size;kernel_actual = ROUND_TO_PAGE(hdr->kernel_size, page_mask);ramdisk_actual = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);second_actual = ROUND_TO_PAGE(hdr->second_size, page_mask);bs_set_timestamp(BS_KERNEL_LOAD_START); // 开始加载boot镜像if (flash_read(ptn, offset, (void *)hdr->kernel_addr, kernel_actual)) // 获取boot镜像offset += kernel_actual;if (flash_read(ptn, offset, (void *)hdr->ramdisk_addr, ramdisk_actual)) //获取ramdisk镜像offset += ramdisk_actual;bs_set_timestamp(BS_KERNEL_LOAD_DONE); // 结束加载if(hdr->second_size != 0) {offset += second_actual;/* Second image loading not implemented. */ASSERT(0); //跳过第二镜像}#if DEVICE_TREEif(hdr->dt_size != 0) { // 如果设备文件存在/* Read the device tree table into buffer */           if(flash_read(ptn, offset, (void *) dt_buf, page_size))  // 读取设备树分区table = (struct dt_table*) dt_buf;if (dev_tree_validate(table, hdr->page_size, &dt_hdr_size) != 0) //校验设备树分区table = (struct dt_table*) memalign(CACHE_LINE, dt_hdr_size); // 获取内存地址/* Read the entire device tree table into buffer */if(flash_read(ptn, offset, (void *)table, dt_hdr_size)) // 读取设备树分区/* Find index of device tree within device tree table */if(dev_tree_get_entry_info(table, &dt_entry) != 0) //获取设备树地址/* Validate and Read device device tree in the "tags_add */if (check_aboot_addr_range_overlap(hdr->tags_addr, dt_entry.size)) // 校验设备树地址是否与aboot地址重合/* Read device device tree in the "tags_add */if(flash_read(ptn, offset + dt_entry.offset, (void *)hdr->tags_addr, dt_entry.size)) // 获取设备树}
#endif}
continue_boot:/* TODO: create/pass atags to kernel */boot_linux((void *)hdr->kernel_addr, (void *)hdr->tags_addr,(const char *)hdr->cmdline, board_machtype(),(void *)hdr->ramdisk_addr, hdr->ramdisk_size); //启动内核return 0;
}

因为时间有限,并没有详细去跟代码。很多细节都是大概扫了一下函数内容或者根据注释得出结果,如有错误,欢迎指出,共同学习,共同进步。

boot.img的头格式定义在:bootable\bootloader\lk\app\nandwrite\bootimg.h。如下:

struct boot_img_hdr
{unsigned char magic[BOOT_MAGIC_SIZE];unsigned kernel_size;  /* size in bytes */unsigned kernel_addr;  /* physical load addr */unsigned ramdisk_size; /* size in bytes */unsigned ramdisk_addr; /* physical load addr */unsigned second_size;  /* size in bytes */unsigned second_addr;  /* physical load addr */unsigned tags_addr;    /* physical addr for kernel tags */unsigned page_size;    /* flash page size we assume */unsigned unused[2];    /* future expansion: should be 0 */unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */unsigned char cmdline[BOOT_ARGS_SIZE];unsigned id[8]; /* timestamp / checksum / sha1 / etc */
};

当我们将编译生成的boot.img用文本编辑软件打开后,能看到boot_im_hdr格式定义的头,如下:

Android源码bootable解析之bootloader LK(little kernel)相关推荐

  1. 《Android源码设计模式解析与实战》读书笔记(十三)

    第十三章.备忘录模式 备忘录模式是一种行为模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态,有点像是我们平常说的"后悔药". 1.定义 在不破坏封闭的前提下,捕获 ...

  2. 《Android源码设计模式解析与实战》读书笔记(十七)

    第十七章.中介者模式 中介者模式也称为调解者模式或调停者模式,是一种行为型模式. 1.定义 中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用.从而使它们可以松散耦合.当某些对象之 ...

  3. 《Android源码设计模式解析与实战》读书笔记(十)

    第十章.解释器模式 解释器模式是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式.但是它的使用场景确实很广泛,只是因为我们自己很少回去构造一个语言的文法,所以使用较少. 1.定义 ...

  4. Android源码完全解析——View的Measure过程

    在Android中,Veiw从内存中到呈现在UI界面上需要经过measure(测量).layout(布局).draw(绘制)这样一个过程.为什么需要measure过程?因为在Android中View有 ...

  5. 《Android源码设计模式解析与实战》读书笔记(十二)

    第十二章.观察者模式 观察者模式是一个使用率非常高的模式,它最常用在GUI系统.订阅–发布系统.因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖. ...

  6. 《Android源码设计模式解析与实战》读书笔记(十四)

    第十四章.迭代器模式 迭代器模式,又叫做游标模式,是行为型设计模式之一.我们知道对容器对象的访问必然会涉及遍历算法,我们可以将遍历的方法封装在容器中,或者不提供遍历方法,让使用容器的人自己去实现去吧. ...

  7. 《Android源码设计模式解析与实战》读书笔记(二十四)

    第二十四章.桥接模式 桥接模式也称为桥梁模式,是结构型设计模式之一.桥接模式中体现了"单一职责原则"."开闭原则"."里氏替换原则".&qu ...

  8. 《Android源码设计模式解析与实战》读书笔记(二十一)

    第二十一章.装饰模式 装饰模式也称为包装模式,是结构型设计模式之一.装饰模式是一种用于替代继承技术的一种方案. 1.定义 动态的给一个对象添加一些额外的职责.就增加功能来说,装饰模式相比生成子类更为灵 ...

  9. 《Android源码设计模式解析与实战》读书笔记(十一)

    第十一章.命令模式 命令模式是行为型模式之一.总体来说并不难理解,只是比较繁琐,他会将简单的调用关系解耦成多个部分,增加类的复杂度,但是即便如此,命令模式的结构依然清晰. 1.定义 将一个请求封装成一 ...

最新文章

  1. 大战设计模式【23】—— 原型模式
  2. 算法与数据结构(python):递归
  3. SuperBenchmarker一个用.NET编写的压测工具
  4. c语言转换为python语言_python和c语言
  5. linux安装后启动mysql,linux安装完mysql后启动错误
  6. MySQL 对查询结果进行排序
  7. YOLOv5瓷砖表面瑕疵质检
  8. skimage 安装和子模块
  9. 谷歌浏览器上传下载奔溃问题解决方法
  10. matlab 的滤波器分析,基于MATLAB仿真软件对有源滤波器进行分析研究
  11. 三个线程顺序执行依次输出123123123.....
  12. 金惟纯《人生只有一件事》读书笔记
  13. 计算机等级考试三级信息安全知识整理
  14. android 安装APP缓存文件在哪,APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了...
  15. 有哪些大型网站是用 Python 开发的
  16. 敢创业但不敢上台演讲?
  17. 2022-2028年中国棉花采摘机行业市场调查研究及投资策略研究报告
  18. php-fpm 多核,linux 多核CPU性能调优
  19. 3个精美漂亮的wordpress律师网站模板
  20. cassandra install troubleshooting

热门文章

  1. 《五行斗》扑克牌游戏
  2. strcasecmp和strncasecmp
  3. Git之深入解析Git的杀手级特性·分支管理与分支变基的开发工作流以及远程分支的跟踪
  4. 成都一烧烤店起火,餐饮店及时配置燃气报警器
  5. 路由器虚拟dns服务器怎样使用,路由器dns服务器怎么设置
  6. Python 爬金十数据并生成词云
  7. 闲鱼服务器临时维护,做好闲鱼运营必知技巧,这就是你不会做的一些原因
  8. android 禁止app横屏(坚屏)方法(备忘)
  9. 软件经验|使用消费级无人机干测绘(三)Pix4Dmapper软件介绍
  10. 海外直播带货系统源码,海外的直播带货究竟是什么样?