Android4.4属性系统-初始化

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

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

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

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

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

三、安卓属性系统介绍

Android 里有很多属性(property),每个属性都有一个名字和值,他们都是字符串格式。这些属性定义了 Android 系统的一些公共系统属性。
在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。属性由键(key)与值(value)构成,其表现形式为“键=值”。

在访问属性值时,添加了访问权限控制,增强了访问的安全性。系统中所有运行中的进程都可以访问属性值,但仅有init进程才能修改属性值。其他进程修改属性值时,必须向init进程提出请求,最终由init进程负责修改属性值。在此过程中,init进程会先检查各属性的访问权限,而后再修改属性值。

当属性值更改后,若定义在init.rc文件中的某个特定条件得到满足,则与此条件相匹配的动作就会发生,每个动作都有一个触发器,决定动作的执行时间,记录在“on property”关键字后的命令即被执行。

3.1属性系统工作框架

image.png
   从图中我们可以看出Android属性系统由有三个进程,一组属性文件和一块共享内存组成。这块共享内存保存着系统中所有的属性记录,只有Property service能写这块共享内存,并且Property service负责将属性文件中的属性记录加载到共享内存中。

  **属性读取进程**(property consumer)把这块共享内存映射到自己的进程空间,然后直接读取它。**属性设置进程**(property setter)也加载这块共享到他的进程空间,但是他不能直接写这块共享内存。当他需要增加或者修改属性的时候,通过Unix Socket发生属性给Property service,Property service将代表设置进程写入共享内存和属性文件。

Property service运行于**init进程**中。init进程首先创建一块共享内存,并把他的句柄fd存放在这块内存中,**init进程通过mmap带MAP_SHARE标志的系统调用,把这块内存映射到他的虚拟空间中,最终这块内存所有的更新将会被所有映射这块共享内存的进程看到**。共享内存句柄fd和共享内存大小存储在系统环境变量“ANDROID_PROPERTY_WORKSPACE”中,所有的进程包括属性设置进程和属性读取进程都将通过这个系统环境变量获得共享内存的句柄fd和大小,然后把这块内存映射到他们自己的虚拟空间。共享内存布局如下:
image.png

当前,属性不能被删除。也就是说一旦属性被创建,将不可以被删除,但是它们可以被修改

四、属性系统的优点

属性系统有一下四个优点

  1. 全局性:只要拥有对应的权限,就可以同步获取和修改
  2. 广泛的可访问性:在Java层,native层,shell层都可以获取和修改
  3. 初始化早:属性服务实在 init 进程中启动的
  4. 使用简单:主要就两个方法 set 和 get

但是只能支持有限的类型:string、int、long、boolean

五、属性系统的初始化和启动过程

属性服务的是从init进程中启动,init 进程(源码位于/system/core/init/init.c)主要完成:

  • 解析 init.rc 文件并执行相应动作和服务
  • 生成设备驱动节点
  • 处理子进程终止
  • 提供属性服务

先上图熟悉流程:


Property_service初始化流程.png

5.1 init进程初始化property_service

init进程是安卓系统第一个进程,由它执行系统的初始化过程,属性服务也是由它开始初始化的
system/core/init/init.h
init.h是init.c的头文件,它定义了两个重要的结构体:commandaction

#include 

struct command
{
     /* list of commands in an action */
    struct listnode clist;  //listnode结构体定义在list.h

    //command 结构体中的func指向一个函数,该函数在后续init 进程调用execute_one_command 时会被调用
    int (*func)(int nargs, char **args);
    int nargs;
    char *args[1];
};

struct action {
    /* node in list of all actions */
    struct listnode alist;
    /* node in the queue of pending actions */
    struct listnode qlist;
    /* node in list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;
    const char *name;

    struct listnode commands;  //listnode结构体定义在list.h
    struct command *current;
};

system/core/init/init.c
init.c是init进程的源文件,找到入口main()函数

//引入property相关的头文件
#include 
#include "property_service.h"

static int property_triggers_enabled = 0;

static struct action *cur_action = NULL;
static struct command *cur_command = NULL;
static struct listnode *command_queue = NULL;
 
int main(int argc, char **argv)
{
    int property_set_fd_init = 0;
    //1-property初始化,位于property_service.c
    property_init();
 
     //2-这些目录必须在初始策略加载之前创建,因此需要将其安全上下文恢复到正确的值。这必须在由ueventd填充/dev之前发生。
     restorecon("/dev");
     restorecon("/dev/socket");
     restorecon("/dev/__properties__");
     restorecon_recursive("/sys");
    
     is_charger = !strcmp(bootmode, "charger");
     INFO("property init\n");
      //3-如果不是充电状态,加载默认properties
     if (!is_charger)
         property_load_boot_defaults();
    // queue_builtin_action()将 property_init_action函数放入特定的列表中
     queue_builtin_action(property_service_init_action, "property_service_init");
    /* run all property triggers based on current state of the properties */
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
    
