Android 属性系统分析

来源:互联网 发布:网络劫持原理 编辑:程序博客网 时间:2024/04/28 13:29
标 题: 【原创】Android属性系统(default.prop)分析
作 者: MindMac
时 间: 2013-12-23,14:52:25
链 接: http://bbs.pediy.com/showthread.php?t=182901

Android 属性系统分析

MindMac
2013/12/23


目录

Android 属性系统初始化...................................................................................... 2
Android 属性系统服务........................................................................................16
系统属性内存空间共享.........................................................................................19
Android 系统属性获取........................................................................................22
Android 系统属性设置........................................................................................27
总结.................................................................................................................35


Android 属性系统通过系统服务提供系统配置和状态的管理。为了让运行中的所有进程
共享系统运行时所需要的各种设置值,系统会开辟一个属性存储区域,并提供访问该内存区
域的 API。所有进程都可以访问属性值,但是只有 init 进程可以修改属性值,其他进程若想
修改属性值,需要向 init 进程发出请求,最终由 init 进程负责修改属性值。本文基于 Android
4.0 对 Android 属性系统进行分析进行了相关分析。


Android 属性系统初始化


init 进程(源码位于/system/core/init/init.c)主要完成:解析 init.rc 文件并执行相应动作和服
务;生成设备驱动节点;处理子进程终止;提供属性服务。因为分析 Android 属性系统,因
此只关注属性服务。在 init.c 的 main 函数中调用 queue_builtin_action()将 property_init_action
函数放入特定的列表中,如下:
点击图片以查看大图图片名称:1.png查看次数:35文件大小:25.6 KB文件 ID :85692

queue_builtin_action 函数定义在 init_parser.c 中(/system/core/init/init_parser.c),如下:
点击图片以查看大图图片名称:2.png查看次数:24文件大小:22.6 KB文件 ID :85693
点击图片以查看大图图片名称:3.png查看次数:11文件大小:12.7 KB文件 ID :85694

上述代码涉及到两个结构体 action 和 command。这两个结构体都定义在 init.h 头文件中
(/system/core/init/init.h)中。
点击图片以查看大图图片名称:4.png查看次数:26文件大小:18.3 KB文件 ID :85695
点击图片以查看大图图片名称:5.png查看次数:6文件大小:10.5 KB文件 ID :85696

两 个 结 构 体 中 都 包 含 listnode 结 构 体 , 该 结 构 体 定 义 在 list.h 头 文 件 中
(/system/core/include/cutils/list.h),如下代码所示,结构体中包含指向 listnode 的 next 和 prev
点击图片以查看大图图片名称:6.png查看次数:13文件大小:5.5 KB文件 ID :85697

指针,因此由 listnode 可以构成一个双向链表。

command 结 构 体 中 的 func 指 向 一 个 函 数 , 该 函 数 在 后 续 init 进 程 调 用
execute_one_command 时会被调用。

回到 queue_builtin_action 函数中,5-6 行完成 action 结构体内存分配,并将 name 成员赋
值为形参 name,对于 Android 系统属性服务来说,该值为 property_init。第 7 行初始化 action
结构体中成员 commands 的初始化,初始化工作很简单,将 listnode 的 next 和 prev 都指向自
身,如下所示:
点击图片以查看大图图片名称:7.png查看次数:15文件大小:5.8 KB文件 ID :85698

queue_builtin_action 函数的 8-10 行完成 command 结构体的内存分配和成员赋值操作,
对于 Android 属性系统服务,command 结构体的成员 func 指向 property_init_action 函数,成
员 args 表示函数运行的参数值,此处只有一个参数,值为 property_init。11 行调用 list_add_tail
将 command 的 clist 节点插入到 action 的 commands 节点之前,list_add_tail 函数代码如下:
点击图片以查看大图图片名称:8.png查看次数:12文件大小:9.2 KB文件 ID :85699

12 行则将 action 的 alist 插入到 action_list 节点之前。那 action_list 又是什么呢?原来在
init_parser.c 源文件的开头(第 40 行)调用了 list_declare 宏定义了一个名为 action_list 的 listnode
结构体,list_declare 定义如下:
点击图片以查看大图图片名称:9.png查看次数:6文件大小:6.2 KB文件 ID :85700

