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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 笔记

一、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 开头的属性文件,如系统语言、国家编码等,如下图


14.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的值是相同的


五、总结,放一张图


15.png


六、参考


Android属性系统

Linux 内存映射函数 mmap

trie

Linux 内存映射函数 mmap()函数详解


目录
相关文章
|
3月前
|
JavaScript 前端开发 Java
[Android][Framework]系统jar包,sdk的制作及引用
[Android][Framework]系统jar包,sdk的制作及引用
79 0
|
17天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
42 15
Android 系统缓存扫描与清理方法分析
|
8天前
|
算法 JavaScript Android开发
|
10天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
25 2
|
19天前
|
安全 搜索推荐 Android开发
深入探索安卓与iOS系统的差异及其对用户体验的影响
在当今的智能手机市场中,安卓和iOS是两大主流操作系统。它们各自拥有独特的特性和优势,为用户提供了不同的使用体验。本文将深入探讨安卓与iOS系统之间的主要差异,包括它们的设计理念、用户界面、应用生态以及安全性等方面,并分析这些差异如何影响用户的使用体验。
|
18天前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
21天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
17 1
|
9天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
9天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
11天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
31 0