    //进入for循环,
    for(;;) {
         int nr, i, timeout = -1;
 
         execute_one_command();//execute_one_command 函数从 action_queue 队列中获取 action 并执行
         restart_processes();
 
         if (!property_set_fd_init && get_property_set_fd() > 0) {
             ufds[fd_count].fd = get_property_set_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
             property_set_fd_init = 1;
         }
         if (!signal_fd_init && get_signal_fd() > 0) {
             ufds[fd_count].fd = get_signal_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
             signal_fd_init = 1;
         }
         if (!keychord_fd_init && get_keychord_fd() > 0) {
             ufds[fd_count].fd = get_keychord_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
             keychord_fd_init = 1;
         }
 
         if (process_needs_restart) {
             timeout = (process_needs_restart - gettime()) * 1000;
             if (timeout < 0)
                 timeout = 0;
         }
 
         if (!action_queue_empty() || cur_action)
             timeout = 0;
 
 #if BOOTCHART
         if (bootchart_count > 0) {
             if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
                 timeout = BOOTCHART_POLLING_MS;
             if (bootchart_step() < 0 || --bootchart_count == 0) {
                 bootchart_finish();
                 bootchart_count = 0;
             }
         }
 #endif
 
         nr = poll(ufds, fd_count, timeout);
         if (nr <= 0)
             continue;
        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents == POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
                else if (ufds[i].fd == get_keychord_fd())
                    handle_keychord();
                else if (ufds[i].fd == get_signal_fd())
                    handle_signal();
            }
        }
    }

}

//property发生改变
void property_changed(const char *name, const char *value)
{
    if (property_triggers_enabled)
        queue_property_triggers(name, value);
}

static int queue_property_triggers_action(int nargs, char **args)
{
    queue_all_property_triggers();  //位于init_parser.c
    /* enable property triggers */
    property_triggers_enabled = 1;
    return 0;
}
//从action队列中读取action并执行
void execute_one_command(void)
{   
    int ret;
    //判断是获取一个新的 action 还是继续执行 action 的 commands 列表中的下一个 command
    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
       //对于Android属性系统,是一个新的action,cur_action==NULL,cur_command==NULL
       //从 action_queue 中 获 取 action 并 赋 值 给 cur_action,定义在init_parser.c中
       cur_action = action_remove_queue_head();  
        cur_command = NULL;
        if (!cur_action)
            return;
        INFO("processing action %p (%s)\n", cur_action, cur_action->name);
      //从 cur_action 中的 commands 节点中获取第一个 command 赋值给 cur_command
        cur_command = get_first_command(cur_action);
    } else {
        cur_command = get_next_command(cur_action, cur_command);
    }
    
    if (!cur_command)
        return;
    //对于 Android 属性系统,action 的 commands 节点只有一个 command,调用
   //command中 func 成员所指向的函数,此处调用 property_service_init_action函数
    ret = cur_command->func(cur_command->nargs, cur_command->args);
    INFO("command '%s' r=%d\n", cur_command->args[0], ret);
}

//从指定action结体中,获取commands链表的第一个节点
static struct command *get_first_command(struct action *act)
{
    struct listnode *node;
    node = list_head(&act->commands);
    if (!node || list_empty(&act->commands))
        return NULL;

    return node_to_item(node, struct command, clist);
}
//Android属性服务初始化函数 
static int property_service_init_action(int nargs, char **args)
{
    /* read any property files on system or data and
     * fire up the property service.  This must happen
     * after the ro.foo properties are set above so
     * that /data/local.prop cannot interfere with them.
     */
    //该方法开始读取系统中所有的property文件或者数据,并启动Property服务,但是这
  //必须在ro.foo的属性被设置之后,这样/data/local.prop就不会干扰它们
    start_property_service();  //定义在property_service.c中
    return 0;
}

5.2 property_service.c解析

system/core/init/property_service.c

#include "property_service.h"
#include 
#include 

#define PERSISTENT_PROPERTY_DIR  "/data/property"

//标识persistent proerties是否加载完成
static int persistent_properties_loaded = 0;
//标识property初始化是否完成
static int property_area_inited = 0;

typedef struct {
    size_t size;  //共享内存空间大小
    int fd;  //句柄
} workspace;

//property初始化
void property_init(void)
{
    init_property_area();
}

/*init_property_area 初始化共享内存空间。在 Android 系统中,所有的进程是共享
*系统属性值的,Android 系统提供了一个名为属性的保存空间,在共享内存区域中
*(ASHMEM),创建并初始化属性域*/
static workspace pa_workspace;
static int init_property_area(void)
{
    if (property_area_inited)  //只能初始化一次
        return -1;
    //初始化共享内存区域,位于system_properties.c
    if(__system_property_area_init())
        return -1;
    //初始化工作空间,大小初始化为0
    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    property_area_inited = 1;  //设置初始化完成标志
    return 0;
}

 //初始化工作空间,workspace在该文件中被定义,参数<工作空间,空间大小>