13 行则通过 action_add_queue_tail 函数将 action 结构体的成员 qlist 添加到 action_queue 前,
action_queue 和 action_list 一样,也是通过 list_declare 宏定义的一个 listnode 变量。调用完
queue_builtin_action 后,对于 action 和 command 结构体以及 action_list 和 action_queue 的关系
名称:  10.png查看次数: 3文件大小:  21.6 KB

图 1 所 示 (queue_builtin_action 在 此 处 并 不 是 最 后 一 次 被 调 用 , 因 此 action_list 以 及
action_queue 和 action 之间用虚点线表示,action_list 和 action_queue 之前还有其他的 action
结构体变量):
名称:  11.png查看次数: 3文件大小:  27.5 KB

再回到 init.c 的 main 函数中,在完成一系列的 queue_builtin_action 和 action_for_each_tirgger
的调用后,进入 for 循环中,如下:
点击图片以查看大图图片名称:12.png查看次数:10文件大小:6.4 KB文件 ID :85703

第 3 行调用 execute_one_command 函数从 action_queue 队列中获取 action 并执行。点击图片以查看大图图片名称:13.png查看次数:10文件大小:9.6 KB文件 ID :85704
点击图片以查看大图图片名称:14.png查看次数:7文件大小:20.6 KB文件 ID :85705

第 4 行首先进行相应检查判断是获取一个新的 action 还是继续执行 action 的 commands 列表
中的下一个 command。其中 cur_action 和 cur_command 定义在 init.c 源文件开头处(71-72 行),
初始值为 NULL。对于 Android 属性系统,当然是一个新的 action,进入 If 分支。第 5 行调用
action_remove_queue_head 从 action_queue 中 获 取 action 并 赋 值 给 cur_action ,
action_remove_queue_head 代码如下:
点击图片以查看大图图片名称:15.png查看次数:9文件大小:15.8 KB文件 ID :85706

第 10 行从 cur_action 中的 commands 节点中获取第一个 command 赋值给 cur_command,
get_first_command 代码如下:
点击图片以查看大图图片名称:16.png查看次数:6文件大小:13.3 KB文件 ID :85707

对于 Android 属性系统,action 的 commands 节点只有一个 command。16 行调用 command
中 func 成员所指向的函数,此处调用 property_init_action 函数,定义如下:
点击图片以查看大图图片名称:17.png查看次数:8文件大小:13.6 KB文件 ID :85708

上述代码第 5 行判断当前设备是否处于充电状态,若处于充电状态则将 load_defaults 设置 为false 。 第7行 调 用property_init函 数 , 该 函 数 定 义 于property_service.c(/system/core/init/property_service.c)文件中,代码如下:
点击图片以查看大图图片名称:18.png查看次数:7文件大小:9.1 KB文件 ID :85709

第 3 行调用 init_property_area 初始化共享内存空间。在 Android 系统中,所有的进程是共享
系统属性值的,Android 系统提供了一个名为属性的保存空间,在共享内存区域中(ASHMEM),
创建并初始化属性域。init_property_area 代码如下:
点击图片以查看大图图片名称:19.png查看次数:11文件大小:24.4 KB文件 ID :85710
点击图片以查看大图图片名称:20.png查看次数:8文件大小:10.9 KB文件 ID :85711


其中 prop_info,prop_area 是定义在/bionic/libc/include/sys/_system_properties.h 头文件中
的两个结构体,如下:
点击图片以查看大图图片名称:21.png查看次数:5文件大小:4.9 KB文件 ID :85712
点击图片以查看大图图片名称:22.png查看次数:8文件大小:13.2 KB文件 ID :85713

prop_area 中 count 成员表示 prop_info 的个数,serial 表示整个 prop_area 被修改的次数,包括
新增加的,toc 是一个 32bit 无符号的数组,每个 32bits 的高 8 位表示 prop_info 的 name 的长
度,低 24 位表示对应 prop_info 相对 prop_area 起始地址的位移。prop_info 中的 serial,其高
8 位表示该 prop_info 中 name 的长度,而低 24 位表示该 prop_info 被更新的次数。workspace
结构体定义如下(property_service.c 文件中):
点击图片以查看大图图片名称:23.png查看次数:8文件大小:5.2 KB文件 ID :85714

回到 init_property_area 函数中,第 9 行完成 workspace 的初始化,创建匿名内存空间,大小
为 32768 字节,代码如下:
点击图片以查看大图图片名称:24.png查看次数:8文件大小:11.8 KB文件 ID :85715
点击图片以查看大图图片名称:25.png查看次数:11文件大小:13.9 KB文件 ID :85716

