深入了解Android卷 学习笔记 03

本文详细探讨了Android系统中init进程的角色和功能,包括如何创建zygote进程以及管理属性服务。通过分析init的源码,阐述了init如何解析配置文件、启动service,特别是zygote的启动过程和属性服务的工作原理,对于理解Android系统启动流程具有重要作用。

第三章 深入理解 init

3.1 概述

init 是一个进程,它是 Linux 系统中用户空间的第一个进程,所以 init 也是 Android 系统中用户空间的第一个进程,所以它的进程号是 1。init 的两个比较重要的职责:

  • init 进程负责创建系统中的几个关键进程,尤其是下一章要介绍的 zygote,它更是 Java 世界的开创者。
  • Android 系统有很多属性,于是 init 就提供了一个 property service (属性服务)来管理它们。

下面主要分析 init 的两个方面:

  • init 如何创建 zygote。
  • init 的属性服务是如何工作的。

3.2 init 分析

init 进程的入口函数是main。

init 把动作执行的时间分为四个阶段:early-init、init、early-boot、boot。

init 关注来自四个方面的事情:

  • device_fd 用于监听来自内核的 Uevent 事件。
  • property_set_fd 用于监听来自属性服务器的事件。
  • signal_recv_fd 由 socketpair 创建,它的事件来自另外一个 socket。
  • 如果 keychord 设备初始化成功,则 init 也会关注来自这个设备的事件。

Boot chart 是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表,以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。

init 的工作流程精简为一下四点:

  • 解析两个配置文件,下面将分析其中对 init.rc 文件的解析。
  • 执行各个阶段的动作,创建 zygote 的工作就是在其中的某个阶段完成的。
  • 调用 property_init 初始化属性相关的资源,并且通过 property_start_service 启动属性服务。
  • init 进入一个无限循环,并且等待一些事情的发生。重点关注 init 如何处理来自 socket 和来自属性
    服务器的相关事情。

3.2.1 解析配置文件

在 init 中会解析两个配置文件,其中一个是系统配置文件 init.rc,另外一个是与硬件平台相关的配置文件。对这两个配置文件进行解析,调用的是同一个 parse_config_file 函数。

从整体来说,parse_config 首先会找到配置文件的一个 section,然后针对不同的 section 使用不同的解析函数来解析。

1、关键字定义
keywords.h 这个文件定义了 init 中使用的关键字。主要做了两件事情:

  • 第一次包含 keywords.h 时,它声明了一些诸如 do_class_start 的函数,另外还定义了一个枚举,枚举值为 K_class,K_mkdir 等关键字。
  • 第二次包含 keywords.h 后,得到了一个 keyword_info 结构体数组,这个 keywords_info 结构体数组以前面定义的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称、处理函数、处理函数的参数个数,以及属性。

关键字信息中最重要的就是 symbol 和 flags 了。根据 keywords.h 的定义,当 symbol 为 on 或 service 的时候表示 section。

2、解析 init.rc
对 init.rc 的分析可知:

  • 一个 section 的内容从这个标识 section 的关键字开始,到下一个标识 section 的地方结束。
  • init.rc 中出现了名为 boot 和 init 的 section,这里的boot 和 init 就是前面介绍的4个动作执行阶段中的 boot 和 init。也就是说,在 boot 阶段执行的动作都是由 boot 这个 section 定义的。

3.2.2 解析 service

zygote 对应的 service section 内容是:
在这里插入图片描述解析 section 的入口函数是 parse_new_section。其中,解析 service 时,用到了 parse_service 和 parse_line_service 这两个函数。
1、service 结构体
init 中使用了一个叫 service 的结构体来保存与 service section 相关的信息。

listnode 是一个特殊的结构体,在内核代码中用得非常多,主要用来将结构体连接成一个双向链表。init 中有一个全局的 service_list,专门用来保存解析配置文件后得到的service。

有些 service 需要使用 socket,下面这个 socketinfo 用来描述 socket 的相关信息。我们的 zygote 也使用了 socket,配置文件中的内容是 socket zygote stream 666。它表示将创建一个 AF_STREAM 类型的 socket(其实就是 TCP socket),该socket 的名为 “zygote”,读写权限是 666。
service 一般运行在一个单独的进程中, envvars 用来描述创建这个进程时所需的环境变量信息。

