您的位置:首页 > 移动开发 > Android开发

Android 5.1 property属性系统分析

2017-07-23 22:43 393 查看

简介

在”init进程分析”系统文章中,主要介绍了init进程如何处理rc文件,没有过多讲解init进程启动的相关核心服务.例如Android属性系统等,接下来的会逐个讲解init进程启动的核心android服务.
Android属性系统其实可以理解为键值对:属性名字和属性值;很类似于windows上的注册表.
我们可以通过在adb shell里敲入getprop命令来获取当前系统的所有属性内容:
1
2
3
4
5
6
7
8

root@generic_x86_64:/ # getprop
[ARGH]: [ARGH]
[dalvik.vm.dex2oat-Xms]: [64m]
[dalvik.vm.dex2oat-Xmx]: [512m]
[dalvik.vm.heapsize]: [64m]
[dalvik.vm.image-dex2oat-Xms]: [64m]
[dalvik.vm.image-dex2oat-Xmx]: [64m]
...................................

我们还可以敲入类似“getprop 属性名”的命令来获取特定属性的值。另外,设置属性值的方法也很简单,只需敲入“setprop 属性名 新值”命令即可。
Android 属性机制如下图所示:



大部分属性是记录在某些文件中的,Android系统会在init进程启动的时候,加载这些文件,初始化属性系统.那些属性键值对是存储在一块儿共享内存中的,所有的属性都可以直读取,但是不能直接设置属性.设置属性的时候,必须依赖于property service.实际上就是socket通信.
总的来说有以下特点:
1
2
3
4
5
6

1)  系统一启动就会从若干属性脚本文件中加载属性内容;
2)  系统中的所有属性(key/value)会存入同一块共享内存中;
3)  系统中的各个进程会将这块共享内存映射到自己的内存空间,这样就可以直接读取属性内容了;
4)  系统中只有一个实体可以设置、修改属性值,它就是属性服务(Property Service);
5)  不同进程只可以通过socket方式,向属性服务发出修改属性值的请求,而不能直接修改属性值;
6)  共享内存中的键值内容会以一种字典树的形式进行组织。

存储属性的文件:

1
2
3
4
5

/default.prop
/system/build.prop
/system/default.prop(该文件不一定存在)
/data/local.prop
/data/property目录里的若干脚本

以ro开头的属性都是只读属性,以persist开头的属性,一般都是从/data/property目录中加载的.
个人理解属性系统有两个作用:

1
2

1,设置属性触发相应动作
2,作为程序中的判断条件

初始化共享内存

属性是存储在共享内存中的,而要在使用共享内存之前呢,又必须要先初始化共享内存.初始化之后,肯定要加载那些存储属性的文件等等.这些都是init进程中完成的.
init.c 的main函数中调用了property_init函数
1
23
4

void property_init(void)
{
init_property_area();
}

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16

static int init_property_area(void)
{
if (property_area_inited)
return -1;

if(__system_property_area_init())//会以读写方式打开/dev/__properties__
return -1;

if(init_workspace(&pa_workspace, 0))//这里面会以只读方式再次打开/dev/__properties__
return -1;

fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

property_area_inited = 1;//表明共享内存已经被初始化了
return 0;
}

system_property_area_init用来初始化共享内存.
1
23
4

int __system_property_area_init()
{
return map_prop_area_rw();
}

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

static int map_prop_area_rw()
{
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
//proerty_file是 /dev/__properties__,要注意,这里是以可读可写方式打开的,这个文件描述符是给property service使用的
const int fd = open(property_filename,
O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);

if (fd < 0) {
if (errno == EACCES) {
/* for consistency with the case where the process has already
* mapped the page in and segfaults when trying to write to it
*/
abort();
}
return -1;
}

// TODO: Is this really required ? Does android run on any kernels that
// don't support O_CLOEXEC ?
const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret < 0) {
close(fd);
return -1;
}

if (ftruncate(fd, PA_SIZE) < 0) {
close(fd);
return -1;
}

pa_size = PA_SIZE;
pa_data_size = pa_size - sizeof(prop_area);
compat_mode = false;

//----------------pa_size大小为128K
void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (memory_area == MAP_FAILED) {
close(fd);
return -1;
}

prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);