init_workspace 第 7 行通过 mmap 进行匿名内存映射,为关联进程提供共享内存空间。13-15
行初始化 workspace 结构体,分别为 data,size 和 fd 赋值,data 值为所分配内存空间的起
始地址,size 大小为 32768,fd 为/dev/__properties__文件句柄。

init_property_area 第 11 行给 init_workspace 中创建的文件描述符设置文件描述符标记。
第 12 行设置 pa_info_array 的起始地址,该起始地址为 pa_workspace 中 data 起始地址加上
1024 个字节,之所以地址为这个是因为 prop_area 除 toc 成员外占用 32 字节,而 Android
系统最多允许 247 个 prop_info,因此 toc 一共 247 个,所以偏移地址为 32 + 247*4 = 1020,
但是 Android 系统给偏移地址增加了 4 个字节(为什么增加这 4 个字节?为了对齐?)。所以
从内存空间上看 Android 属性系统如图 2 所示。第 13-14 行将 pa_worksapce 的 data 成员值
赋值给局部变量 prop_area 并将内存区域值初始化为 0。15-16 行为 pa 成员变量 magic 和version赋 值 。 PROP_AREA_MAGIC以 及PROP_AREA_VERSION定 义 在
/bionic/libc/include/sys/_system_properties.h 中。17 行将 pa 赋值给__system_property_area__,18 行将 property_area_inited 设置为 1,表示属性区域初始化完成。
名称:  26.png查看次数: 0文件大小:  15.6 KB

回到 property_init 函数,第 4 行判断是否需要加载默认属性,我们分析未充电情况,因此此处 load_defaults 值为 true,所以函数执行会执行 load_properties_from_file 函数,参数
PROP_PATH_RAMDISK_DEFAULT 定义在/bionic/libc/include/sys/_system_properties.h 的 102 行,
值为/default.prop,该文件中的内容一般如图 3 所示。default.prop 文件中的内容是在 Android
系统编译时写入到 boot.img 中的,而每次开机,boot.img 会进行解压,所以想要修改该文件
中的内容可以重新编译系统,或者修改 boot.img 文件。在/build/core/main.mk 文件中定义了
不同属性的值,ADDITIONAL_DEFAULT_PROPERTIES 变量值就是需要写入到 default.prop 文件中
的内容。
名称:  27.png查看次数: 2文件大小:  8.0 KB

load_properties_from_file 函数代码如下:
点击图片以查看大图图片名称:28.png查看次数:8文件大小:11.9 KB文件 ID :85719

第 5 行从文件中读取内容,第 7 行调用 load_properties 函数加载属性。load_properties 函数如
下:
点击图片以查看大图图片名称:29.png查看次数:7文件大小:7.9 KB文件 ID :85720
点击图片以查看大图图片名称:30.png查看次数:8文件大小:23.1 KB文件 ID :85721

其中 6-18 行对每一行的文件内容进行解析,提取出其中的键值对。关键是第 19 行调用
property_set 函数设置属性。property_set 函数接受两个参数,分别是属性的键和值,其代码
如下:
点击图片以查看大图图片名称:31.png查看次数:10文件大小:17.7 KB文件 ID :85722
点击图片以查看大图图片名称:32.png查看次数:8文件大小:31.2 KB文件 ID :85723
点击图片以查看大图图片名称:33.png查看次数:6文件大小:20.6 KB文件 ID :85724
点击图片以查看大图图片名称:34.png查看次数:5文件大小:730 字节文件 ID :85725

第 7-9 行对属性键值对名称的长度做检查,PROP_NAME_MAX 和 PROP_VALUE_MAX 定义在
/bionic/libc/include/sys/system_properties.h 中 , 其 值 分 别 为 32 和 92 。 第 10 行 调 用
__system_property_find 查询系统中是否已经存在该属性。 __system_property_find 定义在
/bionic/libc/bionc/system_properties.c 文件中,定义如下:
点击图片以查看大图图片名称:35.png查看次数:10文件大小:23.2 KB文件 ID :85726

