Android属性服务分析(property service)

1.首先init()进程在启动的过程对属性服务做了如下操作:

1)property_init(); //创建属性存储空间
2)property_load_boot_defaults(); //从 “/default.prop”中加载默认属性
3)queue_builtin_action(property_service_init_action, “property_service_init”); //初始化属性服务,创建名为“property_service”的socket,该socket文件存放在“/dev/socket/”目录下。然后绑定socket,开始监听。
4)queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”); //首先将“queue_property_triggers”插入到执行action队列,这个action对应的执行函数位“queue_property_triggers_action”

2.文件系统-知识补充:

1)tmpfs文件系统
tmpfs是一种虚拟的内存文件系统,它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有文件均为临时性文件,tmpfs是一个独立的文件系统,不是快设备,只要挂载即可使用。它驻留在RAM中,大小自动变化,断电后tmpfs的内容就会消失。

2)devpts文件系统
devpts文件系统为伪终端(pty-pseudo-terminal slave)提供了一个标准接口,它的标准挂接点是/dev/pts。伪终端是成对的逻辑终端设备(即master主和slave从设备,对master的操作会反应到slave上)

3)proc文件系统
proc是一个非常重要的文件系统,它可以看做是内核内部数据的接口,通过它可以获取系统的信息,同时也能在运行时修改特定的参数,只需要在对应的文件中添加新的值即可,如果修改失败,则只能重新启动设备。

4)sysfs文件系统
与proc文件系统相似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂载到/sys目录下。sysfs文件系统是linux2.6内核引入,它把连接到系统上的设备和总线组织为一个分级的文件,使得他们可以在用户空间存取。

在init进程开始时,创建了三个文件夹,分别为/dev,/proc和/sys。之后又创建了/dev/pts和/dev/socket两个文件夹,分别用于伪终端和用于与服务通信的socket。以上四中文件系统均被挂载到制定的目录上。

3.下面针对上面四点逐一解析;
1)创建属性存储空间

void property_init(void)
{
    init_property_area();
}

init_property_area()函数如下:

static int init_property_area(void)
{
    if (property_area_inited)
        return -1;

    if(__system_property_area_init())
        return -1;

    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    property_area_inited = 1;
    return 0;
}

其中property_area_inited标记属性存储区域是否初始化,__system_property_area_init()函数如下,该函数位于“bionic/libc/bionic/system_properties.cpp”中:

int __system_property_area_init()
{
    return map_prop_area_rw();
}

map_prop_area_rw()函数如下:

static int map_prop_area_rw()
{
    /* dev is a tmpfs that we can use to carve a shared workspace
     * out of, so let's do that...
     */
    const int 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;
    }

    // TODO: Is this really required ? Does android run on any kernels that
    // don't support O_CLOEXEC ?
    const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0) {
        close(fd);
        return -1;
    }

    if (ftruncate(fd, PA_SIZE) < 0) {
        close(fd);
        return -1;
    }
    pa_size = PA_SIZE;
    pa_data_size = pa_size - sizeof(prop_area);
    compat_mode = false;

    void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (memory_area == MAP_FAILED) {
        close(fd);
        return -1;
    }

    prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);

    /* plug into the lib property services */
    __system_property_area__ = pa;

    close(fd);
    return 0;
}

由于dev是tmpfs系统,所以可以用来开辟一块共享存储空间。map_prop_area_rw()函数首先创建属性文件“/dev/_properties_”,大小PA_SIZE为128*1024,然后将该文件的对象映射进内存。由于属性是存放在一个混合的线索/二进制树结构(prop_bt)中,所以在内存区域中new了一个prop_area的头节点,并赋给了变量”_system_property_area_“.
之后,init_workspace()函数初始化工作空间,其中workspace的结构体如下:

typedef struct {
    size_t size;
    int fd;
} workspace;

此时,fd为之间创建的属性文件的open操作的句柄。size为初始化指定大小。最后将“property_area_inited”置为1,至此,属性存储空间开辟完成。

4.从 “/default.prop”中加载默认属性

void property_load_boot_defaults(void)
{
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}

其中PROP_PATH_RAMDISK_DEFAULT文件为”/default.prop”

static void load_properties_from_file(const char *fn, const char *filter)
{
    char *data;
    unsigned sz;

    data = read_file(fn, &sz);

    if(data != 0) {
        load_properties(data, filter); 
        free(data);
    }
}

