![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=1739532702-hjg6MVpKuIL4Rx76LTTzUnw1MBjk1xSY-0-f1b3b6debb4badd3e62cd24ee1c5c1d6)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer396.jpg?sign=1739532702-k2LjaBBwcMRxJ7xo4ysh2QktQgsumSeg-0-565b29f3cf598508585bd4d6ef0d7d41)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer418.jpg?sign=1739532702-vmccw7iOt4rsnuNYWkZi9K9R3b9P35FH-0-179516419f69b83bae91e48e4e07fa6c)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer440.jpg?sign=1739532702-w3nu25TD3W0anJIi90dVVbXwMwhJeAm6-0-b456836194a8155d5bf436ef2d23bbec)
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=1739532702-haBNcGzZ3BLCBY8T4S9isO8WN44cIDW3-0-1f01e4e98f226edfccc93988eb4744a3)
这里只截取了一部分代码。on init和on boot是Action类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer461.jpg?sign=1739532702-KyDWfRj9JyxgKtYYaIgAI83uopNVy4l3-0-1994d3fe9efa54eeff4a3020782a7d43)
为了分析如何创建Zygote,我们主要查看Service类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer462.jpg?sign=1739532702-CAzRQMfxjkYjkJxnau6SrSD1HWUhLwCS-0-1f097c0fe5f2e40dbf25d1d011807526)
需要注意的是,在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=1739532702-2ZRh4NRE6rtLKGRdYfayCJg5VBEBYna5-0-b26493a78d5873b449737def17527682)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer477.jpg?sign=1739532702-nZQ9FJ9V2BqOQlb1CMy4tfyohpkyii3a-0-9ec7b873fde1645fd7e4819010dac4ad)
根据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=1739532702-g7vLyxfRoRACdUimoScPJ50RnbMReuh0-0-99de7056d26d00363e9420fb387a36ec)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer503.jpg?sign=1739532702-ZbiAgB2P5rj4x3iYjwdL0b62YVyhB2Q6-0-b486946569f6c96c929398d20145a8a4)
注释1处,根据参数,构造出一个Service对象,它的classname为default。在解析完所有数据后,会调用EndSection函数:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer504.jpg?sign=1739532702-iapjUb93kmsjjyRbAS9kHQjUvbYmsJJm-0-aacf905f39a5c1f276fb26e6a2caba2b)
EndSection函数中会调用ServiceManager的AddService函数,接着查看AddService函数做了什么:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer505.jpg?sign=1739532702-0EVRzkegviRBxgFvxFFemOMaBbx8SKjU-0-615e301d116b00514373b7a261f5a873)
注释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=1739532702-OcSlK55V6VsKTq3rIenAVXngxHK2oMN0-0-9d15218452c02272b50262ab2ab58f97)
其中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=1739532702-ygZnM6L3lbRk9aZ0ogjjPSjWXLLMDwU4-0-4d0b9059feeb9acbc3aaa679de2b3a1b)
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer520.jpg?sign=1739532702-F6i9BK72je2hCpcQbqbmbWo6x12xMLVh-0-0ff4778cf2c22f47f435c6923cd9401b)
注释1处,如果Service没有在其对应的rc文件中设置disabled选项,则会调用Start函数启动该Service,Zygote对应的init.zygote64.rc中并没有设置disabled选项,因此我们接着来查看Start函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer521.jpg?sign=1739532702-PTfGqPo0zTsfedsGBqA5g2E7Qa06WOLU-0-68fb9ca94c3be9ce66e610daa2e7afcc)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer544.jpg?sign=1739532702-ZUxWOnscoqm9qSBTZ0RwvdKtL0BcR0uX-0-4866349df98ed194304f29daaff11028)
首先判断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=1739532702-EHYOmD6NREF4QgRZb4sKceLSIVJXMXT7-0-1997bc8370e0d21fc2df36d1cbf07596)
从注释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=1739532702-SEEbeo6lWNLEOdVBPmPjExdRSn4NEZwj-0-5bedb4898e95e7a30aeaa6eedb809a4b)
这两行代码用来初始化属性服务配置并启动属性服务。首先我们来学习属性服务配置的初始化和启动。
1.属性服务初始化与启动
property_init函数的具体实现代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer585.jpg?sign=1739532702-yP2rdmIod8WPLdJa7V5773A8r2pwOet2-0-771ed7803158b7ce70d6ffb2735ba6f9)
__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer586.jpg?sign=1739532702-bXj7K5cp7IA9GVusXPf6x6xIFY868b9V-0-96e78c921eb3db5c4eb4ba5a2dd85ad3)
在注释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=1739532702-fQAdXhhvOo8TpUdkpY9ugDPQ6UJxS3yY-0-46369d8c0b61c3990bdd94c66b980772)
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=1739532702-FXaUJfDmxEwAHTuXkbbF0fBj5Pc5iGho-0-2faf4f2de28b80df651c144121c54207)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer622.jpg?sign=1739532702-c66d7tpq44t5uo8lA99kdYZIP9MYyPun-0-ffede38bdb376d8d1c88601b727bb074)
系统属性分为两种类型:一种是普通属性;还有一种是控制属性,控制属性用来执行一些命令,比如开机的动画就使用了这种属性。因此,handle_property_set函数分为了两个处理分支,一部分处理控制属性,另一部分用于处理普通属性,这里只分析处理普通属性。如果注释1处的属性名称以“ctl.”开头,就说明是控制属性,如果客户端权限满足,则会调用handle_control_message函数来修改控制属性。如果是普通属性,则会在客户端权限满足的条件下调用注释3处的property_set函数来对普通属性进行修改,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer623.jpg?sign=1739532702-dOZpeQntRP9TXRBvFxxWryJA3oDqdd2y-0-7fb27d49ce1100b08a5d2940efa3ffc3)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer647.jpg?sign=1739532702-DHRFT2isU7y2k6pJzry8qQAZJYTbI0NY-0-64ae2a12f0c8b246def592829681b7ae)
property_set函数主要对普通属性进行修改,首先要判断该属性是否合法,如果合法就在注释1处从属性存储控件中查找该属性,如果属性存在,就更新属性值,否则就添加该属性。另外,还对名称以“ro”“persist”开头的属性进行了相应的处理。
2.1.7 init进程启动总结
init进程启动做了很多的工作,总的来说主要做了以下三件事:
(1)创建和挂载启动所需的文件目录。
(2)初始化和启动属性服务。
(3)解析init.rc配置文件并启动Zygote进程。