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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 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()函数详解


目录
相关文章
|
2月前
|
Android开发
基于android-11.0.0_r39,系统应用的手动签名方法和过程
本文介绍了基于Android 11.0.0_r39版本进行系统应用手动签名的方法和解决签名过程中遇到的错误,包括处理`no conscrypt_openjdk_jni-linux-x86_64`和`RegisterNatives failed`的问题。
88 2
|
2月前
|
JavaScript 前端开发 Java
[Android][Framework]系统jar包,sdk的制作及引用
[Android][Framework]系统jar包,sdk的制作及引用
43 0
|
8天前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
|
5天前
|
Dart 开发工具 Android开发
在 Android 系统上搭建 Flutter 环境的具体步骤是什么?
在 Android 系统上搭建 Flutter 环境的具体步骤是什么?
|
21天前
|
JavaScript 前端开发 Android开发
让Vite+Vue3项目在Android端离线打开(不需要起服务)
让Vite+Vue3项目在Android端离线打开(不需要起服务)
|
1月前
|
调度 Android开发 UED
Android经典实战之Android 14前台服务适配
本文介绍了在Android 14中适配前台服务的关键步骤与最佳实践,包括指定服务类型、请求权限、优化用户体验及使用WorkManager等。通过遵循这些指南,确保应用在新系统上顺畅运行并提升用户体验。
51 6
|
28天前
|
Android开发 UED 开发者
Android经典实战之WindowManager和创建系统悬浮窗
本文详细介绍了Android系统服务`WindowManager`,包括其主要功能和工作原理,并提供了创建系统悬浮窗的完整步骤。通过示例代码,展示了如何添加权限、请求权限、实现悬浮窗口及最佳实践,帮助开发者轻松掌握悬浮窗开发技巧。
55 1
|
7天前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。
|
7天前
|
编解码 开发工具 Android开发
Android平台实现屏幕录制(屏幕投影)|音频播放采集|麦克风采集并推送RTMP或轻量级RTSP服务
Android平台屏幕采集、音频播放声音采集、麦克风采集编码打包推送到RTMP和轻量级RTSP服务的相关技术实现,做成高稳定低延迟的同屏系统,还需要有配套好的RTMP、RTSP直播播放器
|
2月前
|
Java 物联网 Android开发
移动应用与系统:技术演进与未来展望探索安卓应用开发:从新手到专家的旅程
【8月更文挑战第28天】本文将深入探讨移动应用开发的技术演进、移动操作系统的发展历程以及未来的发展趋势。我们将通过实例和代码示例,展示如何利用最新的技术和工具来开发高效、可靠的移动应用。无论你是初学者还是经验丰富的开发者,这篇文章都将为你提供有价值的信息和见解。 【8月更文挑战第28天】在这个数字时代,掌握安卓应用的开发技能不仅是技术人员的追求,也成为了许多人实现创意和梦想的途径。本文将通过深入浅出的方式,带领读者从零基础开始,一步步走进安卓开发的奇妙世界。我们将探讨如何配置开发环境,理解安卓应用的核心组件,以及如何通过实际编码来构建一个功能完整的应用。无论你是编程新手还是希望提升自己的开发者