第 3 行将__system_property_area__值赋给局部变量 pa,此处__system_property_area__又是什么
时候初始化的呢?原来在 property_service.c 源文件中引用了这个变量(158 行),同时在
init_property_area 中为此变量赋值,可以参考前文关于 init_property_area 函数的分析。4-5 行
获取 prop_info 的个数以及第一个 toc 的起始地址。9-13 行根据 toc 来查找 prop_info。第 10
行使用 TOC_NAME_LEN(toc)宏来根据 toc 计算存储的 prop_info 的 name 长度是否相符,
TOC_NAME_LEN(toc)和TOC_TO_INFO(area,    toc)宏 定 义 于
/bionic/libc/include/sys/_system_properties.h 头文件中,定义如下:

点击图片以查看大图图片名称:36.png查看次数:5文件大小:6.1 KB文件 ID :85727


在前文对 prop_area 结构体分析时,已经说明了 toc 高 8 位表示 prop_info 的 name 的长度,低
24 位表示 prop_info 相对 prop_area 的偏移地址。所以 TOC_NAME_LEN 宏可以计算 prop_info 的
name 长度,而 TOC_TO_INFO 可以获取对应 prop_info 的地址。第 11 行获取 prop_info 的起始
地址。第 12 行通过比较 name 值是否相同来判断是否找到需要的 prop_info。若找到则返回
prop_info 的起始地址,否则返回 0。

继续回到 property_set 函数。对于开始设置属性,__system_property_find 返回值当然是 0
了,所以进入 else 分支。18 行将__system_property_area__赋值给局部变量 pa。19 行判断
prop_info 的个数是否达到上限,若达到则返回。20 行获取新的 prop_info 的起始地址,21-23
行为该 prop_info 中的成员设置相应的值,包括 serial 的值,我们可以发现 serial 的高 8 位被
设置为 name 的长度。24-27 行则完成 prop_area 中成员相应值得更新,增加一个 toc,更新
count 和 serial 值。这样就完成了一个新属性在内存空间的设置。

那么如果不是一个新的 prop_info,而是需要更新一个 prop_info,又会如何操作呢?分析
下 property_set 函数的 if 分支不就清楚了。第 12 行判断需要更新的属性名是否已”ro.”开头,
若是,则返回,也就是说所有属性名称类似 ro.*的属性,一旦设置就不能再更改。14 行调用
update_prop_info 更新相应的 prop_info,当然就是更新属性的值了,update_prop_info 的函数如
下,主要完成属性值以及 prop_info 中 serial 成员更新。
点击图片以查看大图图片名称:37.png查看次数:9文件大小:13.0 KB文件 ID :85728

property_set 函数在完成 prop_info 的更新后,会相应更新 prop_area 中 serial 值。

以上分析了 Android 属性系统初始化的过程,因为属性系统最终是通过服务的形式提供
查询、更新的,在分析该服务之前,先做总结如图 4 所示流程图。
点击图片以查看大图图片名称:38.png查看次数:8文件大小:30.3 KB文件 ID :85729

Android 属性系统服务

Android 属性系统服务是如何启动的呢?在 init.c 的 main 函数中,调用 queue_builtin_action
函数除了将 property_init_action 添加到 action_queue 队列中外,还将 property_service_init_action
添加到了 action_queue 队列中(init.c 的 735 行)。代码如下:
点击图片以查看大图图片名称:39.png查看次数:6文件大小:7.1 KB文件 ID :85730

queue_builtin_action 函数在前文已经分析过了,名为 property_service_init_action 的函数会在
execute_one_command 调用中执行。property_service_init_action 函数代码如下:
点击图片以查看大图图片名称:40.png查看次数:9文件大小:16.3 KB文件 ID :85731

从注释中可以看出在读取属性文件时会触发属性服务运行。该函数最终会调用
start_property_service 运行属性服务,主要是创建一个 socket 并监听。start_property_service
定义在/system/core/init/property_service.c 文件中,代码如下:
点击图片以查看大图图片名称:41.png查看次数:10文件大小:24.8 KB文件 ID :85732