/* plug into the lib property services */
__system_property_area__ = pa;//共享内存的起始地址存储在这个全局变量上

close(fd);
return 0;
}

我们可以看到,在init进程的main()函数里,打开了一个设备文件“/dev/properties”,并把它设定为128KB大小,接着调用mmap()将这块内存映射到init进程空间了。这个内存的首地址被记录在system_property_area全局变量里,以后每添加或修改一个属性,都会基于这个system_property_area变量来计算位置。
在来看下面的函数:

1
2
3
4
5
6
7
89
10
11

static int init_workspace(workspace *w, size_t size)//size传入的参数值是0
{
void *data;
int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);//以只读的方式再次打开/dev/__properties__,
if (fd < 0)
return -1;

w->size = size;
w->fd = fd;
return 0;
}

打开的句柄记录在pa_workspace.fd处,以后每当init进程调用service_start()时,会执行下面的代码
1
2
3
4
5

if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}

说白了就是把 pa_workspace.fd 的句柄记入一个名叫“ ANDROID_PROPERTY_WORKSPACE ”的环境变量去,另外size似乎没什么用,一直是0.
1
2

root@generic_x86_64:/ # echo $ANDROID_PROPERTY_WORKSPACE
8,0

存入环境变量的作用是其他进程可以很方便拿到文件描述符fd,利用这个fd就可以读取属性值了.
为什么要两次open那个/dev/properties文件呢?是这样的:第一次open的句柄,最终是给属性服务自己用的,所以需要有读写权限;而第二次open的句柄,会被记入pa_workspace.fd,并在合适时机添加进环境变量,供其他进程使用,因此只能具有读取权限。

初始化属性系统

main()函数在设置好属性内存块之后,会调用queue_builtin_action()函数向内部的action_list列表添加一个action.后续,系统会在合适时机回调“由queue_builtin_action()的参数”所指定的property_service_init_action()函数.
1
2
3
4
5
6
7
89
10
11
12
13
14
15

static int property_service_init_action(int nargs, char **args)
{
/* read any property files on system or data and
* fire up the property service.  This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
start_property_service();
if (get_property_set_fd() < 0) {
ERROR("start_property_service() failed\n");
exit(1);
}

return 0;
}

1
2
3
4
5
6
7
89
10
11
12
13

void start_property_service(void)
{
int fd;

fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
if(fd < 0) return;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);

listen(fd, 8);
property_set_fd = fd;
}

很简单,就是创建了一个套接字,然后监听.这个套接字是UNIX域的,在/dev/socket/property_service.
这个socket是专门用来监听其他进程发来的“修改”属性值的命令的,它被设置成“非阻塞”(O_NONBLOCK)的socket。

加载属性文件

属性文件位置:

1
2
3
4
5
6

#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_VENDOR_BUILD     "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
#define PROP_PATH_FACTORY          "/factory/factory.prop"

属性文件的加载也是在init.c的main函数中.
1
2

INFO("property init\n");
property_load_boot_defaults();

core/init/Property_service.c:

1
23
4

void property_load_boot_defaults(void)
{
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);//加载/default.prop
}

这里只是加载了根目录下的default.prop.也就是ramdisk中的default.prop,那么其他属性文件却没有在main函数中看到有加载.那就只有一种可能了,就是rc文件中肯定有与属性相关的段.
init.rc中,果不其然有与属性相关的段:

1
2
3
4
5
67

on load_all_props_action
load_all_props

on late-init
...............................
trigger load_all_props_action
...............................

load_all_props是一个关键字,其处理函数为:
1
2
3
4
5
67

int do_load_all_props(int nargs, char **args) {
if (nargs == 1) {
load_all_props();
return 0;
}
return -1;
}

core/init/Property_service.c:

1
2
3
4
5
6
7
89
10
11
12

void load_all_props(void)
{
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");

load_override_properties();

/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
}

加载完所有的属性文件之后,还要进行一个很重要的操作.前面我们提到过,属性可以作为触发条件对吧,那么既然现在我们已经加载了所有的属性,那么就可以看看这些属性是否可以触发某些动作了.
init.c main函数中:

1
2

/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

与当初init.rc里记录的某action的触发条件匹配时,就把该action插入action_queue的尾部.

前面我们提到,当加载了所有的属性文件之后,会去检查属性作为触发条件的action,并把满足触发条件的action加入到action_queue链表中去.

那么还有一种很常见的情况,就是Android系统运行的时候修改属性值,这样也可能导致某些action因为触发条件满足,而被触发.那么这些是如何检测的呢?

还是看 init.c中的main函数

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

for() {
int nr, i, timeout = -1;

execute_one_command();//执行action_queue链表中的action
restart_processes();//重启那些需要重启的service

if (!property_set_fd_init && get_property_set_fd() > 0) {//第一次执行for循环的时候,这个if的条件才满足

/*
初始化属性服务的时候调用property_service_init_action函数,它内部调用start_property_service创建socket
,然后将socket描述符赋给property_set_fd
,get_property_set_fd函数用来获取这个socket描述符
*/
ufds[fd_count].fd = get_property_set_fd();

ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;//下次for循环,if条件就不满足了
}
if (!signal_fd_init && get_signal_fd() > 0) {//监听由init进程fork的子进程中哪些被杀死了
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {//组合按键
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}

............................
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents & POLLIN) {
if (ufds[i].fd == get_property_set_fd())//循环监听是否有向property socket发送请求
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())//监听是否有组合按键按下
handle_keychord();
else if (ufds[i].fd == get_signal_fd())//处理因子进程挂掉而发来的信号
handle_signal();
}
}
..................
}