static int init_workspace(workspace *w, size_t size)
{
    void *data;
    //#define PROP_FILENAME "/dev/__properties__"  定义在_system_properties.h
    int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);  //打开内存映射文件
    if (fd < 0)
        return -1;

    w->size = size;  //设置空间大小
    w->fd = fd;  //设置文件句柄,fd 为/dev/__properties__文件句柄
    return 0;
}

void property_load_boot_defaults(void)
{
    //从文件中加载,PROP_PATH_RAMDISK_DEFAULT=“/defalult.prop”
    //定义在_system_properties.h文件中
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}

/*
default.prop 文件中的内容是在 Android系统编译时写入到 boot.img 中的,而每次
开机,boot.img 会进行解压,所以想要修改该文件中的内容可以重新编译系统,或
者修改 boot.img 文件,在/build/core/main.mk 文件中定义了不同属性的值,
ADDITIONAL_DEFAULT_PROPERTIES 变量值就是需要写入到 default.prop 文件中
的内容
build/core/main.mk部分内容如下:
# Target is secure in user builds.
ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=1
# Disallow mock locations by default for user builds
ADDITIONAL_DEFAULT_PROPERTIES += ro.allow.mock.location=0

default.prop文件内容一般如下 :
shell@sp9820w_poc_780:/ $ cat default.prop
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
ro.allow.mock.location=0
ro.debuggable=1
camera.disable_zsl_mode=1
persist.sys.usb.config=mtp,adb
*/

//从指定文件中加载属性
static void load_properties_from_file(const char *fn)
{
    char *data;
    unsigned sz;
    //打开文件,读取内容
    data = read_file(fn, &sz);

    if(data != 0) {
        //打开成功,加载property
        load_properties(data);
        //关闭文件
        free(data);
    }
}

//解析property数据,逐行解析
static void load_properties(char *data)
{   
    char *key, *value, *eol, *sol, *tmp;
    
    sol = data;  //sol指向文件开始
    //char *strchr(const char *str, int c)返回在字符串 str 中第一次出现字符 c 的位置,实现逐行读取
    while((eol = strchr(sol, '\n'))) {  //返回换行符的指针
        key = sol;   //key表示行首的指针
        *eol++ = 0;  //相关于*eol=0; eol++; '\n'替换为0,此时eol为下一行的行首指针
        sol = eol;    //赋值给sol
        value = strchr(key, '=');  //截取一行中'='前面的字符,value指向'='
        if(value == 0) continue;  //如果=前面为空,跳过
        *value++ = 0;  //’=‘替换为0,value指向原'='后面的内容,即实际的value字节地址
        
        while(isspace(*key)) key++;  //去掉一行前面的空格
        
        if(*key == '#') continue;  //如果是'#'开头,表示注释,跳过
        tmp = value - 2;  //value-2为key的末尾地址
        while((tmp > key) && isspace(*tmp)) *tmp-- = 0; //判断key末尾是否有空白符,如果有替换为0
        
        while(isspace(*value)) value++;  //判断value起始内容中是否有空白符,如果有替换为0
        tmp = eol - 2;  //判断value末尾内容中是否有空白符,如果有替换为0
        while((tmp > value) && isspace(*tmp)) *tmp-- = 0;
        //设置属性,关键调用,该方法本章不再继续深究
        property_set(key, value);
    }
}

//启动属性服务
void start_property_service(void)
{
    int fd;
    //1.加载/system/build.prop
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    //2.加载 /system/default.prop
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    //3.加载override props
    load_override_properties();
    /* Read persistent properties after all default values have been loaded. */
    load_persistent_properties();

    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    if(fd < 0) return;
    fcntl(fd, F_SETFD, FD_CLOEXEC);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    listen(fd, 8);
    property_set_fd = fd;
}

5.3 system_properties.c解析

属性存储在混合(trie)字典树 /(binary)二叉树结构中。每个属性的名称都以'.'分隔,字符和标记放在一起成为一个特里结构。 特里结构的每个级别的兄弟姐妹都存储在一个二叉树。例如,“ro.secure”=“1”可以存储如下:

image.png

特里结构的根结点是空的,后面第二个节点为ro,ro结点是一个二叉树的根结点,它的左孩子为net,右孩子为sys。ro的后驱结点为secure,它也是一个二驻树的根结点,左孩子为com,右孩子为prop,值为1。这样把prop前面的串起来就是ro.secure=1。

bionic/libc/include/sys/system_properties.h

