Android4.4属性系统-内存空间共享

一、Android4.4属性系统系列文章

Android4.4属性系统-初始化
Android4.4属性系统-系统服务
Android4.4属性系统-内存空间共享
Android4.4属性系统-属性获取
Android4.4属性系统-属性设置
Android4.4-属性的使用总结

二、写在前面-如何阅读本系列文章

本系列文章大部分是对源码的解析和注释,所以读起来枯燥无味,并且杂乱,这是阅读系统源码无法避免的,如果你有条件,可以点击下载Android4.4源码,阅读源码可以使用eclise,AndroidStudio,vim等。

文章的章节安卓是按照代码模块区分的,例如init进程代码,libcutils代码是按章节来区分,但不同模块的代码之间是有关联的,阅读时需要经常跳转,通过搜索功能进行页内搜索即可

三、内存空间共享

通过前文分析可知,Android 系统属性是通过在内核空间中开辟的一个共享内存来存储
属性信息的,那么在整个系统中,其他进程是如何读取这块内存并映射到当前进程空间中的
呢?

3.1 属性服务的启动和停止

system/core/init/init.c

//处理控制消息函数,参数<动作,服务名称>,属性服务名称是property_service
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);
    }
}

//启动指定的服务
static void msg_start(const char *name)
{   
    struct service *svc = NULL;
    char *tmp = NULL;
    char *args = NULL;
    
    //根据name查询系统中的服务
    if (!strchr(name, ':'))
        svc = service_find_by_name(name);
    else {
        tmp = strdup(name);
        if (tmp) { 
            args = strchr(tmp, ':');
            *args = '\0';
            args++;
            
            svc = service_find_by_name(tmp);
        }
    }
    
    if (svc) {
        service_start(svc, args);  //启动查找到的服务
    } else {
        ERROR("no such service '%s'\n", name);
    }
    if (tmp)
        free(tmp);
}

//启动服务
void service_start(struct service *svc, const char *dynamic_args)
{
    //省略部分代码
    ...
    pid = fork();  //fork 出子进程

    if (pid == 0) {  //判断是否是子进程,fork()返回0表示是子进程
        struct socketinfo *si;
        struct svcenvinfo *ei;
        char tmp[32];
        int fd, sz;

        umask(077);
        if(!strcmp(svc->name, "zygote")){
            BOOTINFO("service_start name is %s \n", svc->name);
        }
        if (properties_inited()) {  //properties_inited 函数判断属性区域是否已经初始化
            get_property_workspace(&fd, &sz);  //获取prop_area 起始地址以及大小
            sprintf(tmp, "%d,%d", dup(fd), sz);\
            //add_environment 将获取的属性区域信息添加到环境变量中
            //但是此处的环境变量实际上是定义在 init.c 中的长度为 32 的静态指针数组 static const char
*ENV[32]
            add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        }

        for (ei = svc->envvars; ei; ei = ei->next)
            add_environment(ei->name, ei->value);

        setsockcreatecon(scon);
        //省略部分代码
        ....
         setpgid(0, getpid()); //设置pid,gid
        
        //省略部分代码
        ....
        // execve 执行相应的 service 进程,该函数最终会执行到
        //__libc_init_common 函数(源码位于bionic/libc/bionic/libc_init_common.cpp)
        execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
}

const char *ENV[32];
/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
{
    int n;

    for (n = 0; n < 31; n++) {
        if (!ENV[n]) {  //找到一个未填充的入口
            size_t len = strlen(key) + strlen(val) + 2; //计算key+value的总长度len
            char *entry = malloc(len);  //分配一块len大小的内存区域
            snprintf(entry, len, "%s=%s", key, val);
            ENV[n] = entry;  //将内存中分配用来存储环境变量值的起始地址复制给该未填充的入口
            return 0;
        }
    }

    return 1;
}

3.2 property_service.c

system/core/init/property_service.c

//判断属性区域是否已经初始化
int properties_inited(void)
{
    return property_area_inited;
}

//获取prop_area 起始地址以及大小
//pa_workspace 在 init_workspace 函数中初始化,fd 为/dev/__properties__的文件描述符,size
//表示 prop_area 内存区域的大小,该值固定为 PA_SIZE=128K,定义在_system_properties.h
void get_property_workspace(int *fd, int *sz)
{
    *fd = pa_workspace.fd;
    *sz = pa_workspace.size;
}


3.3 bionic/libc库

bionic/libc/bionic/libc_init_static.cpp

void __libc_init_common(KernelArgumentBlock& args) {
  // Initialize various globals.
  environ = args.envp;
  errno = 0;
  __libc_auxv = args.auxv;
  __progname = args.argv[0] ? args.argv[0] : "<unknown>";
  __abort_message_ptr = args.abort_message_ptr;

  // AT_RANDOM is a pointer to 16 bytes of randomness on the stack.
  __stack_chk_guard = *reinterpret_cast<uintptr_t*>(getauxval(AT_RANDOM));

  // Get the main thread from TLS and add it to the thread list.
  pthread_internal_t* main_thread = __get_thread();
  main_thread->allocated_on_heap = false;
  _pthread_internal_add(main_thread);

  //调用__system_properties_init,位于system_properties.c
  __system_properties_init(); // Requires 'environ'.
}

bionic/libc/bionic/system_properties.c

//属性系统初始化
int __system_properties_init()
{
    return map_prop_area();
}

//初始化可读写的共享内存区域
static int map_prop_area_rw()
{
    prop_area *pa; //prop_area结构体定义在该文件中
    int fd;
    int ret;

    /* dev is a tmpfs that we can use to carve a shared workspace
     * out of, so let's do that...
     */
    //dev是一个临时文件系统,我们可以用来定制共享工作区
    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;
    }
    //fcntl 根据文件描述词来操作文件特性,给 init_workspace 中创建的文件描述符设置文件描述符标记
    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0)
        goto out;

    if (ftruncate(fd, PA_SIZE) < 0)
        goto out;

    pa_size = PA_SIZE;  //设置映射内存空间大小
    pa_data_size = pa_size - sizeof(prop_area);  //数据空间大小=总空间-prop_area结构体占用的空间
    compat_mode = false;
    /*映射为共享内存,mmap将一个文件或者其它对象映射进内存
      *这里是关键的部分,关于mmap可以参考(https://blog.csdn.net/yangle4695/article/details/52139585)
    */
    /*参数<映射的内存起始地址为NULL=0,映射大小,可读|可写,
      *MAP_SHARED :对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享
      * fd文件描述符, offset:表示被映射对象(即文件)从那里开始对映,通常都是用0>
      * 返回被映射区的指针pa
      */
    pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(pa == MAP_FAILED)
        goto out;
    
    memset(pa, 0, pa_size);  //初始化映射内存区域为 0
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;
    /* reserve root node */
    pa->bytes_used = sizeof(prop_bt);

    /* plug into the lib property services */
    //将 pa 赋值给__system_property_area__
    __system_property_area__ = pa;

    close(fd);
    return 0;

