Android4.4属性系统-系统服务

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

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

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

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

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

三、属性服务解析

本章节介绍属性系统服务的详细启动过程,Android 属性系统服务实际上是由 init 进程维护的,通过创建一个 socket来监听相应的属性获取和设置的请求,最终需要由 init 进程来处理。

Android4.4属性系统-初始化中介绍过,在init.c的main()函数中,调用了如下代码启动系统服务

//设置函数指针property_service_init_action,服务名称为"property_service_init"
queue_builtin_action(property_service_init_action, "property_service_init");

之后在for循环中执行了execute_one_command(),调用了每个ation的函数指针,property_service_init_action()也被调用,继而开始启动属性系统服务。

3.1系统服务的启动init.c

property_service_init_action开始启动属性系统服务,注释中写明在读取属性文件时会触发属性服务运行,该函数最终会调用start_property_service 运行属性服务,主要是创建一个 socket 并监听。
system/core/init/init.c

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();
    return 0;
}

3.2 property_service.c解析

system/core/init/property_service.c
start_property_service首先调用 load_properties_from_file 分别从 3 个文件中加载属性。PROP_PATH_SYSTEM_BUILD、PROP_PATH_SYSTEM_DEFAULT都定义在/bionic/libc/include/sys/_system_properties.h中。

void start_property_service(void)
{
    int fd;

    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);// /system/build.prop
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);// /system/default.prop
    load_override_properties();  //加载overrides属性
    /* Read persistent properties after all default values have been loaded. */
    load_persistent_properties(); //加载完所有default属性后开始加载持久化属性

    /*创建一个socket,create_socket定义在util.c
     *参数<"property_service",  scoket类型,0666 表示权限, uid, gid>
     * PROP_SERVICE_NAME定义在_system_properties.h中;,对应的 socket 文件为”/dev/socket/property_service”
     * uid=0,gid=0表示 root 用户和组
     */
    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);
    //监听创建的 socket,listen 函数定义于/bionic/libc/unistd/socketcalls.c 文件中
    listen(fd, 8);
    //将 property_set_fd 赋值为创建的 socket 文件描述符
    property_set_fd = fd;
}

//加载可覆盖的属性,PROP_PATH_LOCAL_OVERRIDE定义在_system_properties.h
static void load_override_properties() {
#ifdef ALLOW_LOCAL_PROP_OVERRIDE
    char debuggable[PROP_VALUE_MAX];
    int ret;

    ret = property_get("ro.debuggable", debuggable);
    if (ret && (strcmp(debuggable, "1") == 0)) {
        load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);// /data/local.prop
    }
#endif /* ALLOW_LOCAL_PROP_OVERRIDE */
}

//从指定文件中加载属性
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);
    }
}

