- Android进阶解密
- 刘望舒
- 3400字
- 2020-08-27 17:04:55
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,代码如下所示:
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的配置代码如下所示:
这里只截取了一部分代码。on init和on boot是Action类型语句,它的格式如下所示:
为了分析如何创建Zygote,我们主要查看Service类型语句,它的格式如下所示:
需要注意的是,在Android 8.0中对init.rc文件进行了拆分,每个服务对应一个rc文件。我们要分析的Zygote启动脚本则在init.zygoteXX.rc中定义,这里拿64位处理器为例,init.zygote64.rc的代码如下所示:
根据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,用于解析子项。代码如下所示:
注释1处,根据参数,构造出一个Service对象,它的classname为default。在解析完所有数据后,会调用EndSection函数:
EndSection函数中会调用ServiceManager的AddService函数,接着查看AddService函数做了什么:
注释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中有如下配置代码:
其中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中定义,如下所示:
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数,如下所示:
注释1处,如果Service没有在其对应的rc文件中设置disabled选项,则会调用Start函数启动该Service,Zygote对应的init.zygote64.rc中并没有设置disabled选项,因此我们接着来查看Start函数,如下所示:
首先判断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函数中,代码如下:
从注释1处的代码可以得知调用runtime的start函数启动Zygote,至此Zygote就启动了。
2.1.6 属性服务
Windows 平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,其还是能够根据之前注册表中的记录,进行相应的初始化工作。Android也提供了一个类似的机制,叫作属性服务。
init进程启动时会启动属性服务,并为其分配内存,用来存储这些属性,如果需要这些属性直接读取就可以了,在2.1.2节的开头部分,我们提到在init.cpp的main函数中与属性服务相关的代码有以下两行:
这两行代码用来初始化属性服务配置并启动属性服务。首先我们来学习属性服务配置的初始化和启动。
1.属性服务初始化与启动
property_init函数的具体实现代码如下所示:
__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:
在注释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函数进行处理:
Android 7.0中只用handle_property_set_fd函数来处理客户端请求,Android 8.0的源码中则增加了注释1处的handle_property_set函数做进一步封装处理,如下所示:
系统属性分为两种类型:一种是普通属性;还有一种是控制属性,控制属性用来执行一些命令,比如开机的动画就使用了这种属性。因此,handle_property_set函数分为了两个处理分支,一部分处理控制属性,另一部分用于处理普通属性,这里只分析处理普通属性。如果注释1处的属性名称以“ctl.”开头,就说明是控制属性,如果客户端权限满足,则会调用handle_control_message函数来修改控制属性。如果是普通属性,则会在客户端权限满足的条件下调用注释3处的property_set函数来对普通属性进行修改,如下所示:
property_set函数主要对普通属性进行修改,首先要判断该属性是否合法,如果合法就在注释1处从属性存储控件中查找该属性,如果属性存在,就更新属性值,否则就添加该属性。另外,还对名称以“ro”“persist”开头的属性进行了相应的处理。
2.1.7 init进程启动总结
init进程启动做了很多的工作,总的来说主要做了以下三件事:
(1)创建和挂载启动所需的文件目录。
(2)初始化和启动属性服务。
(3)解析init.rc配置文件并启动Zygote进程。