0
点赞
收藏
分享

微信扫一扫

深入理解init_5-----属性服务(基于Android 2.2,代码源自Google)


深入理解init_5—–属性服务

Android 2.2)
我们知道,windows平台上有一个叫做注册表的东西。注册表可以存储一些类似key/value的键值对。一般而言,系统或者应用程序,会把自己的一些属性存储在注册表中,即使系统重启或者应用程序重启,他还能根据够根据之前在注册表中设置的属性,进行相应的初始化工作。Android 平台也提供了,一个类似的机制,查询和设置属性,我们可以通过用adb shell登录真机或者模拟机器,然后用getprop查看当前系统中有哪些属性。 比如我的HTC G7测试结果如下(部分属性):

这个属性是怎么实现的呢,看下面代码其中和init.c和属性服务有关的代码有下面两行:
property_init();
property_set_fd=start_property_service();

分别来看他们

1、跳转到property_service.c文件

1.1、 文件位置

/system/core/init/property_service.c

1.2关键代码分析

属性服务初始化,创建存储空间

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

在property_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件内容。再看看init_property_area是如何工作的。

static int init_property_area(void)
{
    prop_area *pa;

    if(pa_info_array)
        return -1;

/*初始化存储空间,PA_SIZE是这块存储空间的总大小,为32768字节,pa_workspace为workspace类型的结构体,下面是他的定义:
typedef_struct{

void *data;   //存储空间的起始地址
size_t size ;//存储空间的大小
int fd;  //共享内存的文件描述符

}workspace;
init_workspace函数调用Android系统提供的ashmem_create_region函数创建一块共享内存。
*/
    if(init_workspace(&pa_workspace, PA_SIZE))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

//在32768字节的存储空间里,有PA_INFO_START(1024)个字节用来存储头部信息。
    pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);

    pa = pa_workspace.data;
    memset(pa, 0, PA_SIZE);
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;

        /* plug into the lib property services */
//_system_property_area_这个变量由bionic libc库输出,
    __system_property_area__ = pa;

    return 0;
}

上面的内容比较简单,不过最后的赋值语句很重呀。system_property_area是bionic libc库中输出的一个变量,为什么在这里要给它赋值呢?
原来,虽然属性区域虽然是由init进程创建的,但Android希望其他进程也能够读取到这块内存中的内容,为了做到这一点,它便做了以下两个工作。
1) 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点在上面的代码中可以看到,init_workspace函数内部将创建这个共享内存。
2) 如何让其他进程知道这个共享内存呢?Android利用gcc的constructor属性,这个属性指明一个_libc_prenit函数,当bionic libc 库被加载时,将自动调用这个_libc_prenit,这个函数内部将完成共享内存到本地进程的映射工作。

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

2、跳转到libc_init_dynamic.c文件

2.1、文件位置

/bionic/libc/bionic/libc_init_dynamic.c

2.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)
{
    /* Read the ELF data pointer form a special slot of the
     * TLS area, then call __libc_init_common with it.
     *
     * Note that:
     * - we clear the slot so no other initializer sees its value.
     * - __libc_init_common() will change the TLS area so the old one
     *   won't be accessible anyway.
     */
    void**      tls_area = (void**)__get_tls();
    unsigned*   elfdata   = tls_area[TLS_SLOT_BIONIC_PREINIT];

    tls_area[TLS_SLOT_BIONIC_PREINIT] = NULL;

    __libc_init_common(elfdata);     //调用这个函数

    /* Setup malloc routines accordingly to the environment.
     * Requires system properties
     */
    extern void malloc_debug_init(void);
malloc_debug_init();

寻找__libc_init_common(elfdata); 函数

3、跳转到libc_init_common.c文件

3.1、文件位置

/bionic/libc/bionic/libc_init_common.c

3.2、关键代码分析

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();     //初始化客户端的属性存储区域
}

寻找 __system_properties_init(); 函数

4、跳到system_properties.c 文件