看看 service 中使用的这个 action 结构体:
一个 action 结构体可存放在三个双向链表中,qlist 用于链接那些等待执行的 action,tlist 用于链接那些待某些条件满足后就需要执行的 action。
2、了解 parse_service
parse_service 函数只是搭建了一个service的架子,具体的内容尚需由后面的解析函数来填充。
3、了解 parse_line_service
parse_line_service 将根据配置文件的内容填充 service 结构体。zygote 解析后的结果:
在这里插入图片描述由图可知:

  • service_list 链表将解析后的 service 全部链接到了一起,并且是一个双向链表,前向节点用 prev 表示,后向节点用 next 表示。
  • socketinfo 也是一个双向链表,因为 zygote 只有一个 socket,所以画了一个虚框 socket 作为链表的示范。
  • onrestart 通过 commands 指向一个 commands 链表,zygote 有三个 commands。

3.2.3 init 控制 service

1、启动 zygote

class_start 是一个 COMMAND,对应的函数为 do_class_start,它位于 boot section 的范围内。

do_class_start 函数如下:
在这里插入图片描述zygote 这个 service 的 classname 的值就是 “default”,所以会针对这个 service 调用 service_start_if_not_disabled。

service 一般运行于另外一个进程中,这个进程也是 init 的子进程,所以启动 service 前需要判断对应的可执行文件是否存在,zygote 对应的可执行文件是 /system/bin/app_process。
执行 /system/bin/app_process,这样就进入到 app_process 的 main 函数中了。

2、重启 zygote
onrestart 是在 zygote 重启时用的。signal_fd 就是在 init 中通过 socketpair 创建的两个 socket 中的一个,既然会往这个 signal_fd 中发送数据,那么另外一个 socket 就一定能接收到,这样就会导致 init 从 poll 函数中返回。

如果设置了 SVC_CRITICAL 标识,则 4 分钟内该服务重启的次数不能超过 4 次,否则机器会在重启时进入 recovery 模式。根据 init.rc 的配置来看,只有 servicemanager 进程享有此种待遇。

zygote 在下面代码中进行重启:
在这里插入图片描述

3.2.4 属性服务

我们知道,Windows 平台上有一个叫注册表的东西。注册表可以存储一些类似 key/value 的键值对。一般而言,系统或某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性,进行相应的初始化工作。Android 平台也提供了一个类似的机制,称之为属性服务(property service)。
与 init.c 和属性服务有关的代码有下面两个:

  • property_init();
  • property_set_fd = start_property_service();

1、属性服务初始化
(1)创建存储空间
在 property_init 函数中,先调用 init_property_area 函数,创建一块用于存储属性的存储区域,然后加载 default.prop 文件中的内容。
初始化存储空间,PA_SIZE 是这块存储空间的总大小,为 32768 字节,pa_workspace 为 workspace 类型的结构体,下面是它的定义

typedef struct{
    void *data;     //存储空间的起始地址
    size_t size;    //存储空间的大小
    int fd;     //共享内存的文件描述符
}workspace;

init_workspace 函数调用 Android 系统提供的 ashmen_create_region 函数创建一块共享内存。
虽然属性区域是由 init 进程创建的,但 Android 系统希望其他进程也能读取这块内存里的东西。为了做到这一点,它便做了以下两项工作:

  • 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,已经在上面的代码中见到了,init_workspace 函数内部将创建这个共享内存。
  • Android 利用了 gcc 的 constructor 属性,这个属性指明了一个 _libc_prenit 函数,当 bionic libs 库被加载时,将自动调用这个 _libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。

(2)客户端进程获取存储空间

constructor 属性指示加载器加载该库后,首先调用 _libc_prenit 函数,接着调用 _libc_init_common 函数,然后调用 _system_properties_init。

客户端进程可以直接读取属性空间,但没有权限设置属性。
2、属性服务器的分析
(1)启动属性服务器
init 进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性,属性服务器是由 start_property_service 函数启动。

/*
加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。
Android 系统一共提供了四个存储属性的文件,它们分别是:
*/
#define PROP_PATH_RAMDISK_DEFAULT  “/default.prop”
#define PROP_PATH_SYSTEM_BUILD     “/system/build.prop”
#define PROP_PATH_SYSTEM_DEFAULT   “/system/default.prop”
#define PROP_PATH_LOCAL_OVERRIDE   “/data/local.prop”

有一些属性是需要保存到永久介质上的,这些属性文件则由下面这个函数加载,这些文件存储在 /data/property 目录下,并且这些文件的文件名必须以 persist. 开头。

属性服务创建了一个用来接收请求的 socket,在 init 中的 for 循环处对其进行了相关处理。
(2)处理设置属性请求
当属性服务器收到客户端请求时,init 会调用 handle_property_set_fd 进行处理。当客户端的权限满足要求的时候,init 就调用 property_set 进行相关处理。
(3)客户端发送请求
客户端通过 property_set 发送请求,property_set 由 libcutils 库提供。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值