BootAnimation解析(三)

304 阅读4分钟
原文链接: zhuanlan.zhihu.com
BootAnimation这个进程的控制其实大家都知道是通过一个叫带有ctl的property进行控制的,property作为一个android系统中最常使用的保存数据或者记录系统信息或状态的工具,一直是以一个service的角色存在。这里就从对bootanimation的控制入手查看系统中property service的工作机制、property如何控制进程以及一个跟android设备相关的串行号property需要如何修改这三部分展开。

在init进程的main函数中会有一步操作,启动property service

void start_property_service() {
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);
    if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }

    listen(property_set_fd, 8);

    register_epoll_handler(property_set_fd, handle_property_set_fd);
}



可以看到这里会险创建一个socket,用于IPC,这里创建的套接字跟在写init.rc启动脚本中写的socket root root 666这种是一样的,都是属于Unix domain的套接字,这些套接字文件都存储在/dev/socket下面,我们熟悉的vold使用的套接字也是存放这这个下面。创建好之后就会去监听该socket的fd。然后注册监听的回调函数handle_property_set_fd,这个回调函数也是从消息中解析出cmd,然后做对应的操作:

static void handle_property_set_fd()
{
    prop_msg msg;
    int s;
    int r;
    struct ucred cr;
    struct sockaddr_un addr;
    socklen_t addr_size = sizeof(addr);
    socklen_t cr_size = sizeof(cr);
    char * source_ctx = NULL;
    struct pollfd ufds[1];
    const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */
    int nr;

    if ((s = accept(property_set_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 receive socket options\n");
        return;
    }

    ufds[0].fd = s;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;
    nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
    if (nr == 0) {
        ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
        close(s);
        return;
    } else if (nr < 0) {
        ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno));
        close(s);
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n",
              r, sizeof(prop_msg), strerror(errno));
        close(s);
        return;
    }
//这里解析cmd来区别需要做怎样的操作,这里肯定会有疑惑,为什么只有setprop这个命令,主要是getprop是不需要通过property service的,get就直接读取而已,而只有setprop才会通过property service进行操作
    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);

        if(memcmp(msg.name,"ctl.",4) == 0) {//比较property的前四个字节是否是ctl.
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);//处理控制ctl消息
            } 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, source_ctx)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }
}

注意:在surfaceflinger中使用property_set("ctl.start", "bootanim");来打开bootanimation这个服务

handle_control_message的实现如下:

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);
    }

}

如果是start就会继续调用msg_start("bootanimation"),会去根据解析init.rc得到的所有service的结果去找到bootanimation这个service,然后启动。停止这个service也是类似的原理。

这样来讲,能通过ctl.开头的property控制的service,必须是在init.rc中确实是申明过的才能使用这种property去控制。到此基本上就讲清除了bootanimation的打开和关闭机制。

接下来讨论一下property的存储机制,不太想trace整个加载属性文件的过程,property在内存中存储使用的是字典树的数据结构,它的优点是利用字符串的公共前缀来减少查询时间,这估计就是使用字典树的根本原因吧,因为在property中是存在很多开头相同属性,如http://ro.xxx这种

大致就画一张图来表明这种数据结构的特性,例如我们有如下几个property:

ro.abc.def=1

ro.hhh.def=2

sys.os.ccc=3

rs.ppp.qqq=4

在这样一个树中,跟每个节点产生直接关系的有三个,左兄弟、有兄弟和子节点

兄弟节点间的关系就是字典排序,左<中<右

这里还是借用网络上看到别人画的一张图:


但是要注意一点就是一个节点的右节点的左节点并不一定是它本身


最后讲一下如何修改手机或者其他设备的ro.serialno串号这个property,因为是一个只读属性,所以直接setprop是无效的。先给出结论,要修改此property需要在bootloader中写入androidboot.serialno这个bootarg的值。因为boot会将这些bootargs交给kernel,并存在/proc/cmdline中,开机之后property service就会去读取这个键值对,然后做两下property名字的转换最终得到ro.serialno这个property。就这么简单,但是不是原厂估计也没办法修改boot传给kernel的bootargs的值吧。