处理属性设置请求

当捕获到设置属性的请求的时候,会调用handle_property_set_fd函数:
1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

void handle_property_set_fd()
{
prop_msg msg;
. . . . . .
/*后面会利用poll机制,监听这个accept返回的描述符,一旦有数据可读,就会去读取数据到msg中*/
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
. . . . . .
switch(msg.cmd) {//msg是读取到的数据
case PROP_MSG_SETPROP:
/*
#define PROP_NAME_MAX   32
#define PROP_VALUE_MAX  92
说明设置属性时,属性名和属性值的长度都是有限制的
*/
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
. . . . . .
/*根据msg.name做不同的处理*/
if(memcmp(msg.name,"ctl.",4) == 0) {
. . . . . .
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d  name:%s\n",
cr.uid, msg.name);
}
. . . . . .
close(s);
}
. . . . . .
break;
. . . . . .
}
}


处理ctr.

对于普通属性而言,主要是调用property_set()来设置属性值,但是有一类特殊属性是以“ctl.”开头的,它们本质上是一些控制命令.例如属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止某个service.如用adb shell登录后,输入setprop ctl.start bootanim就可以查看开机动画了,如果要关闭就输入setprop ctr.stop bootanim就可以了。 这种控制命令需调用handle_control_message()来处理。

当然,并不是随便谁都可以发出这种控制命令的,也就是说,不是谁都可以成功设置以“ctl.”开头的特殊属性。handle_property_set_fd()会先调用check_control_mac_perms()来检查发起方是否具有相应的权限。
1
2
3
4
5
6
7
89
10
11
12
13
14
15
16

static int check_control_mac_perms(const char *name, char *sctx)
{
/*
*  Create a name prefix out of ctl.<service name>
*  The new prefix allows the use of the existing
*  property service backend labeling while avoiding
*  mislabels based on true property prefixes.
*/
char ctl_name[PROP_VALUE_MAX+4];
int ret = snprintf(ctl_name, sizeof(ctl_name), "ctl.%s", name);

if (ret < 0 || (size_t) ret >= sizeof(ctl_name))
return 0;

return check_mac_perms(ctl_name, sctx);
}

代码中的注释也已经很清楚了,就是吧属性值加一个 “ctl.”的前缀,然后在调用check_mac_perms来真正检查权限:
1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

static int check_mac_perms(const char *name, char *sctx)
{
if (is_selinux_enabled() <= 0)//如果没有开启SEAndroid,那么直接返回,也就是说不用检查权限了
return 1;

char *tctx = NULL;
const char *class = "property_service";
const char *perm = "set";
int result = 0;

if (!sctx)
goto err;

if (!sehandle_prop)
goto err;

if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0)
goto err;

if (selinux_check_access(sctx, tctx, class, perm, (void*) name) == 0)
result = 1;