4-6 行调用 load_properties_from_file 分别从 3 个文件中加载属性。PROP_PATH_SYSTEM_BUILD、
PROP_PATH_SYSTEM_DEFAULT和PROP_PATH_LOCAL_OVERRIDE都 定 义 在/bionic/libc/include/sys/_system_properties.h 头 文 件 中 , 值 分 别 为 /system/build.prop 、
/system/default.prop 和 /data/local.prop 。 在 加 载 完 上 述 属 性 后 , 第 7 行 调 用
load_persistent_properties 加载持久化属性,这些属性存在于/data/property 目录下,该目录下
存放了以 persist 开头的属性文件,如系统语言、国家编码等。第 8 行创建一个 socket,
create_socket 函数定义于/system/core/init/util.c 文件中,其中 0666 表示权限,两个 0 表示 uid
和 gid 的值都为 0,也就是 root 用户和组,PROP_SERVICE_NAME 值为”property_service”,对应
的 socket 文件为”/dev/socket/property_service”,可参考 util.c 源文件的第 96 行。12 行监听创
建的 socket,此处就不再继续跟踪这个 listen 函数了,有兴趣的可以继续分析,listen 函数定
义于/bionic/libc/unistd/socketcalls.c 文件中。13 行将 property_set_fd 赋值为创建的 socket 文件
描述符。

综上分析可知,Android 属性系统服务实际上是由 init 进程维护的,通过创建一个 socket
来监听相应的属性获取和设置的请求,最终需要由 init 进程来处理,至于是如何通过 socket
来获取和设置属性,后面会继续分析。

在继续分析之前,再来看下另一个与属性设置相关的 action,这个 action 在 init.c 文件
的 722 行处添加到 action_queue 中的,代码如下:
点击图片以查看大图图片名称:42.png查看次数:6文件大小:6.8 KB文件 ID :85733

set_init_properties_action 主要是设置了一些以 ro 开头的属性,包括基带信息,启动模式
等,代码如下:
点击图片以查看大图图片名称:43.png查看次数:6文件大小:21.2 KB文件 ID :85734
点击图片以查看大图图片名称:44.png查看次数:8文件大小:25.3 KB文件 ID :85735

系统属性内存空间共享

通过前文分析可知,Android 系统属性是通过在内核空间中开辟的一个共享内存来存储
属性信息的,那么在整个系统中,其他进程是如何读取这块内存并映射到当前进程空间中的
呢?研究下 init.c 文件中的 service_start 函数,该函数部分代码如下:
点击图片以查看大图图片名称:45.png查看次数:10文件大小:21.7 KB文件 ID :85736
点击图片以查看大图图片名称:46.png查看次数:5文件大小:1.2 KB文件 ID :85737

第 4 行 fork 出子进程,第 5 判断判断是否是子进程,若是,进入 if 分支。第 10 行通过
properties_inited 函数判断属性区域是否已经初始化,11 行通过 get_property_workspace 获取
prop_area 起始地址以及大小,代码如下:
点击图片以查看大图图片名称:47.png查看次数:6文件大小:6.8 KB文件 ID :85738

pa_workspace 在 init_workspace 函数中初始化,fd 为/dev/__properties__的文件描述符,size
表示 prop_area 内存区域的大小,该值固定为 32768。

service_start 第 13 行调用 add_environment 将获取的属性区域信息添加到环境变量中。但
是此处的环境变量实际上是定义在 init.c 中的长度为 32 的静态指针数组 static const char
*ENV[32]。add_environment 函数代码如下:
点击图片以查看大图图片名称:48.png查看次数:6文件大小:16.9 KB文件 ID :85739

从上述代码易知,该函数首先从 ENV 中找到一个未填充的入口,然后将内存中分配用
来存储环境变量值的起始地址复制给该未填充的入口。

继续回到 service_start 函数,在完成其他环境变量的设置以及 gid、uid 等的设置后,会
调用 execve 执行相应的 service 进程,如下代码所示:
点击图片以查看大图图片名称:49.png查看次数:4文件大小:2.9 KB文件 ID :85740

一直没有找到 execve 的函数源码 (汗! ),从网上查了些资料,该函数最终会执行到
__libc_init_common 函数(源码位于/bionic/libc/bionic/libc_init_common.c),部分代码如下:
点击图片以查看大图图片名称:50.png查看次数:4文件大小:7.9 KB文件 ID :85741

第 5 行调用__system_properties_init 函数,该函数位于/bionic/libc/bionic/system_properties.c 文
件中,部分代码如下:
点击图片以查看大图图片名称:51.png查看次数:10文件大小:17.1 KB文件 ID :85742
点击图片以查看大图图片名称:52.png查看次数:5文件大小:3.8 KB文件 ID :85743