//设置属性,参数<key,value>
int property_set(const char *name, const char *value)
{
    prop_info *pi;
    int ret;
  
    size_t namelen = strlen(name);  //key 长度
    size_t valuelen = strlen(value);  //value长度

    if (!is_legal_property_name(name, namelen)) return -1;  //判断prop name是否合法
    if (valuelen >= PROP_VALUE_MAX) return -1;  //判断value长度是否合法,PROP_VALUE_MAX=96

    pi = (prop_info*) __system_property_find(name);  //查询prop是否存在

    if(pi != 0) {  //如果系统已经存在该property
        /* ro.* properties may NEVER be modified once set */
        if(!strncmp(name, "ro.", 3)) return -1;  //ro-read only,ro开头是不允许修改的,返回-1
        
        __system_property_update(pi, value, valuelen);  //更新property
    } else {  //如果系统中不存在,添加新的property
        ret = __system_property_add(name, namelen, value, valuelen);
        if (ret < 0) {
            ERROR("Failed to set '%s'='%s'\n", name, value);
            return ret;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {  //如果以net.开头
        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.
        */
        //'net.change'属性是一个特殊属性,用于更新任何'net.*'属性名称时的跟踪,它只能在这里更新。其值包含最后更新的'net.*'属性。
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&  // persistent_properties_loaded 在执行完load_persistent_properties()后置为1
            strncmp("persist.", name, strlen("persist.")) == 0) {//persist.开头的
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
        //在读取完所有的default properties之后再写入硬盘,避免它们被default 值覆盖
        write_persistent_property(name, value);
    } else if (strcmp("selinux.reload_policy", name) == 0 &&
               strcmp("1", value) == 0) {  //如果是selinux.reload_policy=1
        selinux_reload_policy();  //重新加载selinux
    }
    property_changed(name, value);    //property_changed位于init.c
    return 0;
}

//判断property字符串是否合法
static bool is_legal_property_name(const char* name, size_t namelen)
{
    size_t i;
    bool previous_was_dot = false;
    if (namelen >= PROP_NAME_MAX) return false;
    if (namelen < 1) return false;
    if (name[0] == '.') return false;
    if (name[namelen - 1] == '.') return false;

    /* Only allow alphanumeric, plus '.', '-', or '_' */
    /* Don't allow ".." to appear in a property name */
    for (i = 0; i < namelen; i++) {
        if (name[i] == '.') {
            if (previous_was_dot == true) return false;
            previous_was_dot = true;
            continue;
        }
        previous_was_dot = false;
        if (name[i] == '_' || name[i] == '-') continue;
        if (name[i] >= 'a' && name[i] <= 'z') continue;
        if (name[i] >= 'A' && name[i] <= 'Z') continue;
        if (name[i] >= '0' && name[i] <= '9') continue;
        return false;
    }

    return true;
}

load_persistent_properties 加载持久化属性,这些属性存在于/data/property 目录下,该目录下
存放了以 persist 开头的属性文件,如系统语言、国家编码等,如下图

image.png

可以看到文件名就是属性名,文件内容就是属性的value值。

//持久化属性存储路径
#define PERSISTENT_PROPERTY_DIR  "/data/property"
//加载持久化属性
static void load_persistent_properties()
{
    DIR* dir = opendir(PERSISTENT_PROPERTY_DIR);
    int dir_fd;
    struct dirent*  entry;
    char value[PROP_VALUE_MAX];
    int fd, length;
    struct stat sb;

    if (dir) {
        dir_fd = dirfd(dir); //返回参数dir所指向的目录文件的文件描述符
        while ((entry = readdir(dir)) != NULL) {  /readdir打开目录,返回下个目录进入节点
            //d_name 文件名,不是以persist.开头跳过
            if (strncmp("persist.", entry->d_name, strlen("persist.")))  
                continue;
#if HAVE_DIRENT_D_TYPE
            if (entry->d_type != DT_REG)  //判断文件类型,DT_REG常规文件
                continue;
#endif
            /* open the file and read the property value */
            //打开文件
            fd = openat(dir_fd, entry->d_name, O_RDONLY | O_NOFOLLOW);
            if (fd < 0) {
                ERROR("Unable to open persistent property file \"%s\" errno: %d\n",
                      entry->d_name, errno);
                continue;
            }
            if (fstat(fd, &sb) < 0) {  //获取文件状态,写入到sb中
                ERROR("fstat on property file \"%s\" failed errno: %d\n", entry->d_name, errno);
                close(fd);
                continue;
            }

            // File must not be accessible to others, be owned by root/root, and
            // not be a hard link to any other file.
            /*文件不允许root/root用户/组之外的用户有访问权限,也不允许硬链接
             *st_mode 表示文件权限和文件类型信息
             * 如果存在以下几种情况是不允许读取的
             */
            if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) //如果属组或者其它用户有读写权限 
                    || (sb.st_uid != 0)  //uid!=0,不是root用户
                    || (sb.st_gid != 0)  //不是root组
                    || (sb.st_nlink != 1)) {  //该文件上硬连接的个数不是1(应该只有root一个)
                ERROR("skipping insecure property file %s (uid=%lu gid=%lu nlink=%d mode=%o)\n",
                      entry->d_name, sb.st_uid, sb.st_gid, sb.st_nlink, sb.st_mode);
                close(fd);
                continue;
            }

            length = read(fd, value, sizeof(value) - 1);  //从文件中读取value值
            if (length >= 0) {
                value[length] = 0;
                property_set(entry->d_name, value);  //设置property,文件名即为属性名
            } else {
                ERROR("Unable to read persistent property file %s errno: %d\n",
                      entry->d_name, errno);
            }
            close(fd);
        }
        closedir(dir);
    } else {
        ERROR("Unable to open persistent property directory %s errno: %d\n", PERSISTENT_PROPERTY_DIR, errno);
    }
    //设置标志位为1
    persistent_properties_loaded = 1;
}

