![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=1738805422-lHso3RiuCgfTIvE41BG7BOt4aW1XXdTy-0-6c76caaee56117e6d1d4118599f20eb0)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer396.jpg?sign=1738805422-eyIGy4H1cCQ4LhH4ONJG2um0LWdSOEP8-0-ab491b142b6a136a6c03341d0e385c63)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer418.jpg?sign=1738805422-3luydIocUCriwa5KX1V8imNJDsFm2Kkl-0-db2feffdbee889b6e72eac89c8f9374d)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer440.jpg?sign=1738805422-8Tr1Xqy6vRGl31xA9r9NxWeCj4JXFQ9U-0-4be7f9498ecb5e904a3e09edbe088225)
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=1738805422-mJKugwyJZb6OAkNwprKUZnz9MsWvVhXi-0-d049ebd20c1722dfe8f1a484bd95ac38)
这里只截取了一部分代码。on init和on boot是Action类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer461.jpg?sign=1738805422-aADkZdZY1Gbkum7l8Y81CeGAx3pgKdrH-0-0696252b86091fbfdbbcdd1c8fd90756)
为了分析如何创建Zygote,我们主要查看Service类型语句,它的格式如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer462.jpg?sign=1738805422-B45QZpkh3rkTWBUuLYNbpq5NGRlsVPsL-0-12e6c7ff4620af939b0b8432c58c1925)
需要注意的是,在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=1738805422-rUl0QYSt6dBowiE0SMYVsYnyTjebdSvF-0-827cb7efb6c9538c86f425a91fe20f7a)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer477.jpg?sign=1738805422-p09obVXsT3WmL08pZ5BI6f7MEVAqCWEq-0-78620e2e9ac36024abd3082682f26222)
根据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=1738805422-OWbtePItHpTfrxUtiwuzx3x4oTenuJx0-0-3dfc3c0732495c378166e6ad889fe0e2)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer503.jpg?sign=1738805422-LFbgIdiqf4VeozEy4OuHgxj2wEAhf7ju-0-930aa07d8b3f8778660fb3bd27d3ca95)
注释1处,根据参数,构造出一个Service对象,它的classname为default。在解析完所有数据后,会调用EndSection函数:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer504.jpg?sign=1738805422-s1t8M3mspgmG9gfBQfThcI2vACtmNIcw-0-ccd16fc9c9bf4a04d11bdcf159b7c25a)
EndSection函数中会调用ServiceManager的AddService函数,接着查看AddService函数做了什么:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer505.jpg?sign=1738805422-7NUtv59D5mcXum6MtICFMmtg091oPk30-0-9af63abe68c4fc6eca806dda27160ee9)
注释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=1738805422-RyUWJAHx72NS27YGiMRl6or2OxRcxmC4-0-1ecd77462d24e2c5f385021373ee1d8e)
其中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=1738805422-xmf9wmzkrxLNiLZ34KBhaQDb10dSLzls-0-c9ac53b857f4e91d94dc96c1e9e49759)
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer520.jpg?sign=1738805422-Y1i9eIJgSPySVabC4tGIi29uVefZlLQv-0-55b4eea6e3762ed05d7ad738d0a039c6)
注释1处,如果Service没有在其对应的rc文件中设置disabled选项,则会调用Start函数启动该Service,Zygote对应的init.zygote64.rc中并没有设置disabled选项,因此我们接着来查看Start函数,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer521.jpg?sign=1738805422-HFxYDNQBI4oQzVWLmgOuhp7uMfCiMbPQ-0-3cd4778f286e70693eb3b2b9a01dd3fe)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer544.jpg?sign=1738805422-zLrcIAxUilJ76bwX70zk3S2R2qLLNa2F-0-5b5a7ae46eea8436092c5409d3f0eb66)
首先判断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=1738805422-PPAQcgu45HRBlffJacDmqKwdXntGvmfm-0-5e534cbb9f21d4c18dc84f0022d53c0b)
从注释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=1738805422-OUNbSj06JzRtXvtItBtPOMRUWRKm77vV-0-6eabf0ffd1f5b18c8e787f30b4335dd6)
这两行代码用来初始化属性服务配置并启动属性服务。首先我们来学习属性服务配置的初始化和启动。
1.属性服务初始化与启动
property_init函数的具体实现代码如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer585.jpg?sign=1738805422-OQfSUCEqv8WtQkpcyapBmb4rIvyAA37W-0-bfd59ccd77bd55089ff5bbf9c40621b3)
__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer586.jpg?sign=1738805422-kE82vhd4NS4hnOC1IxLibFFhnL9Y0mCp-0-2f776f1abec83a41c236dc814fb212d0)
在注释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=1738805422-UNPa4ugUmbnBwZiPlI2dosPhviBKOhkT-0-e8bfdddf8d3ee1a6534eb7815c833a20)
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=1738805422-9t9QL3Pi17kEXDg7POxvyzTNOY19VbGW-0-653b6d21c45144f274956eeebee7ab3c)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer622.jpg?sign=1738805422-MvKJXgia5tWc6TAFLruRAJi6MAJfh0jX-0-079920befa68b30a5b6cab5e6075c805)
系统属性分为两种类型:一种是普通属性;还有一种是控制属性,控制属性用来执行一些命令,比如开机的动画就使用了这种属性。因此,handle_property_set函数分为了两个处理分支,一部分处理控制属性,另一部分用于处理普通属性,这里只分析处理普通属性。如果注释1处的属性名称以“ctl.”开头,就说明是控制属性,如果客户端权限满足,则会调用handle_control_message函数来修改控制属性。如果是普通属性,则会在客户端权限满足的条件下调用注释3处的property_set函数来对普通属性进行修改,如下所示:
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer623.jpg?sign=1738805422-AUb8BNod8knim341VBoHrfQlLRWwISLw-0-a7de01b48ba366a9423afaa3444525af)
![](https://epubservercos.yuewen.com/D63A94/16896237205618706/epubprivate/OEBPS/Images/figer647.jpg?sign=1738805422-glNDyUPKoGaI0fcOpS1svu2l4Kwi0Reo-0-6ebcc2925bdece482cae57c7cd439992)
property_set函数主要对普通属性进行修改,首先要判断该属性是否合法,如果合法就在注释1处从属性存储控件中查找该属性,如果属性存在,就更新属性值,否则就添加该属性。另外,还对名称以“ro”“persist”开头的属性进行了相应的处理。
2.1.7 init进程启动总结
init进程启动做了很多的工作,总的来说主要做了以下三件事:
(1)创建和挂载启动所需的文件目录。
(2)初始化和启动属性服务。
(3)解析init.rc配置文件并启动Zygote进程。