第 8 行获取名为 ANDROID_PROPERTY_WORKSPACE 的环境变量值,10 行从该环境变量中提取
出 prop_area 的起始地址,11-13 行获取 prop_area 内存区域大小,14 行完成内存映射并将起
始地址返回给局部变量 pa,注意此处调用 mmap 映射内存是只读的。16 行将 pa 又赋值给全
局变量__system_property_area__。

以上分析了 Android 属性系统区域内存的共享,主要是在 init 进程中将 prop_area 信息存
储到环境变量中,在进程中获取该环境变量并解析,调用 mmap 进行内存空间映射,由于此
内存空间是只读的,所以所有对属性系统区域的更改最终需要通过 init 进程来完成。由于每
个进程都会执行这个函数,__system_property_area__是一个 static 变量,所以每个进程都有一
个队里的对系统属性的引用,可以通过这个静态变量来访问系统属性区域内存信息。

Android 系统属性获取

在 Android 应用程序开发中,可以通过 android.os.Build 类来访问一些属性信息,如设备
品牌,设备名称,CPU 信息等。我们以访问 Build.BRAND 来分析 Android 系统中是如何获取
属性信息的。

android.os.Build.java 源码位于/frameworks/base/core/java/android/os/Build.java,BRAND 的
定义如下:
点击图片以查看大图图片名称:53.png查看次数:5文件大小:6.9 KB文件 ID :85744

从上述代码易知,BRAND 值时通过调用 getString(“ro.product.brand”)来获取的,getString 代码
如下:
点击图片以查看大图图片名称:54.png查看次数:8文件大小:6.4 KB文件 ID :85745

getString 通过 SystemProperties 类(/frameworks/base/core/java/android/os/SystemProperties.java)
的 get(String key, String def)获取对应属性信息,该函数代码如下:
点击图片以查看大图图片名称:55.png查看次数:6文件大小:5.2 KB文件 ID :85746
点击图片以查看大图图片名称:56.png查看次数:6文件大小:7.2 KB文件 ID :85747

第 2 行首先检查属性名称长度是否超过预定义的最大长度,PROP_NAME_MAX 值为 31,这与
/bionic/libc/include/sys/system_properties.h 中 PROP_NAME_MAX=32 其实一致的,因为一个是
通过”>”比较,一个是通过”>=”比较。第 5 行调用 native_get 函数,从函数名可知该函数时 Native
Code,在 SystemProperties.java 第 32 行有相应的声明,如下:
点击图片以查看大图图片名称:57.png查看次数:5文件大小:3.2 KB文件 ID :85749

一般来说调用 Native 函数,需要在 static 块中加载对应的库,即调用 System.loadLibrary,但
是在 SystemProperties 类中并没有显示的加载对应的库。原来在 Zygote 启动过程中会调用
startReg 来注册 JNI 函数,native_get 这个 JNI 函数就是在此时注册的。关于注册过程可以简
单分析如下。

在 app_main.cpp(/frameworks/base/cmds/app_process/app_main.cpp)的 main 函数中启动
Zygote,如下代码所示:
点击图片以查看大图图片名称:58.png查看次数:6文件大小:7.3 KB文件 ID :85750

其中 runtime 是 AppRuntime 的实例,而 AppRuntime 继承自 AndroidRuntime,AndroidRuntime
类定义在/frameworks/base/core/jni/AndroidRuntime.cpp 中。AndroidRuntime::start 函数相关代码
如下:
点击图片以查看大图图片名称:59.png查看次数:8文件大小:10.2 KB文件 ID :85751

startReg 完成相关 Native 函数的注册,该函数最终调用 register_jni_procs 完成注册过程。
register_jni_procs 代码如下:
点击图片以查看大图图片名称:60.png查看次数:6文件大小:11.6 KB文件 ID :85752

register_jni_procs 会遍历传入的 RegJNIRec 数组并调用相应的函数进行注册,这个数组定义在
AndroidRuntime.cpp 中名为 gRegJNI,其中包括了 register_android_os_SystemProperties,这个
函数定义在/frameworks/base/core/jni/android_os_SystemProperties.cpp 中,代码如下:
点击图片以查看大图图片名称:61.png查看次数:6文件大小:11.1 KB文件 ID :85753

通过 AndroidRuntime::registerNativeMethods 注册 method_table 数组参数中定义的 Native 函数。
关于 registerNativeMethods,有兴趣的可以继续分析,此处就忽略了。参数 method_table 数组
定义如下:
点击图片以查看大图图片名称:62.png查看次数:6文件大小:12.6 KB文件 ID :85754
点击图片以查看大图图片名称:63.png查看次数:5文件大小:20.0 KB文件 ID :85755

