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()函数详解


目录
相关文章
|
1月前
|
人工智能 搜索推荐 物联网
Android系统版本演进与未来展望####
本文深入探讨了Android操作系统从诞生至今的发展历程,详细阐述了其关键版本迭代带来的创新特性、用户体验提升及对全球移动生态系统的影响。通过对Android历史版本的回顾与分析,本文旨在揭示其成功背后的驱动力,并展望未来Android可能的发展趋势与面临的挑战,为读者呈现一个既全面又具深度的技术视角。 ####
|
1月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
21天前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
22天前
|
存储 安全 Android开发
探索Android系统的最新安全特性
在数字时代,智能手机已成为我们生活中不可或缺的一部分。随着技术的不断进步,手机操作系统的安全性也越来越受到重视。本文将深入探讨Android系统最新的安全特性,包括其设计理念、实施方式以及对用户的影响。通过分析这些安全措施如何保护用户免受恶意软件和网络攻击的威胁,我们希望为读者提供对Android安全性的全面了解。
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
81 15
Android 系统缓存扫描与清理方法分析
|
1月前
|
监控 Java Android开发
深入探讨Android系统的内存管理机制
本文将深入分析Android系统的内存管理机制,包括其内存分配、回收策略以及常见的内存泄漏问题。通过对这些方面的详细讨论,读者可以更好地理解Android系统如何高效地管理内存资源,从而提高应用程序的性能和稳定性。
68 16
|
28天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS系统的差异及未来发展趋势
本文旨在深入分析Android和iOS两大移动操作系统的核心技术差异、用户体验以及各自的市场表现,进一步探讨它们在未来技术革新中可能的发展方向。通过对比两者的开放性、安全性、生态系统等方面,本文揭示了两大系统在移动设备市场中的竞争态势和潜在变革。
|
1月前
|
算法 JavaScript Android开发
|
1月前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
109 2
|
2月前
|
安全 搜索推荐 Android开发
深入探索安卓与iOS系统的差异及其对用户体验的影响
在当今的智能手机市场中,安卓和iOS是两大主流操作系统。它们各自拥有独特的特性和优势,为用户提供了不同的使用体验。本文将深入探讨安卓与iOS系统之间的主要差异,包括它们的设计理念、用户界面、应用生态以及安全性等方面,并分析这些差异如何影响用户的使用体验。