基于 Android 6.0.0 源码进行分析
init
进程涉及的源码:
/system/core/init/init.cpp
概述
init
进程是 Linux 系统中用户空间的第一个进程,其进程号为 1。在 Kernel
启动后,在用户空间启动 init
进程,并调用 init
中的 main()
方法执行 init
进程的职责。对于 init
进程的功能,通过分析其 main()
方法的逻辑,主要可以划分为以下 5 部分:
- 挂载、创建所系统运行所需要的目录,如
tmpfs
、pts
、proc
… - 加载属性服务;
- 解析并运行所有的 init.rc 相关的文件;
- 化身为守护进程,处理signal、property和keychord等服务;
接下来,让我们一起进入 Android Framework 源码的世界吧!
main
[->init.cpp]
1 | int main(int argc, char** argv) { |
挂载文件系统
在 init
进程初始化的过程会分别挂载 tmpfs
、devpts
、proc
、sysfs
文件系统。
tmpfs文件系统
tmpfs是一种虚拟内存文件系统,因此它会将所有的文件存储在虚拟内存中。并且tmpfs下的所有内容均为临时性的内容。tmpfs是一个独立的文件系统,不是块设备,只要挂接就可以使用。同时,mpfs是驻留在RAM的,因此它的内容是不持久的,断电后,tmpfs的内容就消失了,这也是被称作tmpfs的原因。
devpts文件系统
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc文件系统
proc
文件系统是一个非常重要的虚拟文件系统,它可以看着是内核数据结构的接口,通过它我们可以获取到系统的信息,同时也可以在运行时修改特定的内核参数。
sysfs文件系统
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
准备klog
1 | void klog_init(void) { |
这里通过 open
函数打开 /dev/kmsg
设备节点。例如可以通过 cat /dev/kmsg
来打印内核的 log
日志。
然后通过 klog_set_level(5)
设备日志输出的条件。当日志小于 5 的时候,会输出日志到文件 /dev/kmsg
。
属性服务
属性服务在 init
守护进程中运行。每一个客户端想要设置属性时,必须连接属性服务,再向其发送信息。属性服务会在共享内存中修改、创建属性。
工作原理图:
[->\system\core\init\property_service.cpp]
1 | void property_init() { |
其中,主要的核心的主要操作是在方法 __system_property_area_init
中:
- 通过
open
打开/dev/__properties__
文件; - 通过
mmap
内存映射到进程中,并以共享内存的方式分配#define PA_SIZE (128 * 1024)
大小的内存区域,这样其他进程就可以跨进程访问属性区域了; - 然后把内存的首地址保存在全局变量
__system_property_area__
中,后续的增加或者修改属性都是基于该变量来计算位置。
#1.0 start_property_service
1 | void start_property_service() { |
handle_property_set_fd 回调函数主要是针对 socket 接收的事件进行处理,同时在设置属性的时候,也会对属性名称进行检查是否合法。
解析 init.rc 文件
在 init
进程中,配置文件是指 init.rc
文件,其路径是:
\system\core\rootdir\init.rc
文件 init.rc
包含四种状态类被,分别是 Action、Commands、Services 和 Options。当 声明一个 Services 或 Action 的时候,它将隐式声明一个 section,它之后跟随的 Command 或者 Option 都属于这个 section。
Action
Action 就是在某种条件下触发的一系列命令,通常有一个 trigger
,通过触发器 trigger
,即以 on 开头的语句来决定执行相应的 Service 的时机,具体的时机如下:
- on early-init:在初始化早期阶段触发;
- on init:在初始化阶段触发;
- on late-init:在初始化阶段触发;
- on boot/charger:当系统启动/充电时触发;
- on property:
= :当属性值满足条件时触发;
通常的形式如下:
1 | on <trigger> |
如下例子:
1 | on early-init |
下面列举常用的 command 命令:
- class_start
: 启动属于同一个class的所有服务; - start
: 启动指定的服务,若已启动则跳过; - stop
: 停止正在运行的服务; - setprop
:设置属性值; - mkdir
:创建指定目录; - symlink
: 创建连接到 的 符号链接; - write
: 向文件path中写入字符串; - exec: fork并执行,会阻塞init进程直到程序完毕;
- exprot
:设定环境变量; - loglevel
:设置log级别;
Service
服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。
Service 的结构如下:
1 | service <name> <pathname> [<argument>]* |
如下例子:
1 | service surfaceflinger /system/bin/surfaceflinger |
在上述的例子中,surfaceflinger
表示为服务名,而 /system/bin/surfaceflinger
表示的是服务的执行路径。
其中,**option **是 Service 的修饰词,也可以理解为 Service 的可选项,和 Service
配合使用。主要包括以下的选项:
- critical:表示如果服务在 4 分钟重启的次数多于 4 次,则系统重启到 recovery mode;
- disabled:表示服务不会自动重启,需要手动调用名字重启。
- setEnv
: 设置启动环境变量; - socket
[ [ ]]: 开启一个 unix 域的 socket,名字为 /dev/socket/ , 只能是 dgram 或者stream, 和 默认为 0; - user
: 表示将用户切换为 , 用户名已经定义好了,只能是 /system/root; - group
:表示将组切换为 - oneshot:表示这个 Service 只启动一次;
- class
: 指定一个要启动的类,这个类中如果有多个 Service,将会被同时启动。默认的class 将会 “default”; - onrrestart: 在重启时执行一条命令;
分析 init.rc 的过程
init
是在 main
方法中通过 init_parse_config_file
来解析 init.rc
文件的。整个解析的流程可以总结为:
init_parse_config_file >> read_file >> parse_config
其中 init_parse_config_file
、以及 parse_config
主要是通过 for
循环对文件 init.rc
的内容进行解析,以一行一行的形式进行读取。
最后会来到 parse_config
中对读取到的每一行进行解析:
1 | static void parse_config(const char *fn, const std::string& data) |
其中上面关键的代码是 lookup_keyword
主要是对每一行的第一个字符进行进行 case
判断。如常用的 service 和 on 经过 lookup_keyword
解析会返回 K_service
和 K_on
,随后通过 kw_is(kw, type)
判断是否属于 SECTION 类型,而在 init.rc
文件中只有 service 和 on 满足该类型。
启动服务
1 | on early-init |
从 init.rc
文件可以看出该文件的解析是按照一定的顺序进行解析的。即以 early-init >> init >> late-init
的顺序进行解析。
Zygote
zygote
服务被定义在 init.zygote.rc
文件中,与 init.rc
在相同的目录下,即 system/core/rootdir
目录下。
1 | service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote |
其中,会执行到 zygote
的源代码, 其源代码位于 /framework/base/cmds/app_process/APP_main.cpp
文件的 main
下,但在这里先不对其详细的逻辑进行展开。
信号处理
# 1.0 restart_processes
[->init.cpp]
1 | static void restart_processes() |
主要的作用是检查 service_list
中的所有服务,对带有 SVC_RESTARTING
标志的服务,则会去调用方法 restart_service_if_needed
。
1 | static void restart_service_if_needed(struct service *svc) |
然后会去调用 service_start
方法来启动服务。
# 1.2 signal_handler_init
在 init
进程的 main
方法中,通过调用 signal_handler_init
方法来初始化对 signal
信号的处理。其主要的处理逻辑有以下:
sigaction
动作注册需要监听的信号类型;- 通过
waitpid
循环监听子进程的结束情况,存在子进程退出,便给退出的子进程标记上SVC_RESTARTING
; - 通过
epoll_ctl
把需要监听的socket fd
注册在epoll_fd
上,当socket fd
发生变化,便调用handle_signal
处理子进程退出的情况;
# 1.2.1 signal_handler_init
1 | void signal_handler_init() { |
这里初始化两个 signal_write_fd
、以及 signal_read_fd
读写端,作用于当系统发出指定的 SIGCHLD
信号时,通知执行 handle_signal
。
1 | static void handle_signal() { |
最后在 handle_signal
中,会去调用到 reap_any_outstanding_children
。
1 | static void reap_any_outstanding_children() { |
在 reap_any_outstanding_children
中,如果 wait_for_one_process
在返回 false
的情况下,会一直循环,通过 wait_for_one_process
里面的 waitpid
来等待可能的子进程退出的情况。当监听到子进程存在退出的情况,那么最后会给该子进程打上 SVC_RESTARTING
的标记。
总结
init
进程(pid=1)是 Linux 系统中用户空间的第一个进程,主要的工作如下:
- 初始化属性服务;
- 解析各个 rc 文件,并启动相应的服务进程;
- 初始化 epoll,依次执行 signal 等回调函数;
- 进入无限循环状态,主要做如下工作:
- 检查 service_list 中的所有服务,对于带有 SVC_RESTARTING 标志的服务,则都会调用其相应的restart_service_if_needed;(检查是否有需要重启的进程)
- 进入 epoll_wait 状态,直到系统属性发生变化,或者有 keychord 键盘输入事件,则会等待状态,执行相应的回调函数;