system/core/init/util.c

/*
 * 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<name> ("ANDROID_SOCKET_foo").
 */
//参数<套接字名称, socket类型,权限,用户,组>
//调用时传入参数<"property_service", SOCK_STREAM(流套接字) , 0666, 0, 0>
int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
    struct sockaddr_un addr;
    int fd, ret;
    char *secon;

    //创建socket,PF_UNIX表示进程间通信,0表示自动选取使用TCP还是UDP
    fd = socket(PF_UNIX, type, 0); 
    if (fd < 0) {
        ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
        return -1;
    }

    memset(&addr, 0 , sizeof(addr));  //初始化socket地址为0
    addr.sun_family = AF_UNIX;  //AF_UNIX用于同一台机器上的进程间通信,AF_INET为ipv4
    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;
    }

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

    //对套接字进行地址和端口的绑定,才能进行数据的接收和发送操作
    //位于socketcalls.c文件中
    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(secon);

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

3.3 bionic/libc 库

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__"

socketcalls.c在项目源码中没有找到,应该是被展讯做修改后,当作预置库放到源码中了。
下面是从AOSP下载的Android4.4源码
bionic/libc/unistd/socketcalls.c

//定义了SOCKET行为结构体
enum
{
    SYS_SOCKET = 1,
    SYS_BIND,
    SYS_CONNECT,
    SYS_LISTEN,
    SYS_ACCEPT,
    SYS_GETSOCKNAME,
    SYS_GETPEERNAME,
    SYS_SOCKETPAIR,
    SYS_SEND,
    SYS_RECV,
    SYS_SENDTO,
    SYS_RECVFROM,
    SYS_SHUTDOWN,
    SYS_SETSOCKOPT,
    SYS_GETSOCKOPT,
    SYS_SENDMSG,
    SYS_RECVMSG
};

//socket生成函数,参数<地址域(faminly), socket类型,协议>
int socket(int domain, int type, int protocol)
{
    unsigned long  t[3];

    t[0] = (unsigned long) domain;
    t[1] = (unsigned long) type;
    t[2] = (unsigned long) protocol;

    return (int) __socketcall( SYS_SOCKET, t );
}

//绑定地址和端口,参数<socket, 地址和商品,地址长度>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen)
{
    unsigned long  t[3];

    t[0] = (unsigned long) sockfd;
    t[1] = (unsigned long) my_addr;
    t[2] = (unsigned long) addrlen;

    return (int) __socketcall( SYS_BIND, t );
}


//监听端口,参数<scoket句柄,backlog 为请求队列的最大长度>
// listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求
int listen(int s, int backlog)
{
    unsigned long  t[2];

    t[0] = (unsigned long) s;
    t[1] = (unsigned long) backlog;

    return (int) __socketcall( SYS_LISTEN, t );
}

可以看到上面这几个函数调用的都是__socketcall(),而这个函数是在哪里定义的就找不到喽~有知道的大神还请指点一二, 先谢过了!

四、补充知识点

4.1 fstat函数

fstat函数

文中使用到的sb.st_mode解释如下:
st_mode域是需要一些宏予以配合才能使用的。其实,通俗说,这些宏就是一些特定位置为1的二进制数的外号,我们使用它们和st_mode进行”&”操作,从而就可以得到某些特定的信息。
用于解释st_mode标志的掩码包括:
S_IFMT:文件类型
S_IRWXU:属主的读/写/执行权限,可以分成S_IXUSR, S_IRUSR, S_IWUSR
S_IRWXG:属组的读/写/执行权限,可以分成S_IXGRP, S_IRGRP, S_IWGRP
S_IRWXO:其他用户的读/写/执行权限,可以分为S_IXOTH, S_IROTH, S_IWOTH

4.2 socket函数

socket函数
int socket(int af, int type, int protocol);
af 为地址族(Address Family),也就是 IP 地址类型,比较常用的为AF_INET或AF_UNIX。AF_UNIX用于同一台机器上的进程间通信,AF_INET对于IPV4协议的TCP和UDP ,AF_INET6对应于IPV6。
AF_INET和PF_INET的值是相同的

五、总结,放一张图

property服务.png

六、参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269