read_file()即我们平时普通的读文件操作。load_properties函数采用递归的方式解析读取内容中的key-value对。次函数不予解释。

5.初始化属性服务

queue_builtin_action(property_service_init_action, "property_service_init");

这里,queue_builtin_action的第一个参数是一个函数,第二个参数是action的名称以及命令队列中的名称。

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.
     */
    start_property_service();
    if (get_property_set_fd() < 0) { 
        ERROR("start_property_service() failed\n");
        exit(1);
    }

    return 0;
}

如注释所说,读取系统中所有的属性文件,启动属性服务。

void start_property_service(void)
{
    int fd;

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

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

创建与属性服务通信的socket,创建函数如下:

/*
 * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
 * ("/dev/socket") as dictated in init.rc. This socket is inherited by the
 * daemon. We communicate the file descriptor's value via the environment
 * variable ANDROID_SOCKET_ENV_PREFIX ("ANDROID_SOCKET_foo").
 */
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
                  gid_t gid, const char *socketcon)
{
    struct sockaddr_un addr;
    int fd, ret;
    char *filecon;

    if (socketcon)
        setsockcreatecon(socketcon);

    fd = socket(PF_UNIX, type, 0); 
    if (fd < 0) {
        ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
        return -1;
    }

    if (socketcon)
        setsockcreatecon(NULL);

    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
             name);

    ret = unlink(addr.sun_path);
    if (ret != 0 && errno != ENOENT) {
        ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
        goto out_close;
    }

    filecon = NULL;
    if (sehandle) {
        ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
        if (ret == 0)
            setfscreatecon(filecon);
    }

    ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
    if (ret) {
        ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno));
        goto out_unlink;
    }

    setfscreatecon(NULL);
    freecon(filecon);

    chown(addr.sun_path, uid, gid);
    chmod(addr.sun_path, perm);

    INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
         addr.sun_path, perm, uid, gid);
    return fd;

out_unlink:
    unlink(addr.sun_path);
out_close:
    close(fd);
    return -1;
}

在“/dev/socket”目录下创建一个Unix domain socket(同一台主机进程间通信,不需要网络协议,打包拆包等操作,只是将应用层数据从一个进程拷贝到另一个进程)Unix Domain Socket有SOCK_DGRAM或SOCK_STREAM两种工作模式,类似于UDP和TCP。
UNIX Domain socket与网络socket类似,可以与网络socket对比应用。不同如下:

1.address family为AF_UNIX(PF_UNIX的宏)
2.因为应用与IPC,所以Unix Domain socket不需要IP和端口,取而代之的是文件路径表示网络地址

用socket函数创建套接字之后,要绑定该socket和路径(此处有待斟酌),最后监听该端口

6.将“queue_property_triggers”插入到执行action队列

static int queue_property_triggers_action(int nargs, char **args)
{
    queue_all_property_triggers();
    /* enable property triggers */
    property_triggers_enabled = 1; 
    return 0;
}

queue_all_property_triggers()函数如下:

void queue_all_property_triggers()
{
    struct listnode *node;
    struct action *act;
    list_for_each(node, &action_list) {
        act = node_to_item(node, struct action, alist);
        if (!strncmp(act->name, "property:", strlen("property:"))) {
            /* parse property name and value
               syntax is property:<name>= */
            const char* name = act->name + strlen("property:");
            const char* equals = strchr(name, '=');
            if (equals) {
                char prop_name[PROP_NAME_MAX + 1]; 
                char value[PROP_VALUE_MAX];
                int length = equals - name;
                if (length > PROP_NAME_MAX) {
                    ERROR("property name too long in trigger %s", act->name);
                } else {
                    int ret;
                    memcpy(prop_name, name, length);
                    prop_name[length] = 0;
                    /* does the property exist, and match the trigger value? */
                    ret = property_get(prop_name, value);
                    if (ret > 0 && (!strcmp(equals + 1, value) ||
                                    !strcmp(equals + 1, "*"))) {
                        action_add_queue_tail(act);
                    }
                }
            }
        }
    }
}

该函数主要将解析init.rc文件得到的action列表中的property_action插入到action队列的末尾。

以上即为init进程中属性服务的解释,写这篇博客的目的主要是刚开始学习android源代码,找到方法才是最重要的。并以此鼓励自己。

你可能感兴趣的:(android,property,service,action,android源代码)