高通Android设备启动流程分析(从power-on上电到Home Lanucher启动)

2019-04-13 15:23发布

本文为转载,转载地址:http://huaqianlee.github.io/2015/08/23/Android/%E9%AB%98%E9%80%9AAndroid%E8%AE%BE%E5%A4%87%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90-%E4%BB%8Epower-on%E4%B8%8A%E7%94%B5%E5%88%B0Home-Lanucher%E5%90%AF%E5%8A%A8/   Platform Information :
 System: Android5.1
 Platform: Qualcomm msm8916
 Author: Andy Lee
 Email: huaqianlee@gmail.com
如有错误欢迎指出,共同学习,共同进步
 
在我第一次接触Android得时候,我就很想知道 Android设备在按下电源键后是怎么启动到主界面的,但是到现在为止也没有完全理清这个过程,所以就决定从按下power键开始来分析一下这个流程。 虽然Android基于Linux内核开发的一个操作系统,但是在init进程后Android附加了很多其他操作,所以其启动流程还是有比较大的差别 的,关于Linux系统的启动流程可以参考我的另一篇博文:深入理解linux启动过程。 因为我现在工作中用到的是高通的源码,并且高通也是目前Android手机的主流芯片,所以我就按照高通的msm8916来分析了,不过其他的也应该大同小异。
 
首先来看一下官方给出的Android系统架构:
 
arch
 
当按下电源开关后,主要执行了如下步骤:
 
boot
 
另,在内核启动了第一个进程后,init->home lanucher的详细流程如下:
 
boot
注:此图取自网络,觉得描述得很详细,故附上
接下来就按照引导程序、内核启动、init进程、系统服务、Home Lanucher这样的顺序来分析Android启动的code。

引导程序

引导程序在Android操作系统开始运行前的一个小程序,其主要为内核启动服务。引导程序执行的 第一段代码,因此它是针对特定的主板与芯片的。设备制造商要么使用很受欢迎的引导程序比如redboot、uboot或者开发自己的引导程序,它不是 Android操作系统的一部分。引导程序是OEM厂商或者运营商加锁和限制的地方。 引导程序分两个阶段执行。第一个阶段,检测外部的RAM以及加载对第二阶段有用的程序;第二阶段,引导程序设置网络、内存等等。这些对于运行内核是必要的,为了达到特殊的目标,引导程序可以根据配置参数或者输入数据设置内核。

power-on及系统启动

当按下电源键或者系统重启之后,引导芯片代码 PBL(Primary Boot Loader,类似于x86的BIOS)从预定义的地方(固化在ROM)开始执行,PBL由高通做好了的烧写在芯片中,PBL将启动设备、支持紧急下载 等,然后加载引导程序sbl1,然后跳转到sbl1执行。

处理器启动地址

MSM8916芯片内部有很多不同的处理器,如下: 子系统 处理器 启动地址 APPS Cortex-a53 0xfc010000 RPM Cortex-m3 0x00200000/0x0 Modem MSS_QDSP6 可配置的 Pronto ARM9TM 0x0/0xffff0000/硬件重映射

启动栈

组件 处理器 加载源地址 执行地址 功能 APPS PBL Cortex-A53 NA APPS ROM 启动设备,检测接口,支持紧急下载,通过L2TCM加载和校验SBL1 ELF段,加载校验RPM code RAM SBL1 Cortex-A53 eMMC L2 TCM(segment1)/OCIMEM/RPM code RAM(segment2) 初始化内存子系统(总线,DDR,时钟,CDT),加载校验TZ、Hyperviser、 RPM_FW、APPSBL镜像,通过USB2.0和Sahara协议memory dump,看门狗调试retention(如:L2 flush),RAM dump到eMMC/SD卡等的支持,大容量存储支持,USB驱动支持,USB充电,温度检测,PMIC驱动的支持,配置DDR以及crash调试的 flush L1/L2/ETB支持等相关配置 QSEE/TZ Cortex-A53 eMMC LPDDR2/3 等同于TZBSP,设置运行时安全环境,配置xPU,支持fuse驱动,校验子系统镜像,丢弃RESET调试功能 QHEE(Hypervisior) Cortex-A53 eMMC LPDDR2/3 Hypervisor镜像负责设置VMM,配置SMMU以及控制xPU存取 RPM_FW Cortex-M3 eMMC RPM code RAM 电源资源管理 APPSBL/启动管理器和系统加载器 Cortex-A53 eMMC LPDDR2/3 启动画面,加载校验内核 HLOS Cortex-A53 eMMC LPDDR2/3 引导HLOS镜像,例如a53 HLOS内核镜像,Pronto镜像等 Modem PBL MSS_QDSP6 NA Modem ROM HexagonTM TCM 设置Hexagon TCM,从LPDDR2/3拷贝MBA到Hexagon TCM并校验 MBA MSS_QDSP6 eMMC Hexagon TCM 校验modem镜像,xPU为modem和memory dump保护DDR
eMMC :Embeded Multi Media Card,内嵌式记忆体,内部存储
APPS PBL:Application Processor Primary Boot Loader,应用处理器初级引导程序
SBL1:Secondary Boot Loader Stage1,第二引导程序阶段一(此处写阶段一是因为早期高通芯片分为几个阶段,但现在都由sbl1实现)
TZ:TrustZone
PRM_FW:Resource Power Manager Firmware,电源资源管理固件
HLOS:High-Level Operating System,高级操作系统
Modem PBL:Modem Primary Boot Loader,调制解调器侧初级引导程序
MBA:Modem Boot Authenticator,调制解调器侧引导校验程序