从上述 method_table 中可以发现我们分析的 String native_get(String key, String def)对应的 C++
层函数是 SystemProperties_getSS,该函数关键代码如下:
点击图片以查看大图图片名称:64.png查看次数:4文件大小:8.6 KB文件 ID :85756

SystemProperties_getSS 会调用 property_get 函数获取属性值并存入到 buf 中,最终对 buf 进行
转换并返回 jstring 类型的属性值。property_get 函数定义在/syste/core/libcutils/properties.c 文
件中,不过 该文件会根据平台的不 同定义了不同 的 property_get 函数 。若是定义 了
HAVE_LIBC_SYSTEM_PROPERTIES 宏,则对应的 property_get 函数如下:
点击图片以查看大图图片名称:65.png查看次数:5文件大小:9.4 KB文件 ID :85757
点击图片以查看大图图片名称:66.png查看次数:4文件大小:7.4 KB文件 ID :85758

HAVE_LIBC_SYSTEM_PROPERTIES 宏定义在/system/core/include/arch/linux-arm/AndroidConfig.h
头文件中,也就是对于 linux-arm 平台来说会定义此宏,而对于 freebsd-x86,linux-x86 来说,
则会定义 HAVE_SYSTEM_PROPERTY_SERVER 宏,此宏的定义对应于另一个 property_get 的实
现。此处,以 linux-arm 平台来分析 property_get 函数,如上代码所示。第 4 行调用了
__system_property_get 函数,该函数定义于/bionic/libc/bionic/system_properties.c 文件中,代码
如下:
点击图片以查看大图图片名称:67.png查看次数:5文件大小:13.7 KB文件 ID :85759

该函数通过__system_property_find 函数在系统属性内存区域查询是否存在 name 参数指定的
属性,如果查询到则会通过__system_property_read 读取属性信息。关于__system_property_find
函数,前文已经分析过了。__system_property_read 通过获取的内存地址,从内存中读取属性
信息。

以上就是在 Java 层进行属性访问所涉及的流程,总结如图 5 所示。

Android 系统属性设置

Android 除了提供属性获取函数外,当然还可以进行属性的设置操作,在 Java 层可以通
过调用 SystemProperties.set(String key, String val)函数进行属性设置,该函数代码如下:
点击图片以查看大图图片名称:68.png查看次数:12文件大小:19.7 KB文件 ID :85760
点击图片以查看大图图片名称:69.png查看次数:4文件大小:19.2 KB文件 ID :85761

2-7 行对需要设置的属性的名称以及值做长度检查。9 行调用 Native 函数 native_set。native_set对应的C++层函数为SystemProperties_set(/frameworks/base/core/jni/android_os_SystemProperties.cpp),部分代码如
下:
点击图片以查看大图图片名称:70.png查看次数:4文件大小:8.8 KB文件 ID :85762
点击图片以查看大图图片名称:71.png查看次数:4文件大小:3.4 KB文件 ID :85763

调用了 property_set 函数,该函数统一定义在/system/core/libcutils/properties.c 文件中,仍然
分析以 linux-arm 平台为分析目标,则对应的 property_set 函数代码如下:
点击图片以查看大图图片名称:72.png查看次数:4文件大小:6.5 KB文件 ID :85764

第 3 行调用了__system_property_set 函数,该函数位于/bionic/libc/bionic/system_properties.c 文
件,核心代码如下:
点击图片以查看大图图片名称:73.png查看次数:5文件大小:18.5 KB文件 ID :85765

其中 prop_msg 结构体的定义为:
点击图片以查看大图图片名称:74.png查看次数:4文件大小:7.4 KB文件 ID :85766

7-10 行 为 prop_msg 结 构 体 申 请 内 存 , 并 为 相 应 成 员 赋 值 , 其 中 cmd 赋 值 为
PROP_MSG_SETPROP,表示需要设置属性,name 为属性名称,value 为对应的属性值。11 行
调用 send_prop_msg 函数向属性系统服务的 socket 发送属性设置请求信息,send_prop_msg
函数代码如下:
点击图片以查看大图图片名称:75.png查看次数:5文件大小:25.0 KB文件 ID :85767

