zoukankan      html  css  js  c++  java
  • 《深入理解Android(卷1)》笔记 3.第三章 深入理解init

    知识点7:属性服务(与注册表机制机制类似)

    在init.c中和属性服务相关的代码如下:

    //调用property_init_action方法,该方法调用property_init方法
    queue_builtin_action(property_init_action, "property_init");
    
    //调用property_service_init_action方法,该方法会调用start_property_service方法
    queue_builtin_action(property_service_init_action, "property_service_init");

    下面来分析它们。

    1. 属性服务的初始化

    1.1创建存储空间

    首先看property_init方法

    void property_init(void) {
        init_property_area();  //初始化属性存储区域
        load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT); //加载default.prop文件
    }

    property_init方法首先调用init_property_area()方法创建一块用于存储属性的储存区域,然后调用load_properties_from_file方法加载default.prop文件中的内容。

    init_property_area方法

    staticintinit_property_area(void)
    {
        prop_area *pa;
     
        if(pa_info_array)
            return -1;
      /*
        a. PA_SIZE描述存储空间的总大小,为32768字节,pa_workspace是workspace结构体的对象。
        workspace结构体定义:
        typedef struct {
             void *data;
             size_t size;
             int fd;
        }workspace;
        b. init_workspace函数调用ashmem_create_region函数创建一块共享内存。
      */
        if(init_workspace(&pa_workspace,PA_SIZE))
            return -1;
     
        //设置共享内存执行结束后关闭
        fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
     
        //在PA_SIZE大小的存储空间中,有PA_INFO_START(1024)个字节用来存储头部信息。
        pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
     
        pa = pa_workspace.data;
        memset(pa, 0, PA_SIZE); //初始化内存地址中所有值为0
        pa->magic = PROP_AREA_MAGIC;
        pa->version = PROP_AREA_VERSION;  //设置属性共享内存版本号
     
        /* plug into the lib property services */
        __system_property_area__ = pa;     //(e)很重要
         property_area_inited = 1;
        return 0;
    }

    (e)  __system_property_area__bionic libc库中的一个变量,是属性空间的入口地址

    虽然属性区域是init进程创建的,但是android希望其他进程也能读取这块内存中的东西。为了达到此目的,需要做一下工作:

    (1)把属性区域创建在共享内存中上,因为共享内存是跨进程的。(这个共享内存由init_workspace函数内部调用的ashmem_create_region函数创建)

    (2)让其他进程知道这个共享内存。

    方法:
    原理:利用gcc的constructor属性,这个属性指明了一个__libc_prenit函数(这个函数内部就将完成共享内存到本地进程的映射工作)。
    用法:当bionic libc库被加载时,将自动调用__libc_prenit函数。
    这样在bionic libc动态库被装载时,系统属性缓冲区地址就被确定了,后续的API调用就能找对位置了。

    1.2         客户端进程获取储存空间

    /* We flag the __libc_preinit function as a constructor to ensure
     * that its address is listed in libc.so's .init_array section.
     * This ensures that the function is called by the dynamic linker
     * as soon as the shared library is loaded.
     */
    
    void __attribute__((constructor)) __libc_prenit(void);
    //----------------------------------------------------
    void __libc_prenit(void)
    
    {
      ...
      __libc_init_common(elfdata);  //调用此函数
      ...
    }
    //----------------------------------------------------
    void __libc_init_common(uintptr_t *elfdata)
    {
        int     argc = *elfdata;
        char**  argv = (char**)(elfdata + 1);
        char**  envp = argv + argc + 1;
     
        pthread_attr_t             thread_attr;
        static pthread_internal_t  thread;
        static void*               tls_area[BIONIC_TLS_SLOTS];
    
        /* setup pthread runtime and maint thread descriptor */
    
        unsigned stacktop = (__get_sp() & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
        unsigned stacksize = 128 * 1024;
        unsigned stackbottom = stacktop - stacksize;
    
     
        pthread_attr_init(&thread_attr);
        pthread_attr_setstack(&thread_attr, (void*)stackbottom, stacksize);
        _init_thread(&thread, gettid(), &thread_attr, (void*)stackbottom);
        __init_tls(tls_area, &thread);
    
        /* clear errno - requires TLS area */
        errno = 0;
    
        /* set program name */
        __progname = argv[0] ? argv[0] : "<unknown>";
    
        /* setup environment pointer */
        environ = envp;
    
        /* setup system properties - requires environment */
        __system_properties_init();   //初始化客户端的属性存储空间
    }
    
    //------------------------------------------------------
    int __system_properties_init(void)
    {
        prop_area *pa;
        int s, fd;
        unsigned sz;
        char *env;
    
        if(__system_property_area__ != ((void*) &dummy_props)) {
            return 0;
        }
    
      //取出前面曾添加的环境变量(属性存储区域的相关信息就是在那儿添加的)。
        env = getenv("ANDROID_PROPERTY_WORKSPACE");//获取环境变量值
        if (!env) {
            return -1;
        }
        fd = atoi(env);//获取fd
        env = strchr(env, ',');
        if (!env) {
            return -1;
        }
        sz = atoi(env + 1);//获取size

      /*
        映射init创建的那块内存到本地进程空间,这样本地进程就可以使用这块共享内存了。注意,映射的时候制定了PROT_READ属性,所以客户端进程只能读属性,而不能设置属性   */ pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0); if(pa == MAP_FAILED) { return -1; } if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION)) { munmap(pa, sz); return -1; } __system_property_area__ = pa; return 0; }

    通过这种方式,客户端可以直接读取属性空间,但没有权限设置属性。客户端进程又是如何设置属性的呢?客户端只能通过与属性服务器交互来设置属性。

    如何交互呢,可以归纳成:

    属性客户端

    调用property_set("ct1.start","bootanim")方法,该方法调用send_prop_msg方法通过普通的TCP(SOCK_STREAM)进行通讯。

    服务端

    a. init.c的main方法中,调用start_property_service方法(b) 和 handle_property_set_fd方法(c)。
    
    b. 创建SOCK_STREAM套接字;调用listen方法监听
    
    c. 接收socket连接请求;接收请求数据;处理属性请求数据(switch代码段)
    

     

    下面先来分析服务端的执行过程。 

    2.属性服务器的分析

    2.1 启动属性服务器

    init进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性。属性服务器由start_property_service函数启动。

    void start_property_service(void)
    {
      int fd;
      /*
        加载属性文件,其实就是解析这些文件中的属性,然后把它设置到属性空间中去。以下为android提供是四个存储属性文件:    /default.prop(已在property_init方法中加载过了) 、/system/build.prop、/system/default.prop、/data/local.prop   */ load_properties_from_file(PROP_PATH_SYSTEM_BUILD);// "/system/build.prop" load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);//"/system/default.prop" load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);//"/data/local.prop" // 有一些属性是需要保存到永久介质上的,由下面这个函数加载。这些文件存储在/data/property目录下,文件名必须以persist.开头。   load_persistent_properties();   //创建一个socket,用于IPC通信。 fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);//"property_service" if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); listen(fd, 8); property_set_fd = fd; }

    这个socket在哪里被处理呢?事实上是在init中的for循环处已经进行相关处理了。在2.2中分析。

    2.2 处理设置属性请求

     接收请求的地方在init进程for循环中。

    if (ufds[i].revents == POLLIN) {
       if (ufds[i].fd == get_property_set_fd())
          handle_property_set_fd();

    当属性服务器接收到客户端请求后,会调用handle_property_set_fd方法进行处理。代码如下:

    void handle_property_set_fd()
    {
        prop_msg msg;
        int s;
        int r;
        int res;
        struct ucred cr;
        struct sockaddr_un addr;
        socklen_t addr_size = sizeof(addr);
        socklen_t cr_size = sizeof(cr);
    
        //先接收TCP连接
        if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
            return;
        }
    
        /*取出客户端进程的权限等属性*/
        if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
            close(s);
            ERROR("Unable to recieve socket options\n");
            return;
        }
        //接收请求数据
        r = recv(s, &msg, sizeof(msg), 0);
        close(s);
        if(r != sizeof(prop_msg)) {
            ERROR("sys_prop: mis-match msg size recieved: %d expected: %d errno: %d\n",
                  r, sizeof(prop_msg), errno);
            return;
        }
    
        switch(msg.cmd) {
        case PROP_MSG_SETPROP:
            msg.name[PROP_NAME_MAX-1] = 0;
            msg.value[PROP_VALUE_MAX-1] = 0;
    
            /*
               如果是ct1开头的消息,则认为是控制消息,控制消息用来执行一些命令。例如用adb shell登陆后,输入setprop ct1.start bootanim就可以查看开机动画了,如果要关闭就输入setprop ct1.stop bootanim。很有趣哦~
            */
            if(memcmp(msg.name,"ctl.",4) == 0) {
                if (check_control_perms(msg.value, cr.uid, cr.gid)) {
                    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)) {
                    //然后调用property_set设置
                    property_set((char*) msg.name, (char*) msg.value);
                } else {
                    ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                          cr.uid, msg.name);
                }
            }
            break;
        default:
            break;
        }
    }        

    然后当客户端的权限满足要求时,init就调用property_set进行相关处理。代码如下:

    property_set
    int property_set(const char *name, const char *value)
    {
        prop_area *pa;
        prop_info *pi;
    
        int namelen = strlen(name);
        int valuelen = strlen(value);
    
        if(namelen >= PROP_NAME_MAX) return -1;
        if(valuelen >= PROP_VALUE_MAX) return -1;
        if(namelen < 1) return -1;
    
        //从属性存储空间中寻找是否已经存在该属性。
        pi = (prop_info*) __system_property_find(name);
    
        if(pi != 0) {
            //如果属性名以ro.开头,则表示是只读的,不能设置,所以直接返回。
            /* ro.* properties may NEVER be modified once set */
            if(!strncmp(name, "ro.", 3)) return -1;
    
            pa = __system_property_area__;
            update_prop_info(pi, value, valuelen); //更新该属性的值。
            pa->serial++;
            __futex_wake(&pa->serial, INT32_MAX);
        } else {
            //如果没有找到对应的属性,则认为是增加属性,所以需要新创建一项。注意,Android最多支持247项属性,如果达到上限,则直接返回。
            pa = __system_property_area__;
            if(pa->count == PA_COUNT_MAX) return -1;
    
            pi = pa_info_array + pa->count;
            pi->serial = (valuelen << 24);
            memcpy(pi->name, name, namelen + 1);
            memcpy(pi->value, value, valuelen + 1);
    
            pa->toc[pa->count] =
                (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
    
            pa->count++;
            pa->serial++;
            __futex_wake(&pa->serial, INT32_MAX);
        }
        //有一些特殊的属性需要特殊处理,主要是以net.change开头的属性
        /* If name starts with "net." treat as a DNS property. */
        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.
            */
            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.开头,则需要把这些值写到对应的文件中去
            write_persistent_property(name, value);
        }
        /*init.rc中执行start adbd命令的话,如下:
            on property:persist.service.adb.enable=1
                start adbd
            则会调用property_changed方法来完成。
        */
        property_changed(name, value);
        return 0;

     属性服务器的工作介绍完毕了,下面再看客户端是如何执行的。

    3.客户端发送请求

    客户端通过property_set发送请求(此 property_set与上面的 property_set不同,它是由libcutils库提供的),代码如下:

    int property_set(const char *key, const char *value)
    {
        prop_msg msg;
        unsigned resp;
        if(key == 0) return -1;
        if(value == 0) value = "";
        
        if(strlen(key) >= PROP_NAME_MAX) return -1;
        if(strlen(value) >= PROP_VALUE_MAX) return -1;
        
        msg.cmd = PROP_MSG_SETPROP;  //设置消息码为PROP_MSG_SETPROP.
        strcpy((char*) msg.name, key);
        strcpy((char*) msg.value, value);
        return send_prop_msg(&msg); //发送请求
    }
     
     
     static int send_prop_msg(prop_msg *msg)
    {
        int s;
        int r;
        //建立和属性服务器的socket连接。
        s = socket_local_client(PROP_SERVICE_NAME, 
                                ANDROID_SOCKET_NAMESPACE_RESERVED,
                                SOCK_STREAM);
        if(s < 0) return -1;
        //通过socket发送出去。
        while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
            if((errno == EINTR) || (errno == EAGAIN)) continue;
            break;
        }
        if(r == sizeof(prop_msg)) {
            r = 0;
        } else {
            r = -1;
        }
        close(s);
        return r;
    }

    over...

  • 相关阅读:
    People Picker 处理过程
    DC与GC的区别
    说明DOS命令格式的语法
    SQL Server Express的Limitation
    关于IE缓存的一些基础
    Content Deployment入门(上)
    Microsoft SQL Server版本号小结
    要研究一下的技术要点
    区分斜杠与反斜杠
    Windows Virtual PC的虚拟机与宿主机共享文件
  • 原文地址:https://www.cnblogs.com/chenbin7/p/2799597.html
Copyright © 2011-2022 走看看