引导代码流程

  1. 系统上电或者MSM8916 AP侧CPU重启。
  2. Cortex-A53中APPS PBL执行,从启动设备中加载校验是sbl1镜像,然后跳转到sbl1中执行。
  3. sbl1初始化ddr,从启动设备中加载校验QSEE/TZ、QHEE、RPM_FW、APPSBL镜像到DDR。
  4. sbl1将控制权给QSEE/TZ,QSEE/TZ将设置一个安全环境,配置xPU,并支持fuse驱动。
  5. QSEE传递控制权给QHEE,QHEE负责设置VMM,配置SMMU和xPU存取控制。
  6. QHEE通知RPM开始执行RPM固件。
  7. QHEE将控制器传递给HLOS APPSBL,APPSBL将初始化系统。
  8. HLOS APPSBL加载和校验HLOS内核。
  9. HLOS内核通过PIL.Modem加载MBA和modem镜像到DDR,然后继续启动进程。
  10. HLOS通过PIL加载外围设备镜像Pronto到DDR,在通过TZ校验。

第一阶段引导程序和第二阶段引导程序

由PBL加载的sbl1是第一阶段引导程序,APP SBL为第二阶段引导程序。这两部分代码的作用在上面启动栈引导代码流程中已有一个简单的描述,如果想了解更多请参考我另外两篇博文: Android源码bootable解析之bootloader LK(little kernel)
高通平台Android源码bootloader分析之sbl1(一)

内核

Android的内核就是用的Linux的内核,只是针对移动设备做了一些优化,所有Android内核与 linux内核启动的方式差不多。内核主要设置缓存、被保护存储器、计划列表,加载驱动等。当内核完成这些系统设置后,它首先在系统文件中寻 找”init”文件,然后启动root进程或者系统的第一个进程。这部分可以参考我的另一篇博文: 深入理解linux启动过程

init进程

init进程时Android的第一个用户空间进程,是所有进程的父进程。init进程主要有两个任务,一是挂载目录,比如/sys、/dev、/proc,二是读取解析init.rc脚本,将其中的元素整理成自己的数据结构(链表)。 init进程实现路径: systemcoreinit

init.c

首先来看一下init进程的实现代码init.c, 其关键代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 # systemcoreinitinit.c int main(int argc, char **argv) { ... /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); ... property_init(); // 初始化属性服务,主要为属性文件分配存储空间 get_hardware_name(hardware, &revision); // 从虚拟文件/proc/cpuinfo中获取hardware及revision,后面init.rc中的hardware变量值从此获取 process_kernel_cmdline(); // 导入命令行参数并用属性值设置内核变量, /proc/cmdline ... selinux_initialize();// 初始化selinux安全机制 init_parse_config_file("/init.rc"); // 解析init.rc文件,主要生成action和service链表 /* 解析完init.rc配置文件后,会得到一系列的Action,action_for_each_trigger函数用来将Action加入action_queue,有关init.rc、action等内容下面再分析*/ action_for_each_trigger("early-init", action_add_queue_tail); // 添加“early-init”action queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); // 添加“init”action queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(property_service_init_action, "property_service_init"); // 启动属性服务 queue_builtin_action(signal_init_action, "signal_init"); /* Don't mount filesystems or start core system services if in charger mode. */ if (is_charger) { action_for_each_trigger("charger", action_add_queue_tail); } else { if (is_ffbm) { action_for_each_trigger("ffbm", action_add_queue_tail); } else { action_for_each_trigger("late-init", action_add_queue_tail); // 添加“late-init”action } } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); for(;;) { // 无限循环,建立init子进程 ... execute_one_command(); // 执行节点command,zygote service也在此启动,稍后再详细分析 restart_processes(); // 重启进程 # 监听属性服务事件 ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; // 属性事件 ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].events = POLLIN; // 子进程事件 ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].events = POLLIN; // keychord热键事件 ... #if BOOTCHART // bootchart是一个性能统计工具,用于搜集硬件和系统的信息,并将其写入磁盘,以便其他程序使用 #endif nr = poll(ufds, fd_count, timeout); // 等待下一个命令提交 # 处理具体消息 handle_property_set_fd(); // 处理属性命令 handle_keychord(); // adb使能时处理keychord handle_signal(); // 处理子进程挂掉发来的信号,service重启 ... }  

init.rc

.rc文件的语法 init.rc文件是Android的有特定格式和规则的脚本文件,位于:systemcore ootdirinit.rc,称为Android的初始化语言。当进入adb shell后,我们能在根目录看到一个只读的虚拟内存文件init.rc,源文件init.rc被打包在boot.img中ramdisk.img中。其有四类声明:
  1. Action - 动作
  2. Command - 命令
  3. Service - 服务
  4. Option - 选项
该语言规定,Action和Service是以一种“小节”(Section)的形式出现的,其中每个Action小节可以含有若干Command,而每个Service小节可以含有若干Option。小节只有起始标记,却没有明确的结束标记,也就是说,是用“后一个小节”的起始来结束“前一个小节”的。 脚本中的Action大体上表示一个“动作”,它用一系列Command共同完成该“动作”。Action需要有一个触发器(trigger)来触发它,一旦满足了触发条件,这个Action就会被加到执行队列的末尾。Action的形式如下: 1 2 3 4 on ......   Service表示一个服务程序,会在初始化时启动,当服务退出时init进程会视情况重启服务。因为init.rc脚本中描述的服务往往都是核心服务,所以(基本上所有的)服务会在退出时自动重启。Service的形式如下: 1 2 3 4 service []*