简介
在”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 2 3 4 | void property_init(void) { init_property_area(); } |
1 2 3 4 5 6 7 8 9 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 2 3 4 | int __system_property_area_init() { return map_prop_area_rw(); } |
1 2 3 4 5 6 7 8 9 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 8 9 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 8 9 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 8 9 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 2 3 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 6 7 | on load_all_props_action load_all_props on late-init ............................... trigger load_all_props_action ............................... |
load_all_props是一个关键字,其处理函数为:
1 2 3 4 5 6 7 | 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 | 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 8 9 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 8 9 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 8 9 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 8 9 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 8 9 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 6 7 | 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进程本身了.