其中 property_service_socket 值为”/dev/socket/property_service”,正是前文分析 Android 属性系
统服务所监听的 socket。第 10 行尝试连接这个服务端 socket,14 行向该 socket 发送消息。
服务端是如何处理接受到的消息呢?

在 init.c 文件中 main 函数的无限 for 循环中有对上述 socket 事件的处理。关键代码如下:

点击图片以查看大图图片名称:76.png查看次数:4文件大小:18.8 KB文件 ID :85768
点击图片以查看大图图片名称:77.png查看次数:5文件大小:10.1 KB文件 ID :85769

5-9 行设置需要监听的 socket 文件描述符以及 socket 请求的事件。12 行调用 poll 检查是否
有期望的 soket 事件发生。16-17 行判断所发生的的 socket 事件是否是来自于属性服务的
socket。18 行调用 handle_property_set_fd 处理相应的 socket 请求,handle_property_set_fd 定义
在/system/core/init/property_service.c 文件中,部分代码如下:
点击图片以查看大图图片名称:78.png查看次数:4文件大小:2.2 KB文件 ID :85770
点击图片以查看大图图片名称:79.png查看次数:8文件大小:22.2 KB文件 ID :85771
点击图片以查看大图图片名称:80.png查看次数:5文件大小:18.9 KB文件 ID :85772
点击图片以查看大图图片名称:81.png查看次数:4文件大小:5.4 KB文件 ID :85773

第 9 行接受 socket 连接请求,13 行从 socket 中获取数据,由于 send_prop_msg 发送的数据
位 prop_msg,因此此处获得的数据便是一个 prop_msg 结构体。15-16 行根据 prop_msg 成员
cmd 值做相应操作,对于属性设置请求来说,cmd 值为 PROP_MSG_SETPROP,而实际上
handle_property_set_fd 函数也只是处理属性设置请求。19 行判断需要设置的属性名称是否
以”ctl.”开头,ctl.start 可以用来启动服务,ctl.stop 用来停止服务,ctl.restart 可以用来重启服
务。若是,则 21 行调用 check_control_perms 来检查请求属性设置进程的权限,该函数代码
如下:
点击图片以查看大图图片名称:82.png查看次数:5文件大小:20.3 KB文件 ID :85774

权限检查的基本思路是:若进程的 uid 属于 system 或者 root,则通过;若进程在 control_perms
白名单中,也可以通过检查,但是目前为止,没有用户应用程序存在于该白名单中。因此只
有具有 system 或者 root 权限才可以启动、停止以及重启服务。若通过了权限检查,则调用
handle_control_message 函数,该函数位于/system/core/init/init.c 文件中,代码如下:
点击图片以查看大图图片名称:83.png查看次数:6文件大小:20.3 KB文件 ID :85775

从上述代码可知,handle_control_message 根据属性名称来启动、停止或者重启服务,大家有
兴趣可以继续跟进。

如果请求的属性设置不是以”ctl.”开头,则会调用 check_perms 检查权限,该函数代码如
下:
点击图片以查看大图图片名称:84.png查看次数:4文件大小:14.7 KB文件 ID :85776
点击图片以查看大图图片名称:85.png查看次数:5文件大小:10.8 KB文件 ID :85777

4-5 行判断是否具有 root 权限,若有当然通过检查了。与 check_control_perms 一样,
property_perms 也是一个可以进行属性设置的白名单。若通过权限检查,则会调用 property_set
来进行属性设置操作,该函数在前文已有分析,此处略去。

通过上述分析可知,Android 属性设置最终需要通过 init 进程完成,同时还有严格的权限
检查,一般用户进程无法对 Android 属性进行设置。基本流程如图 6 所示。
点击图片以查看大图图片名称:86.png查看次数:10文件大小:20.9 KB文件 ID :85778

总结
以上分析了 Android 属性系统的初始化过程、属性区域内存空间的共享以及属性的获取
和设置。Android 属性系统通过在内核区域开辟一块共享内存使得所有进程可以共享系统运
行时所需的各种设置值。属性服务通过监听 socket 实现对属性设置的请求,其他进程需要
设置属性,必须向 init 进程提出请求,通过权限检查后,由 init 进程完成属性的设置。

注:本帖由看雪论坛志愿者PEstone 重新将PDF整理排版,若和原文有出入,以原作者附件为准
0 0
原创粉丝点击