typedef struct prop_info prop_info;
//prop key和value的最大长度
#define PROP_NAME_MAX   32
#define PROP_VALUE_MAX  92

bionic/libc/include/sys/_system_properties.h

#define PROP_AREA_MAGIC   0x504f5250
#define PROP_AREA_VERSION 0xfc6ed0ab
#define PROP_AREA_VERSION_COMPAT 0x45434f76

#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"
#define PROP_PATH_FACTORY          "/factory/factory.prop"

//定义映射内存空间的大小128K
#define PA_SIZE         (128 * 1024)

//prop service的名称
#define PROP_SERVICE_NAME "property_service"
//prop内存映射文件
#define PROP_FILENAME "/dev/__properties__"

bionic/libc/bionic/system_properties.c

static char property_filename[PATH_MAX] = PROP_FILENAME;

//property共享内存结构体
struct prop_area {
    unsigned bytes_used;  //已使用的字节数
    unsigned volatile serial; //serial 表示整个 prop_area 被修改的次数,包括
新增加的
    unsigned magic;
    unsigned version;
    unsigned reserved[28];
    char data[0];  //实际的共享内存区域
};
//定义全局变量
typedef struct prop_area prop_area;

//property info结构体
struct prop_info {
    //prop_info 中的 serial,其高8 位表示该 prop_info 中 name 的长度,而低 24 位表示该 prop_info 被更新的次数
    unsigned volatile serial;
    char value[PROP_VALUE_MAX];
    char name[0];
};
//定义全局变量
typedef struct prop_info prop_info;


//初始化共享内存
int __system_property_area_init()
{
    return map_prop_area_rw();
}
//初始化可读写的共享内存区域
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;
    }
}

Android4.4属性系统-初始化_第1张图片
image.png

5.4 init_parser.c解析

system/core/init/init_parser.c

static list_declare(service_list);
static list_declare(action_list);  //声明action list
static list_declare(action_queue);

//参数<函数指针,函数指针指向函数的参数>
void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
{
    struct action *act;  //定义在init.h中
    struct command *cmd;  //定义在init.h中

    act = calloc(1, sizeof(*act));  //act分配内存
    act->name = name;  //Android 系统属性服务来说,该值为 property_service_init
    list_init(&act->commands);  //初始化,将 listnode 的 next 和 prev 都指向自
身
    list_init(&act->qlist);  //初始化qlist

    cmd = calloc(1, sizeof(*cmd));  //cmd分配内存
    cmd->func = func;  //设置函数指针,对于属性服务为property_service_init_action函数
    cmd->args[0] = name; //args 表示函数运行的参数值,此处只有一个name参数
    list_add_tail(&act->commands, &cmd->clist); //在action的commands链表的尾部添加节点cmd->clist
    //action_list尾部添加节点act->alist
    list_add_tail(&action_list, &act->alist);
    //
    action_add_queue_tail(act);
}

void action_add_queue_tail(struct action *act)
{
    if (list_empty(&act->qlist)) {  //如果链表为空则添加
        list_add_tail(&action_queue, &act->qlist);
    }
}
//从action_queue获取头节点,并删除头结点 
struct action *action_remove_queue_head(void)
{
    if (list_empty(&action_queue)) {
        return 0;
    } else {
        struct listnode *node = list_head(&action_queue);
        struct action *act = node_to_item(node, struct action, qlist);
        list_remove(node);
        list_init(node);
        return act;
    }
}

queue_builtin_action方法执行完后,action 和 command 结构体以及 action_list 和 action_queue 的关系如下图(queue_builtin_action 在 此 处 并 不 是 最 后 一 次 被 调 用 , 因 此 action_list 以 及action_queue 和 action 之间用虚点线表示,action_list 和 action_queue 之前还有其他的 action结构体变量)


image.png

5.5 cutils库解析

目录位置system/core/libcutils,是一些c的函数工具库,现在介绍一些涉及到的文件
system/core/include/cutils/list.h

//可构成双向链表
struct listnode
{   
    struct listnode *next;  
    struct listnode *prev;
};

//宏定义,声明并初始化链表
#define list_declare(name) \
    struct listnode name = { \
        .next = &name, \
        .prev = &name, \
    }

system/core/libcutils/list.c

#include 

//初始化链表
void list_init(struct listnode *node)
{
    node->next = node;
    node->prev = node;
}
//末尾添加节点
void list_add_tail(struct listnode *head, struct listnode *item)
{
    item->next = head;
    item->prev = head->prev;
    head->prev->next = item;
    head->prev = item;
}
//移除节点
void list_remove(struct listnode *item)
{
    item->next->prev = item->prev;
    item->prev->next = item->next;
}

六、参考

Android属性系统
Linux 内存映射函数 mmap
trie
Linux 内存映射函数 mmap()函数详解
Android属性系统

你可能感兴趣的:(Android4.4属性系统-初始化)