代码改变世界

《深入理解Android(卷1)》笔记 3.第三章 深入理解init

2012-12-03 17:00  ...平..淡...  阅读(861)  评论(0编辑  收藏  举报

知识点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...