4.1、文件位置

/bionic/libc/bionic/ system_properties.c

4.2、关键代码分析

int __system_properties_init(void)
{
    prop_area *pa;
    int s, fd;
    unsigned sz;
    char *env;

    if(__system_property_area__ != ((void*) &dummy_props)) {
        return 0;
    }
/*还记得在“启动zygote”一节中提到的添加环境变量的地方吗?属性存储区域的相关信息就是在那儿添加的,这里需要取出来使用*/
    env = getenv("ANDROID_PROPERTY_WORKSPACE");
    if (!env) {
        return -1;
}
//取出属性存储区的文件描述符
    fd = atoi(env);
    env = strchr(env, ',');
    if (!env) {
        return -1;
    }
    sz = atoi(env + 1);

//映射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;
}

上面的代码中有很多地方与共享内存有关,总之,通过这种方式,客户端进程可以直接读取属性空间,但没有权限设置属性。客户端是如何设置属性的呢?

5、属性服务器的分析

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

跳到Property_service.c

5.1、文件位置

/system/core/init/Property_service.c

5.2、关键代码分析

int start_property_service(void)
{
    int fd;

/*
加载属性文件,其实就是解析这些文件的属性,然后把它设置到属性空间中去,Android系统一共提供了四个存储属性的文件,他们分别是
#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_LOCAL_OVERRIDE  “/data/local.prop”
*/
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
    /* Read persistent properties after all default values have been loaded. */

/*有一些属性是需要保存到永久介质上的。这些属性文件则由下面这个函数加载,这些文件存储在 /data/property目录下,并且这些文件的文件名必须以persist.开头。*/
 load_persistent_properties();

//创建一个socket,用于IPC通信
    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    if(fd < 0) return -1;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    return fd;
}

属性服务创建一个用来接收请求的socket,可这个请求在哪里被处理呢,实际上,在init中的for循环里面已经进行了相关处理。

6、跳到init.c,处理设置属性请求

6.1、文件位置

/system/core/init/init.c

6.2、关键代码分析

接收请求的地方在init进程中

if (ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd); //处理属性服务响应事件

当属性服务器收到客户端请求时,init会调用handle_property_set_fd进行处理,这个函数代码如下:

7、跳到Property_service.c文件

7.1、文件位置

/system/core/init/Property_service.c

7.2、关键代码分析

void handle_property_set_fd(int 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(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }




    /* Check socket options here */
//取出客户端进程的权限等属性   
 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\n",
              r, sizeof(prop_msg));
        return;
    }

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;
/*
如果是 ctl 开头的消息,则认为是控制消息,控制消息用来执行一些命令,例如用adb shell 登录后,输入 setprop ctl.start bootanim就可以查看开机动画了,如果关闭输入setprop ctl.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 pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, 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进行相关的处理。相关函数如下

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.* properties may NEVER be modified once set */
//如果属性名以ro.开头,则表示是只读的,不能设置,所以直接返回
        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项属性,如果目前属性的存储空间中已经有了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);
    }

/* If name starts with "net." treat as a DNS property. */
//有一些特殊的属性需要处理,这里主要是以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.
        */
        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);
}
/*
on Property: persist.service.adb.enable=1
    start adbd
待persist.service.adb.enable属性置为1后,就会执行start adbd这个command这是通过property_changed函数来完成的
*/
    property_changed(name, value);
    return 0;
}

属性服务端的工作已经了解,下面看客户端是如何设置属性的。

8、跳到properties.c文件

8.1、文件位置

/system/core/libcutils/properties.c

8.2、关键代码分析

客户端通过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;
}

至此,属性服务其器就介绍完了。

以上几篇就是介绍init进程如何解析zygote,以及属性服务其的工作原理,相对来说,init.rc解析难度最大,相信大家通过以上了解,都能够有一定的认识。

文档参考:

整理抄录自 — 《深入理解Android卷1》,邓凡平著。


举报

相关推荐

0 条评论