zoukankan      html  css  js  c++  java
  • Android 4.4 Init进程分析六 :属性服务

    ****************************************************************************

    Android 4.4 init进程分析文章链接

      Android 4.4 Init进程分析一 :Android init进程概述

      Android 4.4 Init进程分析二 :Android初始化语言

      Android 4.4 Init进程分析三:init.rc脚本文件的解析

      Android 4.4 Init进程分析四 :init.rc脚本文件的执行

      Android 4.4 Init进程分析五 :进程的终止与再启动

      Android 4.4 Init进程分析六 :属性服务

    ***************************************************************************

     

    1 前言

      1. 属性变更请求是init事件处理循环处理的另一个事件。

      2. 在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。

      3. 属性又键(key)和值(value)构成,其表现形式为“键=值”。

      4. 系统中所有运行中的进程都可以访问属性值,但仅有init进程才能修改属性值,其他进程修改属性值时,必须向init进程提出请求,最终由init进程负责修改属性值。

      5. init进程修改属性值时,首先会先检查各属性的访问权限,而后再修改属性值。当属性值修改后,若定义在init.rc文件中的某个条件得到满足,则与此条件相匹配的动作就会发生。每个动作都有一个触发器(trigger),他决定动作的执行时间,记录在“on property”关键字后的命令即被执行。

      如下图,简单描述了init进程与其他进程在访问并修改属性值的大致情形:

    2 属性初始化

      在init进程的main()函数中,调用了property_init()函数,用来初始化属性域,如下代码:

    http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1009

    1 int main(int argc, char **argv)
    2 {
    3      ....
    4     property_init();
    5      ....  
    6 }    

    再看一下property_init()函数的定义:

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#541

    1 void property_init(void)
    2 {
    3     init_property_area();
    4 }

    可以看到函数内调用了init_property_area()函数,如下定义

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#133

    static int init_property_area(void)
    {
        if (property_area_inited)
            return -1;
    
        if(__system_property_area_init())
            return -1;
    
        if(init_workspace(&pa_workspace, 0))
            return -1;
    
        fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
    
        property_area_inited = 1;
        return 0;
    }
    View Code

    这个函数会在内存中开辟一块共享内存区域,而后将其用作ashmem(Android Shared Memory),并生成属性域。外部进程可以访问这块共享内存区域,获得属性值,但它们不能通过直接访问共享内存区域的方式更改属性值。

    init_property_area()函数执行后所创建的属性域被初始化。

    在属性域完成初始化之后,就会从指定的文件中读取初始值,并设置为属性值。可以在main()函数中看到:

    http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1035

        if (!is_charger)
            property_load_boot_defaults();

    property_load_boot_defaults()函数定义:

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#546

    void property_load_boot_defaults(void)
    {
        load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
    }
    PROP_PATH_RAMDISK_DEFAULT定义为:
    #define PROP_PATH_RAMDISK_DEFAULT "/default.prop" 

    load_properties_from_file()函数定义为:

     1 static void load_properties_from_file(const char *fn)
     2 {
     3     char *data;
     4     unsigned sz;
     5 
     6     data = read_file(fn, &sz);
     7 
     8     if(data != 0) {
     9         load_properties(data);
    10         free(data);
    11     }
    12 }
    View Code

    load_properties_from_file()函数读取指定的property file内容到内存,然后调用load_properties():

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#440

     1 static void load_properties(char *data)
     2 {
     3     char *key, *value, *eol, *sol, *tmp;
     4 
     5     sol = data;
     6     while((eol = strchr(sol, '
    '))) {
     7         key = sol;
     8         *eol++ = 0;
     9         sol = eol;
    10 
    11         value = strchr(key, '=');
    12         if(value == 0) continue;
    13         *value++ = 0;
    14 
    15         while(isspace(*key)) key++;
    16         if(*key == '#') continue;
    17         tmp = value - 2;
    18         while((tmp > key) && isspace(*tmp)) *tmp-- = 0;
    19 
    20         while(isspace(*value)) value++;
    21         tmp = eol - 2;
    22         while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
    23 
    24         property_set(key, value);
    25     }
    26 }
    View Code

    在load_properties()函数中分析得到key and value,并调用property_set(key, value);将属性值写入共享内存。

    ******************************************

    再回到main()函数继续执行,可以看到如下语句:

    http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1064

    1 queue_builtin_action(property_service_init_action, "property_service_init");

    这个动作执行时,会去调用property_service_init_action()函数:

    http://androidxref.com/4.4_r1/xref/system/core/init/init.c#789

     1 static int property_service_init_action(int nargs, char **args)
     2 {
     3     /* read any property files on system or data and
     4      * fire up the property service.  This must happen
     5      * after the ro.foo properties are set above so
     6      * that /data/local.prop cannot interfere with them.
     7      */
     8     start_property_service();
     9     return 0;
    10 }

    进而调用了start_property_service()函数。用于创建启动属性服务所需要的Unix域套接字,并保存套接字描述符,如下函数定义:

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#581

     1 void start_property_service(void)
     2 {
     3     int fd;
     4 
     5     load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
     6     load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
     7     load_override_properties();
     8     /* Read persistent properties after all default values have been loaded. */
     9     load_persistent_properties();
    10 
    11     fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    12     if(fd < 0) return;
    13     fcntl(fd, F_SETFD, FD_CLOEXEC);
    14     fcntl(fd, F_SETFL, O_NONBLOCK);
    15 
    16     listen(fd, 8);
    17     property_set_fd = fd;
    18 }
    View Code

    start_property_service()函数在创建套接字之前,先读取存储在各个文件中的基本设置值,将它们设置为属性值,如上函数定义会读取以下文件:

    1 //文件
    2 #define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
    3 #define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
    4 #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
    

    当所有系统初始值设置完毕后,开始读取保存在/data/property目录中的属性值:

     1 load_persistent_properties();
     2 
     3 ==>>>
     4 
     5 static void load_persistent_properties()
     6 {
     7     DIR* dir = opendir(PERSISTENT_PROPERTY_DIR);
     8     ....
     9 }
    10 
    11 ==>>>
    12 
    13 #define PERSISTENT_PROPERTY_DIR  "/data/property"

    在/data/property目录中保存着系统运行中其他进程新生成的属性值或更改的属性值,属性的key被用作文件名,value被保存在文件中。

    在属性初始值设置完毕后,就会创建名称为  /dev/socket/property_service  的Unix域套接字。

    上述过程我们大体可以总结如下图所示:

     

    3 属性变更请求处理

    3.1 其他进程请求变更属性值

     其他进程设置属性时是调用了libcutils函数库中的property_set()方法,该方法的定义去下:

    http://androidxref.com/4.4_r1/xref/system/core/libcutils/properties.c#34

    1 int property_set(const char *key, const char *value)
    2 {
    3     return __system_property_set(key, value);
    4 }

    可以看到,property_set调用了__system_property_set方法,这个方法位于:/bionic/libc/bionic/system_properties.c文件中:

    http://androidxref.com/4.4_r1/xref/bionic/libc/bionic/system_properties.c#540

     1 int __system_property_set(const char *key, const char *value)
     2 {
     3     int err;
     4     prop_msg msg;
     5 
     6     if(key == 0) return -1;
     7     if(value == 0) value = "";
     8     if(strlen(key) >= PROP_NAME_MAX) return -1;
     9     if(strlen(value) >= PROP_VALUE_MAX) return -1;
    10 
    11     memset(&msg, 0, sizeof msg);
    12     msg.cmd = PROP_MSG_SETPROP;
    13     strlcpy(msg.name, key, sizeof msg.name);
    14     strlcpy(msg.value, value, sizeof msg.value);
    15 
    16     err = send_prop_msg(&msg);
    17     if(err < 0) {
    18         return err;
    19     }
    20 
    21     return 0;
    22 }
    View Code

    上述方法最终调用到send_prop_msg()函数:

    http://androidxref.com/4.4_r1/xref/bionic/libc/bionic/system_properties.c#send_prop_msg

     1 static int send_prop_msg(prop_msg *msg)
     2 {
     3     struct pollfd pollfds[1];
     4     struct sockaddr_un addr;
     5     socklen_t alen;
     6     size_t namelen;
     7     int s;
     8     int r;
     9     int result = -1;
    10 
    11     s = socket(AF_LOCAL, SOCK_STREAM, 0);
    12     if(s < 0) {
    13         return result;
    14     }
    15 
    16     memset(&addr, 0, sizeof(addr));
    17     namelen = strlen(property_service_socket);
    18     strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);
    19     addr.sun_family = AF_LOCAL;
    20     alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
    21 
    22     if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {
    23         close(s);
    24         return result;
    25     }
    26 
    27     r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));
    28 
    29     if(r == sizeof(prop_msg)) {
    30         // We successfully wrote to the property server but now we
    31         // wait for the property server to finish its work.  It
    32         // acknowledges its completion by closing the socket so we
    33         // poll here (on nothing), waiting for the socket to close.
    34         // If you 'adb shell setprop foo bar' you'll see the POLLHUP
    35         // once the socket closes.  Out of paranoia we cap our poll
    36         // at 250 ms.
    37         pollfds[0].fd = s;
    38         pollfds[0].events = 0;
    39         r = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 /* ms */));
    40         if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {
    41             result = 0;
    42         } else {
    43             // Ignore the timeout and treat it like a success anyway.
    44             // The init process is single-threaded and its property
    45             // service is sometimes slow to respond (perhaps it's off
    46             // starting a child process or something) and thus this
    47             // times out and the caller thinks it failed, even though
    48             // it's still getting around to it.  So we fake it here,
    49             // mostly for ctl.* properties, but we do try and wait 250
    50             // ms so callers who do read-after-write can reliably see
    51             // what they've written.  Most of the time.
    52             // TODO: fix the system properties design.
    53             result = 0;
    54         }
    55     }
    56 
    57     close(s);
    58     return result;
    59 }
    View Code

    send_prop_msg()函数可以看到:建立与属性服务/dev/socket/property_service 的socket连接,并发送要修改的属性信息。

    3.2 init进程处理属性变更

      init进程就收到属性变更请求后,init进程就会调用handle_property_set_fd()函数:

     1 int main(int argc, char **argv)
     2 {  
     3     ....
     4       for (i = 0; i < fd_count; i++) {
     5             if (ufds[i].revents == POLLIN) {
     6                 if (ufds[i].fd == get_property_set_fd())
     7                     handle_property_set_fd();// 处理属性变更请求
     8                 else if (ufds[i].fd == get_keychord_fd())
     9                     handle_keychord();
    10                 else if (ufds[i].fd == get_signal_fd())
    11                     handle_signal();
    12             }
    13         }
    14     ....
    15 }

    我们来看一下handle_property_set_fd()函数的定义:

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#358

     1 void handle_property_set_fd()
     2 {
     3     ....
     4 
     5     /* Check socket options here */
     6     if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { //分析1
     7         close(s);
     8         ERROR("Unable to receive socket options
    ");
     9         return;
    10     }
    11 
    12     ....
    13     switch(msg.cmd) {
    14     case PROP_MSG_SETPROP:
    15         msg.name[PROP_NAME_MAX-1] = 0;
    16         msg.value[PROP_VALUE_MAX-1] = 0;
    17         ...
    18         if(memcmp(msg.name,"ctl.",4) == 0) {
    19             // Keep the old close-socket-early behavior when handling
    20             // ctl.* properties.
    21             close(s);
    22             if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) { //分析2
    23                 handle_control_message((char*) msg.name + 4, (char*) msg.value);
    24             } else {
    25                 ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d
    ",
    26                         msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
    27             }
    28         } else {
    29             if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) { //分析3
    30                 property_set((char*) msg.name, (char*) msg.value); //分析4
    31             } else {
    32                 ERROR("sys_prop: permission denied uid:%d  name:%s
    ",
    33                       cr.uid, msg.name);
    34             }
    35 
    36             // Note: bionic's property client code assumes that the
    37             // property server will not close the socket until *AFTER*
    38             // the property is written to memory.
    39             close(s);
    40         }
    41     ...
    42     }
    43 }
    View Code

    分析1:执行handle_property_set_fd()函数时,会先从套接字获取SO_PEERCRED值,以便检查传递信息的进程的访问权限。在struct ucred结构体中,存储着传递信息的进程uid、pid和gid值。通过此结构体中的值,以及消息的类型,检查进程的访问权限。

    分析2:在属性消息中,以“ctl”开头的消息并非请求更改系统属性值的消息,而是请求进程启动与终止的消息。在代码中调用check_control_perms()函数检查访问权限,仅有system server、root、以及相关进程才能使用ctl消息,终止或启动进程

    分析3:其他消息都被用来更改系统属性值,调用check_perms()函数检查访问权限。各属性的访问权限采用Linux的uid进程区分,其定义如下代码所示。若在系统运行中变更属性设置,应充分考虑各属性的访问权限。

    http://androidxref.com/4.4_r1/xref/system/core/init/property_service.c#60

     1 struct {
     2     const char *prefix;
     3     unsigned int uid;
     4     unsigned int gid;
     5 } property_perms[] = {
     6     { "net.rmnet0.",      AID_RADIO,    0 },
     7     { "net.gprs.",        AID_RADIO,    0 },
     8     { "net.ppp",          AID_RADIO,    0 },
     9     { "net.qmi",          AID_RADIO,    0 },
    10     { "net.lte",          AID_RADIO,    0 },
    11     { "net.cdma",         AID_RADIO,    0 },
    12     { "ril.",             AID_RADIO,    0 },
    13     { "gsm.",             AID_RADIO,    0 },
    14     { "persist.radio",    AID_RADIO,    0 },
    15     { "net.dns",          AID_RADIO,    0 },
    16     { "sys.usb.config",   AID_RADIO,    0 },
    17     { "net.",             AID_SYSTEM,   0 },
    18     { "dev.",             AID_SYSTEM,   0 },
    19     { "runtime.",         AID_SYSTEM,   0 },
    20     { "hw.",              AID_SYSTEM,   0 },
    21     { "sys.",             AID_SYSTEM,   0 },
    22     { "sys.powerctl",     AID_SHELL,    0 },
    23     { "service.",         AID_SYSTEM,   0 },
    24     { "wlan.",            AID_SYSTEM,   0 },
    25     { "bluetooth.",       AID_BLUETOOTH,   0 },
    26     { "dhcp.",            AID_SYSTEM,   0 },
    27     { "dhcp.",            AID_DHCP,     0 },
    28     { "debug.",           AID_SYSTEM,   0 },
    29     { "debug.",           AID_SHELL,    0 },
    30     { "log.",             AID_SHELL,    0 },
    31     { "service.adb.root", AID_SHELL,    0 },
    32     { "service.adb.tcp.port", AID_SHELL,    0 },
    33     { "persist.sys.",     AID_SYSTEM,   0 },
    34     { "persist.service.", AID_SYSTEM,   0 },
    35     { "persist.security.", AID_SYSTEM,   0 },
    36     { "persist.service.bdroid.", AID_BLUETOOTH,   0 },
    37     { "selinux."         , AID_SYSTEM,   0 },
    38     { NULL, 0, 0 }
    39 };
    View Code

    分析4:最后调用property_set()函数更改属性值。若没有问题,接着调用property_changed()函数

    property_changed()函数的定义如下:

    http://androidxref.com/4.4_r1/xref/system/core/init/init.c#412

    1 void property_changed(const char *name, const char *value)
    2 {
    3     if (property_triggers_enabled)
    4         queue_property_triggers(name, value);
    5 }

    http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#517

     1 void queue_property_triggers(const char *name, const char *value)
     2 {
     3     struct listnode *node;
     4     struct action *act;
     5     list_for_each(node, &action_list) {
     6         act = node_to_item(node, struct action, alist);
     7         if (!strncmp(act->name, "property:", strlen("property:"))) {
     8             const char *test = act->name + strlen("property:");
     9             int name_length = strlen(name);
    10 
    11             if (!strncmp(name, test, name_length) &&
    12                     test[name_length] == '=' &&
    13                     (!strcmp(test + name_length + 1, value) ||
    14                      !strcmp(test + name_length + 1, "*"))) {
    15                 action_add_queue_tail(act);
    16             }
    17         }
    18     }
    19 }
    View Code

    queue_property_triggers()函数中,去action_list中检索出与这个属性值相关的action加入到action_queue,等待执行。

    在init.rc脚本文件中,记录着某个属性改变后要采取的动作,动作执行的条件以“on property:<key> = <value>”形式给出。当某个条件相关的键值被设定后,与该条件相关的触发器就会被触发。比如:

    # adbd on at boot in emulator
    on property:ro.kernel.qemu=1
        start adbd

    当ro.kernel.qemu属性值被设置为1时,adbd服务就会启动。

     到这里属性变更的处理就分析完了!

     大体过程如下:

    ========

    88888888

    ========

  • 相关阅读:
    IE下全局对象报 脚本错误提示“对象不支持此属性或方法”解决方案
    IE6、7下inline-block不起作用
    IE下图片切换的时候,图片总是切换不成功---根本问题是IE缓存图片
    Fiddler 跟踪 手机页面数据包
    Regular Expression Matching——没理解的动态规划
    常见排序算法分析
    Flyweight模式(亨元模式)
    组合模式(Composite Pattern)
    装饰者模式(不太理解的设计模式)
    适配器模式
  • 原文地址:https://www.cnblogs.com/roger-yu/p/12206752.html
Copyright © 2011-2022 走看看