out:
    close(fd);
    return -1;
}

//查询prop
const prop_info *__system_property_find(const char *name)
{
    if (__predict_false(compat_mode)) {
        return __system_property_find_compat(name);
    }
    return find_property(root_node(), name, strlen(name), NULL, 0, false);
}

//前面介绍过了,属性的存储结构为特里结构+二叉树结构,所以查找属性其实就是
//特里结构+二叉树结构的遍历 
static const prop_info *find_property(prop_bt *trie, const char *name,
        uint8_t namelen, const char *value, uint8_t valuelen,
        bool alloc_if_needed)
{
    const char *remaining_name = name;

    while (true) {
        char *sep = strchr(remaining_name, '.');
        bool want_subtree = (sep != NULL);
        uint8_t substr_size;

        prop_bt *root;

        if (want_subtree) {
            substr_size = sep - remaining_name;
        } else {
            substr_size = strlen(remaining_name);
        }

        if (!substr_size)
            return NULL;

        if (trie->children) {
            root = to_prop_obj(trie->children);
        } else if (alloc_if_needed) {
            root = new_prop_bt(remaining_name, substr_size, &trie->children);
        } else {                                                                               
            root = NULL;
        }

        if (!root)
            return NULL;

        trie = find_prop_bt(root, remaining_name, substr_size, alloc_if_needed);
        if (!trie)
            return NULL;

        if (!want_subtree)
            break;

        remaining_name = sep + 1;
    }

    if (trie->prop) {
        return to_prop_obj(trie->prop);
    } else if (alloc_if_needed) {
        return new_prop_info(name, namelen, value, valuelen, &trie->prop);
    } else {
        return NULL;
    }
}

四、总结

以上分析了 Android 属性系统区域内存的共享,主要是在 init 进程中将 prop_area 信息存储到环境变量中,在进程中获取该环境变量并解析,调用 mmap 进行内存空间映射,由于此内存空间是只读的,所以所有对属性系统区域的更改最终需要通过 init 进程来完成。由于每个进程都会执行这个函数,system_property_area是一个 static 变量,所以每个进程都有一个对系统属性的引用,可以通过这个静态变量来访问系统属性区域内存信息。

五、参考

Android属性系统
Linux 内存映射函数 mmap
trie-特里结构
Linux 内存映射函数 mmap()函数详解