简介
在”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进程本身了.