freecon(tctx);
err:
return result;
}

总的来说就是靠SEAndroid中的策略来判断是否有权限.这个和Android5.0之前的系统还是差别比较大的.想了解5.0之前的系统如何检查权限的,请阅读其源码吧.这里不在说了.针对SEAndroid,后续计划用好几篇文章来讲解.

检查完权限后,要调用handle_control_message函数来处理了:
1
2
3
4
5
6
7
89
10
11
12
13

void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else if (!strcmp(msg,"restart")) {
msg_restart(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}

如果socket发来的命令是“ctl.start”,那么就会走到msg_start(arg):

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

static void msg_start(const char *name)
{
struct service *svc = NULL;
char *tmp = NULL;
char *args = NULL;

if (!strchr(name, ':'))
svc = service_find_by_name(name);在service_list链表中查找这个service
else {
tmp = strdup(name);
if (tmp) {
args = strchr(tmp, ':');
*args = '\0';
args++;

svc = service_find_by_name(tmp);
}
}

if (svc) {
service_start(svc, args);//如果找到这个service,就启动他
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
}

service_start()常常会fork一个子进程,然后为它设置环境变量(ANDROID_PROPERTY_WORKSPACE).在init.c main函数中的for循环里面,重启service的时候,也是会调用这个函数的.
1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22

void service_start(struct service *svc, const char *dynamic_args)
{
. . . . . .
. . . . . .
pid = fork();

if (pid == 0) {
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;

umask(077);
if (properties_inited()) { get_property_workspace(&fd, &sz); sprintf(tmp, "%d,%d", dup(fd), sz); add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); }
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
. . . . . .

其中 get_property_workspace() 的代码如下:
1
2
3
4
5

void get_property_workspace(int *fd, int *sz)
{
*fd = pa_workspace.fd;
*sz = pa_workspace.size;
}

大家还记得前一篇文章介绍init_workspace()时,把打开的句柄记入pa_workspace.fd的句子吧,现在就是在用这个句柄。 这个句柄就是以可读方式打开/dev/properties,详情请看前一篇文章吧.

一切准备好后,service_start()会在fork的子进程中调用execve(),执行svc->args[0]所指定的可执行文件,然后还要再写个属性值,表明处于runing状态.
1
2
3
4
5
6
7
89
10
11
12
13
14

void service_start(struct service *svc, const char *dynamic_args)
{
. . . . . .
. . . . . .
execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
. . . . . .
. . . . . .
svc->time_started = gettime();
svc->pid = pid;
svc->flags |= SVC_RUNNING;

if (properties_inited())
notify_service_state(svc->name, "running");
}

其中的notify_service_state()的代码如下:
1
2
3
4
5
6
7
89

void notify_service_state(const char *name, const char *state)
{
char pname[PROP_NAME_MAX];
int len = strlen(name);
if ((len + 10) > PROP_NAME_MAX)
return;
snprintf(pname, sizeof(pname), "init.svc.%s", name);
property_set(pname, state);
}

很简单,就是设置init.svc.servicename state,例如在 adb shell中:
1
2
3
4
5
6
7
8

root@generic_x86_64:/ # getprop |grep  init.
[init.svc.adbd]: [running]
[init.svc.bootanim]: [stopped]
[init.svc.debuggerd64]: [running]
[init.svc.debuggerd]: [running]
[init.svc.drm]: [running]
[init.svc.fuse_sdcard]: [running]
.................................

以上是handle_control_message()处理“ctl.start”命令时的情况,相应地还有处理“ctl.stop”命令的情况,此时会调用到msg_stop():
1
2
3
4
5
6
7
89
10

static void msg_stop(const char *name)
{
struct service *svc = service_find_by_name(name);//在 service_list中查找service

if (svc) {
service_stop(svc);//停止该service
} else {
ERROR("no such service '%s'\n", name);
}
}

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
static void service_stop_or_reset(struct service *svc, int how)
{
/* The service is still SVC_RUNNING until its process exits, but if it has
* already exited it shoudn't attempt a restart yet. */
svc->flags &= ~(SVC_RESTARTING | SVC_DISABLED_START);

if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {
/* Hrm, an illegal flag.  Default to SVC_DISABLED */
how = SVC_DISABLED;
}
/* if the service has not yet started, prevent
* it from auto-starting with its class
*/
if (how == SVC_RESET) {
svc->flags |= (svc->flags & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;
} else {
svc->flags |= how;
}

if (svc->pid) {
NOTICE("service '%s' is being killed\n", svc->name);
kill(-svc->pid, SIGKILL);
notify_service_state(svc->name, "stopping");
} else {
notify_service_state(svc->name, "stopped");
}
}

停止一个service时,主要是调用kill( )来杀死服务子进程,并将init.svc.xxx属性值设为stopping。


处理属性设置命令

1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20

void handle_property_set_fd()
{
. . . . . .
if(memcmp(msg.name,"ctl.",4) == 0) {
. . . . . .
} else {
if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d  name:%s\n",
cr.uid, msg.name);
}
. . . . . .
close(s);
}
. . . . . .
break;
. . . . . .
}
}

要设置普通属性,同样要检查权限:
1
2
3
4
5
6
7
89
10
11
12
13
14

/*
* Checks permissions for setting system properties.
* Returns 1 if uid allowed, 0 otherwise.
*/
static int check_perms(const char *name, char *sctx)
{
int i;
unsigned int app_id;

if(!strncmp(name, "ro.", 3))
name +=3;

return check_mac_perms(name, sctx);
}

实际上还是通过调用check_mac_perms函数来检查权限,和前面ctl.一样.同样要注意和Android 5.0之前的系统的区别.

权限检查通过之后,就可以真正设置属性了。前面我们已经说过,只有Property Service(即init进程)可以写入属性值,而普通进程最多只能通过socket向Property Service发出设置新属性值的请求,权限检查通过后,最终还得靠Property Service来写.
1
2
3
4
5
6
7
89
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

int property_set(const char *name, const char *value)
{
prop_info *pi;
int ret;

size_t namelen = strlen(name);
size_t valuelen = strlen(value);

/*判断属性名和属性值的合法性,其实就是判断长度了,前面也介绍了属性名和属性值的长度是有限制的*/
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;

/*查找要设置的属性,在共享内存中是否已经存在*/
pi = (prop_info*) __system_property_find(name);

/*存在的话,检查是否是以ro.开头的,是的话,返回错误,因为ro.是只读属性,不能被改写的*/
if(pi != 0) {
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;

__system_property_update(pi, value, valuelen);
} else {
/*不存在,那么就把新的属性和他的值写入共享内存*/
ret = __system_property_add(name, namelen, value, valuelen);
if (ret < 0) {
ERROR("Failed to set '%s'='%s'\n", name, value);
return ret;
}
}
/* If name starts with "net." treat as a DNS property. */
/*如果设置的是net.change,那么直接返回,因为net.change的值是由系统来设置的*/
if (strncmp("net.", name, strlen("net.")) == 0)  {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
/*如果设置的是net.开头的属性的话,还要把net.change的值设置为刚刚设置的属性名字*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
/*如果要设置persist属性的话,只有在系统将所有的默认persist属性都加载完毕后,才能设置成功。
persist属性应该是那种会存入可持久化文件的属性,
这样,系统在下次启动后,可以将该属性的初始值设置为系统上次关闭时的值*/
write_persistent_property(name, value);
} else if (strcmp("selinux.reload_policy", name) == 0 &&
strcmp("1", value) == 0) {
/*   如果将“selinux.reload_policy”属性设为“1”了,那么要重新加载SEAndroid策略。*/
selinux_reload_policy();
}
/*因为属性值发生了改变,所以要遍历action_list检查下是否满足了触发action的触发条件。*/
property_changed(name, value);
return 0;
}

将满足触发条件的action,添加到action_queue链表中去,然后在init.c main函数中的for循环中,就会被执行了.

1
2
3
4
5

void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
queue_property_triggers(name, value);
}

1
2
3
4
5
67

static int queue_property_triggers_action(int nargs, char **args)
{
queue_all_property_triggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
}


总结

Android 5.0属性的设置相较于之前的Android系统,主要变化是在权限检查这块,其他变化到不大.一般情况下,只要添加或者修改了某个属性的值(ctl.类的属性例外),都会遍历action_list链表,看看是否会触发某些action. property service服务,其实就是init进程本身了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: