Android 5.1 property属性系统分析

简介

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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值