![Android进阶解密](https://wfqqreader-1252317822.image.myqcloud.com/cover/331/31186331/b_31186331.jpg)
2.1 init进程启动过程
init进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动流程中一个关键的步骤,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建Zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init中。
2.1.1 引入init进程
为了讲解init进程,首先要了解Android系统启动流程的前几步,以引入init进程。
1.启动电源以及系统启动
当电源按下时引导芯片代码从预定义的地方(固化在ROM)开始执行。加载引导程序BootLoader到RAM中,然后执行。
2.引导程序BootLoader
引导程序BootLoader是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
3.Linux内核启动
当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。在内核完成系统设置后,它首先在系统文件中寻找init.rc文件,并启动init进程。
4.init进程启动
init进程做的工作比较多,主要用来初始化和启动属性服务,也用来启动Zygote进程。
从上面的步骤可以看出,当我们按下启动电源时,系统启动后会加载引导程序,引导程序又启动Linux 内核,在Linux 内核加载完成后,第一件事就是要启动init 进程。关于Android系统启动的完整流程会在本章的2.5节进行讲解,这一节的任务就是先了解init进程的启动过程。
2.1.2 init进程的入口函数
在Linux内核加载完成后,它首先在系统文件中寻找init.rc文件,并启动init进程,然后查看init进程的入口函数main,代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer380.jpg?sign=1739226410-lAwHAU8GAkDoXwG2Yw8OmmC4FS2HdHwa-0-9a09890d8f73d8a92fedd8e962127c88)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer396.jpg?sign=1739226410-iv9l7Mt37yzDVAKgHKSMmVsd6dNLNtCW-0-60032d2a72bdd609e4d6cdc1a6475944)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer418.jpg?sign=1739226410-eiN5L3xKgN9b2FLLlRbJRw3KLVU2iFM7-0-ccc5752af05fa666134a2c5642d05c27)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer440.jpg?sign=1739226410-qvVy6UJhbYupLtLgio0wO2Du40rx4pHS-0-3d73a45c1848bb4937e7290a1b04f8a2)
init的main函数做了很多事情,比较复杂,我们只需关注主要的几点就可以了。在开始的时候创建和挂载启动所需的文件目录,其中挂载了tmpfs、devpts、proc、sysfs和selinuxfs共5种文件系统,这些都是系统运行时目录,顾名思义,只在系统运行时才会存在,系统停止时会消失。
在注释1处调用property_init函数来对属性进行初始化,并在注释3处调用start_property_service函数启动属性服务,关于属性服务,后面会讲到。在注释2处调用signal_handler_init 函数用于设置子进程信号处理函数,它被定义在system/core/init/signal_handler.cpp中,主要用于防止init进程的子进程成为僵尸进程,为了防止僵尸进程的出现,系统会在子进程暂停和终止的时候发出SIGCHLD信号,而signal_handler_init函数就是用来接收SIGCHLD信号的(其内部只处理进程终止的SIGCHLD信号)。
假设init进程的子进程Zygote终止了,signal_handler_init函数内部会调用handle_signal函数,经过层层的函数调用和处理,最终会找到Zygote进程并移除所有的Zygote进程的信息,再重启Zygote服务的启动脚本(比如init.zygote64.rc)中带有onrestart选项的服务,关于init.zygote64.rc后面会讲到,至于Zygote进程本身会在注释5处被重启。这里只是拿Zygote进程举个例子,其他init进程子进程的原理也是类似的。
注释4处用来解析init.rc文件,解析init.rc的文件为system/core/init/init_parse.cpp文件,接下来我们查看init.rc里做了什么。
僵尸进程与危害
在UNIX/Linux中,父进程使用fork创建子进程,在子进程终止之后,如果父进程并不知道子进程已经终止了,这时子进程虽然已经退出了,但是在系统进程表中还为它保留了一定的信息(比如进程号、退出状态、运行时间等),这个子进程就被称作僵尸进程。系统进程表是一项有限资源,如果系统进程表被僵尸进程耗尽的话,系统就可能无法创建新的进程了。
2.1.3 解析init.rc
init.rc是一个非常重要的配置文件,它是由Android初始化语言(Android Init Language)编写的脚本,这种语言主要包含5种类型语句:Action、Command、Service、Option和Import。init.rc的配置代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer460.jpg?sign=1739226410-DsqfeWUr68yeUlxHReCChC5h5AMXupjl-0-e900c0e9d9bf2bc5e16e8724cf666a98)
这里只截取了一部分代码。on init和on boot是Action类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer461.jpg?sign=1739226410-ZxqBayAMl7vFHj5pQ7zi6QaUnWfiI8BD-0-df6e1d0e33beeb8bdb4d05cd8adc434e)
为了分析如何创建Zygote,我们主要查看Service类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer462.jpg?sign=1739226410-0CPJJNaySLKy5ghjNu86xja69pWZvPhp-0-af38c88cbf05b396a378c8ac75ac3732)
需要注意的是,在Android 8.0中对init.rc文件进行了拆分,每个服务对应一个rc文件。我们要分析的Zygote启动脚本则在init.zygoteXX.rc中定义,这里拿64位处理器为例,init.zygote64.rc的代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer463.jpg?sign=1739226410-aXDj4OzR3sQw5iM2HqYxuV2OBJFWRYLN-0-4e5bfe04a9816ca31ab7185a5329d43b)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer477.jpg?sign=1739226410-NuXzzHg0m5u7L1VtjDO4lFzTEMwzfkT5-0-8a3d54d620610e6a7b4e265daed03477)
根据Service类型语句的格式我们来大概分析上面代码的意思。Service 用于通知init进程创建名为zygote的进程,这个进程执行程序的路径为/system/bin/app_process64①,其后面的代码是要传给app_process64的参数。class main指的是Zygote的classname为main②,后面会用到它。关于Zygote启动脚本会在本章的2.2.2节进行详细介绍。(此处标注的①、②,后续内容会引用到。)
2.1.4 解析Service类型语句
init.rc中的Action类型语句和Service类型语句都有相应的类来进行解析,Action类型语句采用ActionParser来进行解析,Service 类型语句采用ServiceParser来进行解析,这里因为主要分析Zygote,所以只介绍ServiceParser。ServiceParser的实现代码在system/core/init/service.cpp中,接下来我们来查看ServiceParser是如何解析上面提到的Service类型语句的,会用到两个函数:一个是ParseSection,它会解析Service的rc文件,比如上文讲到的init.zygote64.rc,ParseSection函数主要用来搭建Service的架子;另一个是ParseLineSection,用于解析子项。代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer478.jpg?sign=1739226410-AGJc0rmGm30dSWt5ejBINQVRuFYi7oRR-0-613f7ed87ef3d9db80aa6c684399e607)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer503.jpg?sign=1739226410-enJimDw1TRqdnJae0AUEVpKfn5sBLbGJ-0-d652fc594d8eb7807de897e636f90966)
注释1处,根据参数,构造出一个Service对象,它的classname为default。在解析完所有数据后,会调用EndSection函数:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer504.jpg?sign=1739226410-EpmxTkcB52te7AuqIGPecMsZ9pe2Og8h-0-d497b94541c52f6e15d77e124bd89812)
EndSection函数中会调用ServiceManager的AddService函数,接着查看AddService函数做了什么:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer505.jpg?sign=1739226410-H0rOtfjIpdMEJ3LkdOFv6mxm3HkEaTa7-0-1eb20f1d3c80771d81eec74829c4a8f9)
注释1处的代码将Service对象加入Service链表中。上面的Service解析过程总体来讲就是根据参数创建出Service对象,然后根据选项域的内容填充Service对象,最后将Service对象加入vector类型的Service链表中。
2.1.5 init启动Zygote
讲完了解析Service,接下来该讲init是如何启动Service的,在这里主要讲解启动Zygote这个Service。在Zygote 的启动脚本中,我们可知Zygote 的classname 为main。在init.rc中有如下配置代码:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer518.jpg?sign=1739226410-iCeHSBVG902ieSzMr45AmQtw2qqGrK5q-0-910b7a4d315361cffd278317eee88b07)
其中class_start是一个COMMAND,对应的函数为do_class_start。注释1处启动那些classname为main的Service,从2.1.3节末段的标注②处,我们知道Zygote 的classname就是main,因此class_start main是用来启动Zygote的。do_class_start函数在builtins.cpp中定义,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer519.jpg?sign=1739226410-aGnHvtLkHUZFesvYDkrHw4GzVgA6P7Y9-0-6870d9a34b72abc93b95d80c853f0bae)
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer520.jpg?sign=1739226410-Cnciu7GAzdHj04ijZLpXppNYBKd6utub-0-0117dc8b1305e7a3e5a6ebeed92f6b6a)
注释1处,如果Service没有在其对应的rc文件中设置disabled选项,则会调用Start函数启动该Service,Zygote对应的init.zygote64.rc中并没有设置disabled选项,因此我们接着来查看Start函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer521.jpg?sign=1739226410-UoYHo0wkcDOZH6LpXIXW8ZLAXl1V0rCc-0-06f13077fd3166e4175acee58be03e64)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer544.jpg?sign=1739226410-ncSW7lwwIdkvBBb3LaMcgxSr7a21mGcr-0-2ab2db33f3fedbad44e942ed8c8723cb)
首先判断Service是否已经运行,如果运行则不再启动,直接返回false。如果程序走到注释1处,说明子进程还没有被启动,就调用fork函数创建子进程,并返回pid值,注释2处如果pid值为0,则说明当前代码逻辑在子进程中运行。注释3处在子进程中调用execve函数,Service子进程就会被启动,并进入该Service的main函数中,如果该Service是Zygote,从2.1.3节末段的标注①处我们可知Zygote执行程序的路径为/system/bin/app_process64,对应的文件为app_main.cpp,这样就会进入app_main.cpp的main函数中,也就是在Zygote的main函数中,代码如下:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer563.jpg?sign=1739226410-zGtA3tVHQo1iSNGvU8nXVNfCzgLnXnCa-0-ca831a39c90e17df4cf92da82bd6b8bb)
从注释1处的代码可以得知调用runtime的start函数启动Zygote,至此Zygote就启动了。
2.1.6 属性服务
Windows 平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,其还是能够根据之前注册表中的记录,进行相应的初始化工作。Android也提供了一个类似的机制,叫作属性服务。
init进程启动时会启动属性服务,并为其分配内存,用来存储这些属性,如果需要这些属性直接读取就可以了,在2.1.2节的开头部分,我们提到在init.cpp的main函数中与属性服务相关的代码有以下两行:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer564.jpg?sign=1739226410-6kRN6zp6F7gWqCRXiGrMNLubRJXP6KxS-0-1ff11fe185e02e7a917c592b11d35db1)
这两行代码用来初始化属性服务配置并启动属性服务。首先我们来学习属性服务配置的初始化和启动。
1.属性服务初始化与启动
property_init函数的具体实现代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer585.jpg?sign=1739226410-eCeFroAHbhhEsu2qiExenNh3Fl1ZiAMo-0-7e887f23e9d2bae3e28357f486f97e86)
__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer586.jpg?sign=1739226410-YERq5Oow3fDBAvS5unarDJzzanPgRgKh-0-4c68dd074a43795d371d6a4d2dea1854)
在注释1处创建非阻塞的Socket。在注释2处调用listen函数对property_set_fd进行监听,这样创建的Socket就成为server,也就是属性服务;listen函数的第二个参数设置为8,意味着属性服务最多可以同时为8个试图设置属性的用户提供服务。注释3处的代码将property_set_fd放入了epoll中,用epoll来监听property_set_fd:当property_set_fd中有数据到来时,init进程将调用handle_property_set_fd函数进行处理。
在Linux新的内核中,epoll用来替换select,epoll是Linux内核为处理大批量文件描述符而做了改进的poll,是Linux下多路复用I/O接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll 内部用于保存事件的数据类型是红黑树,查找速度快,select采用的数组保存信息,查找速度很慢,只有当等待少量文件描述符时,epoll和select的效率才会差不多。
2.服务处理客户端请求
从上面我们得知,属性服务接收到客户端的请求时,会调用handle_property_set_fd函数进行处理:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer603.jpg?sign=1739226410-D0zfP0B7uA7vaiV4mrDluTiPWfmkGMtM-0-87fa547371941acb27ce00b25c972a25)
Android 7.0中只用handle_property_set_fd函数来处理客户端请求,Android 8.0的源码中则增加了注释1处的handle_property_set函数做进一步封装处理,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer604.jpg?sign=1739226410-f6WwnBWXSloWM2FWl8okSYkwlA56hzGW-0-28eb1a74b925df929a0e5647bcf4b7aa)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer622.jpg?sign=1739226410-Pby406IOW05yvpqPHEPsIe1vwpVy0dRs-0-62d3cd5aebfeb51f3e73d1adb218cae2)
系统属性分为两种类型:一种是普通属性;还有一种是控制属性,控制属性用来执行一些命令,比如开机的动画就使用了这种属性。因此,handle_property_set函数分为了两个处理分支,一部分处理控制属性,另一部分用于处理普通属性,这里只分析处理普通属性。如果注释1处的属性名称以“ctl.”开头,就说明是控制属性,如果客户端权限满足,则会调用handle_control_message函数来修改控制属性。如果是普通属性,则会在客户端权限满足的条件下调用注释3处的property_set函数来对普通属性进行修改,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer623.jpg?sign=1739226410-XNGqbyT6JmE0neajeVVE6gh1pcXpVTe0-0-ab4fe32d8cd056198a0a157cf5031918)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer647.jpg?sign=1739226410-v0mbQdJOZdmDmhwVKbtv2IWPbQfvwc0f-0-799bab990809ceb86fcdd1644bf764cc)
property_set函数主要对普通属性进行修改,首先要判断该属性是否合法,如果合法就在注释1处从属性存储控件中查找该属性,如果属性存在,就更新属性值,否则就添加该属性。另外,还对名称以“ro”“persist”开头的属性进行了相应的处理。
2.1.7 init进程启动总结
init进程启动做了很多的工作,总的来说主要做了以下三件事:
(1)创建和挂载启动所需的文件目录。
(2)初始化和启动属性服务。
(3)解析init.rc配置文件并启动Zygote进程。