ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入剖析安卓的开机从BootLoader到Zygote整个过程

深入剖析安卓的开机从BootLoader到Zygote整个过程 1. Android开机过程要想进行开机速度的优化首先需要了解开机的详细流程。开机过程从CPU上电开始到锁屏界面显示出来结束。下图为比较简洁的开机流程图。boot_simple.png再来看一张比较详细的开机流程图boot_flow_all_3.png总的来说开机过程分为以下六主要子过程1.1 Boot ROMBoot ROM是硬编码在CPU内部固定地址的一段ROM(在一些较老的系统上也可能使用外挂Boot ROM相对CPU来说)这块代码是由CPU制造商提供。当用户按下电源键或者系统重启之后触发CPU上电动作此时其它硬件还未初始化然而这块ROM就已经可读了。CPU首先执行PBL(Primary Boot Loader主引导加载程序固化在ROM上)代码。在必要的硬件初始化之后Boot ROM开始加载Bootloader到RAM中然后PC指针跳过去执行bootloader。在加载 Bootloader之前PBL也可以进行验证。如果验证无法通过则不会加载运行Bootloader从而开机失败。Android_boot_1_boot_ROM.pngA. CPU刚上电时CPU 处于未初始化状态还没有设定内部时钟此时只有内部 RAM 可用。当电源稳定后会开始执行 Boot ROM 代码。Boot ROM通过系统寄存器映射到 ASIC (Application Specific Integrated Circuit, 即专用集成电路是指应特定用户要求和特定电子系统的需要而设计、制造的集成电路)中的物理区域来找到boot media进而可以找到BootloaderB. boot media序列确定之后Boot ROM 加载 Bootloader到内部 RAM 中之后Boot ROM代码会跳到Bootloader1.2 BootloaderBootloader是一个特殊的独立于内核的程序是CPU复位后进入操作系统之前执行的一段代码。Bootloader完成由硬件启动到操作系统启动的过渡从而为操作系统提供基本的运行环境如初始化CPU、时钟、堆栈、存储器系统等。Bootloader功能类似于PC机的BIOS程序,其代码与CPU芯片的内核结构、具体型号、应用系统的配置及使用的操作系统等因素有关因此不可能有通用的bootloader,开发时需要用户根据具体情况进行移植。嵌入式Linux系统中常用的Bootloader有armboot、redboot、blob、U-Boot、Bios-lt、Bootldr等其中U-Boot是当前比较流行功能比较强大的Bootloader可以支持多种体系结构但相对也比较复杂。硬件初始化完成之后Bootloader将boot.img(kernel ramdisk(ramdisk.img中主要是存放android启动后第一个用户进程init可执行文件和init.*.rc等相关启动脚本以及sbin目录下的adbd工具))从flash上copy到RAM里面然后CPU执行转向kernel。Android_boot_2_bootloader.pngA. Bootloader第一阶段首先会检测和设置外部RAMB. 外部 RAM可用之后将Bootloader代码加载到外部RAM中C. Bootloader第二阶段包含了设置文件系统内存网络等等。D. Bootloader查找Linux内核并将其从boot media (或者其他地方这取决于系统配置) 加载到 RAM 中并且会配置一些内核启动时需要的启动参数E. Bootloader执行完之后会跳转到 Linux 内核执行一般也可将Bootloader程序的执行分为两个阶段如下图所示bootloader_1.png执行Bootloader程序过程中如果镜像验证失败、BootLinux (Info) 函数启动失败或者接收到启动至 fastboot 的命令比如使用 adb reboot bootloader进行重启、在启动时按下了电源键下音量键组合时会进入到Fastboot模式(Fastboot 是一种电脑通过USB数据线对手机固件进行刷写、擦除/格式化、调试、传输各种指令的固件通信协议, 俗称线刷模式或快速引导模式)。1.3 KernelAndroid kernel基于上游 Linux LTS (Linux Long Term Supported长期支持) 内核。在 GoogleLTS 内核会与 Android 专用补丁结合形成所谓的“Android 通用内核 (ACKAndroid Common Kernel)”。较新的 ACK版本 5.4 及更高版本也称为 GKI (Generic Kernel Image通用内核镜像 )内核。 GKI项目通过统一核心内核并将 SoC 和板级支持从核心内核移至可加载模块中解决了内核碎片化问题。GKI 内核为内核模块提供了稳定的内核模块接口 (KMI)因此模块和内核可以独立进行更新。GKI 具有以下特点基于 ACK 来源构建而成。是每个架构和每个 LTS 版本的单内核二进制文件以及关联的可加载模块目前只有适用于 android11-5.4 和 android12-5.4 的 arm64。已经过关联 ACK 支持的所有 Android 平台版本的测试。在 GKI 内核版本的生命周期内不会发生功能弃用。为给定 LTS 中的驱动程序提供了稳定版 KMI。不包含 SoC 专用代码或板卡专用代码。下图显示了 GKI 内核和供应商模块架构generic-kernel-image-architecture.png由于Android的kernel实际上就是Linux kernel只是针对移动设备做了一些优化所以与其它Linux kernel的启动方式大同小异都是对start_kernel函数的调用和执行。Kernel主要工作内容为设置缓存、被保护存储器、计划列表加载驱动启动kernel守护挂载根目录初始化输入输出开启中断初始化进程表等。当内核完成这些系统设置后接下来在系统文件中寻找”init”文件然后启动root进程或者系统的第一个进程。Kernel启动过程分为两个阶段1内核引导阶段。通常使用汇编语言编写主要检查内核与当前硬件是否匹配。这部分也与硬件体系结构相关。2内核启动阶段。引导阶段结束前将调用start_kernel()进入内核启动阶段。内核启动阶段相关的代码主要位于kernel/init/main.c。A. 内存管理单元和高速缓存初始化完成之后系统便可以使用虚拟内存和启动用户空间进程B. 内核在根目录寻找初始化程序/system/core/init执行该程序以启动init进程Kernel启动的核心函数是start_kernel函数它完成了内核的大部分初始化工作。这个函数在最后调用了reset_init函数进行后续的初始化。reset_init函数最主要的任务就是启动内核线程kernel_init。kernel_init函数将完成设备驱动程序的初始化并调用init_post函数启动用户空间的init进程。到init_post函数为止内核的初始化已经基本完成。boot_kernel.png1.4 init进程用户空间的第一个进程便是init进程进程号为1。当系统启动完成之后init进程会作为守护进程监视其它进程。在Linux中所有的进程都是由init进程直接或间接fork出来的。在init进程启动的过程中会相继启动servicemanager(binder服务管理者)、Zygote进程。而Zygote又会创建system_server进程以及app进程。对于init进程的功能分为4部分解析并运行所有的init.rc相关文件根据rc文件生成相应的设备驱动节点处理子进程的终止(signal方式)提供属性服务的功能init进程涉及的主要代码文件有system/core/init/-main.cpp-init.cpp-parser.cpp/system/core/rootdir/-init.rcinit进程的入口为main.cpp类的main方法。// system/core/init/main.cppintmain(intargc,char**argv){#if__has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);#endif// 创建设备节点、权限设定等if(!strcmp(basename(argv[0]),ueventd)){returnueventd_main(argc,argv);}if(argc1){// 初始化日志系统if(!strcmp(argv[1],subcontext)){android::base::InitLogging(argv,android::base::KernelLogger);constBuiltinFunctionMap function_map;returnSubcontextMain(argc,argv,function_map);}// 2.创建安全增强型LinuxSELinuxif(!strcmp(argv[1],selinux_setup)){returnSetupSelinux(argv);}// 3.解析init.rc文件、提供服务、创建epoll与处理子进程的终止等if(!strcmp(argv[1],second_stage)){returnSecondStageMain(argc,argv);}}// 1.挂载相关文件系统returnFirstStageMain(argc,argv);}主要执行了三步FirstStageMainSetupSelinuxSecondStageMainFirstStageMaininit进程启动的第一步主要是挂载相关的文件系统// system/core/init/first_stage_init.cppintFirstStageMain(intargc,char**argv){if(REBOOT_BOOTLOADER_ON_PANIC){InstallRebootSignalHandlers();}boot_clock::time_point start_timeboot_clock::now();std::vectorstd::pairstd::string,interrors;#defineCHECKCALL(x)\if(x!0)errors.emplace_back(#x failed,errno);umask(0);// 创建于挂载相关文件系统CHECKCALL(clearenv());CHECKCALL(setenv(PATH,_PATH_DEFPATH,1));// Get the basic filesystem setup we need put together in the initramdisk// on / and then well let the rc file figure out the rest.CHECKCALL(mount(tmpfs,/dev,tmpfs,MS_NOSUID,mode0755));CHECKCALL(mkdir(/dev/pts,0755));CHECKCALL(mkdir(/dev/socket,0755));CHECKCALL(mount(devpts,/dev/pts,devpts,0,NULL));#defineMAKE_STR(x)__STRING(x)CHECKCALL(mount(proc,/proc,proc,0,hidepid2,gidMAKE_STR(AID_READPROC)));#undefMAKE_STR// 原始命令不可暴露给没有特权的进程CHECKCALL(chmod(/proc/cmdline,0440));gid_t groups[]{AID_READPROC};CHECKCALL(setgroups(arraysize(groups),groups));CHECKCALL(mount(sysfs,/sys,sysfs,0,NULL));CHECKCALL(mount(selinuxfs,/sys/fs/selinux,selinuxfs,0,NULL));// tmpfs已经挂载在/dev下并且已生成/dev/kmsg故可以与外界通信// 初始化日志系统InitKernelLogging(argv);// 进入下一步constchar*path/system/bin/init;constchar*args[]{path,selinux_setup,nullptr};execv(path,const_castchar**(args));// 只有在错误发生的情况下execv()函数才会返回PLOG(FATAL)execv(\path\) failed;return1;}主要通过mount挂载对应的文件系统mkdir创建对应的文件目录并配置相应的访问权限。需要注意的是这些文件只是在应用运行的时候存在一旦应用运行结束就会随着应用一起消失。挂载的文件系统主要有四类tmpfs: 一种虚拟内存文件系统它会将所有的文件存储在虚拟内存中。由于tmpfs是驻留在RAM的因此它的内容是不持久的。断电后tmpfs 的内容就消失了这也是被称作tmpfs的根本原因。devpts: 为伪终端提供了一个标准接口它的标准挂接点是/dev/pts。只要pty(pseudo-tty, 虚拟终端)的主复合设备/dev/ptmx被打开就会在/dev/pts下动态的创建一个新的pty设备文件。proc: 也是一个虚拟文件系统它可以看作是内核内部数据结构的接口通过它我们可以获得系统的信息同时也能够在运行时修改特定的内核参数。sysfs: 与proc文件系统类似也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。在FirstStageMain还会通过InitKernelLogging(argv)来初始化log日志系统。此时Android还没有自己的系统日志采用kernel的log系统打开的设备节点/dev/kmsg 那么可通过cat /dev/kmsg来获取内核log。最后会通过execv方法传递对应的path与下一阶段的参数selinux_setup。SetupSelinux// system/core/init/selinux.cppintSetupSelinux(char**argv){//初始化本阶段内核日志InitKernelLogging(argv);if(REBOOT_BOOTLOADER_ON_PANIC){InstallRebootSignalHandlers();}// 初始化 SELinux加载 SELinux 策略SelinuxSetupKernelLogging();SelinuxInitialize();// 再次调用 main 函数并传入 second_stage 进入第二阶段// 而且此次启动就已经在 SELinux 上下文中运行if(selinux_android_restorecon(/system/bin/init,0)-1){PLOG(FATAL)restorecon failed of /system/bin/init failed;}// 进入下一步constchar*path/system/bin/init;constchar*args[]{path,second_stage,nullptr};execv(path,const_castchar**(args));// execv() only returns if an error happened, in which case we// panic and never return from this function.PLOG(FATAL)execv(\path\) failed;return1;}这阶段主要是初始化 SELinux。SELinux 是安全加强型 Linux能够很好的对全部进程强制执行访问控制从而让 Android 更好的保护和限制系统服务、控制对应用数据和系统日志的访问提高系统安全性。接下来调用execv进入到最后阶段SecondStageMain。SecondStageMain// system/core/init/init.cppintSecondStageMain(intargc,char**argv){SetStdioToDevNull(argv);// 初始化本阶段内核日志InitKernelLogging(argv);// 系统属性初始化property_init();// 建立 EpollEpoll epoll;// 注册信号处理InstallSignalFdHandler(epoll);// 加载默认的系统属性property_load_boot_defaults(load_debug_prop);// 启动属性服务StartPropertyService(epoll);subcontextsInitializeSubcontexts();//加载系统启动脚本/init.rcActionManageramActionManager::GetInstance();ServiceListsmServiceList::GetInstance();LoadBootScripts(am,sm);// 触发early-initinitlate-init流程am.QueueEventTrigger(early-init);am.QueueEventTrigger(init);am.QueueBuiltinAction(InitBinder,InitBinder);am.QueueEventTrigger(late-init);//解析启动脚本while(true){// 执行 Actionam.ExecuteOneCommand();// 还有就是重启死掉的子进程autonext_process_action_timeHandleProcessActions();}}SecondStageMain的主要工作总结使用epoll对init子进程的信号进行监听初始化系统属性使用mmap共享内存开启属性服务并注册到epoll中加载系统启动脚本init.rc解析启动脚本启动相关服务重点介绍下init.rc文件的解析//system/core/init/init.cppstaticvoidLoadBootScripts(ActionManageraction_manager,ServiceListservice_list){Parser parserCreateParser(action_manager,service_list);std::string bootscriptGetProperty(ro.boot.init_rc,);if(bootscript.empty()){parser.ParseConfig(/init.rc);if(!parser.ParseConfig(/system/etc/init)){late_import_paths.emplace_back(/system/etc/init);}if(!parser.ParseConfig(/product/etc/init)){late_import_paths.emplace_back(/product/etc/init);}if(!parser.ParseConfig(/product_services/etc/init)){late_import_paths.emplace_back(/product_services/etc/init);}if(!parser.ParseConfig(/odm/etc/init)){late_import_paths.emplace_back(/odm/etc/init);}if(!parser.ParseConfig(/vendor/etc/init)){late_import_paths.emplace_back(/vendor/etc/init);}}else{parser.ParseConfig(bootscript);}}通过ParseConfig来解析init.rc配置文件。.rc文件以行为单位以空格为间隔以#开始代表注释行。.rc文件主要包含Action、Service、Command、Options、Import其中对于Action和Service的名称都是唯一的对于重复的命名视为无效。init.rc中的Action、Service语句都有相应的类来解析即ActionParser、ServiceParser。 以下为init.rc配置文件的部分内容。// system/core/rootdir/init.rcimport/init.environ.rcimport/init.usb.rcimport/init.${ro.hardware}.rcimport/init.${ro.zygote}.rcimport/init.trace.rc on early-init start ueventdmkdir/mnt 0775 root system on initmounttmpfs none /sys/fs/cgroupmode0750,uid0,gid1000mkdir/sys/fs/cgroup/memory 0750 root systemmountcgroup none /sys/fs/cgroup/memory memory on property:sys.boot_from_charger_mode1class_stop charger trigger late-initserviceueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0servicelogd /system/bin/logd class core socket logd stream 0666 logd logd socket logdr seqpacket 0666 logd logd socket logdw dgram 0222 logd logd seclabel u:r:logd:s0serviceconsole /system/bin/sh class core console disabled user shell seclabel u:r:shell:s0serviceadbd /sbin/adbd--root_seclabelu:r:su:s0 class core socket adbd stream660system system disabled seclabel u:r:adbd:s0serviceservicemanager /system/bin/servicemanager class core user system group system critical onrestart restart healthd onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm on late-init trigger early-fs trigger fs trigger post-fs trigger late-fs trigger post-fs-data trigger load_persist_props_action // 这里启动zygote-start trigger zygote-start trigger firmware_mounts_complete trigger early-boot trigger boot可以看到在解析init.rc的配置中在late-init阶段启动了Zygote进程。原文地址https://mp.weixin.qq.com/s/Hp0zx1UMAnm7GJISTnNFaQ更多fw实战开发干货请关注下面“千里马学框架”
返回列表