Android Init 系列专题【篇一:property_service】

目录

一、系统属性初始化

1、系统属性的本质

2、接收来自kernel的参数

2.1 kernel参数如何转换成属性?

流程1:ProcessKernelDt

流程2:ProcessKernelCmdline

流程3:ProcessBootconfig

流程4:ExportKernelBootProps

2.2 如何自定义kernel参数?

1)MTK平台

2)高通平台

3)原生平台

3、初始化所有系统属性

二、系统属性监听

1、属性后台任务PropertyServiceThread

​编辑

2、属性的设置PropertySet

3、属性改变的监听NotifyPropertyChange

三、系统属性相关案例

案例一:ro属性实现允许被修改

案例二:PRODUCT_PRODUCT_PROPERTIES :=和+=的区别


init进程在启动的第二阶段,需要做的最重要事情之一就是提供属性的机制。在整个android领域的native层和fw层,以及app层,都可以通过属性来存储一些比较重要的键值对,init模块源码/system/core/init/property_service.cpp 专门提供了这样的服务,从命名来看可以叫他服务,其实她并不是我们理解的服务。

这里先附上此篇总结的init进程属性服务的流程图:

一、系统属性初始化

init在启动阶段,初始化property,其源码如下:

总结如下三个步骤:

  • 创建/dev/__properties__目录,并进行初始化,创建一些相关文件
  • 接受来自kernel的启动命令和启动参数相关配置,包括kernel额外的一些参数配置
  • 加载默认配置的一些系统属性

1、系统属性的本质

//system/core/init/property_service.cpp
void PropertyInit() {
    ....
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }....
}
void CreateSerializedPropertyInfo() {
    auto property_infos = std::vector<PropertyInfoEntry>();
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {
            return;
        }
        // Don't check for failure here, since we don't always have all of these partitions.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if (access("/dev/selinux/apex_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/dev/selinux/apex_property_contexts", &property_infos);
        }
        if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
                                     &property_infos);
        }
        if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                     &property_infos);
        }
        if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
                                     &property_infos);
        }
        if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
        }
    } else {
        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/dev/selinux/apex_property_contexts", &property_infos);
    }
    auto serialized_contexts = std::string();
    auto error = std::string();
    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                   &error)) {
        LOG(ERROR) << "Unable to serialize property contexts: " << error;
        return;
    }
    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}

如上代码逻辑首先创建了/dev/__properties__目录,然后在CreateSerializedPropertyInfo函数中创建了/dev/__properties__/property_info目录和里面看起来跟selinux scontext相关的东西。我找了一台remount的机器,进入此目录如下:

紧接着执行__system_property_area_init用来初始化属性空间,其实就是引用上文创建的这个目录,从这个文件里面函数的定义来,除了初始化之后还有get和set等定义,即个人猜测我们设置某条属性和获取某条属性就是通过此接口来实现,其本质就是通过文件的方式来保存。

最后property_info_area.LoadDefaultPath来序列号此文件里面的内容到内存中,其核心逻辑使用了mmap映射的方式:

2、接收来自kernel的参数

1)kernel参数如何转换成属性?

流程1:ProcessKernelDt

获取kernel设备树/proc/device-tree目录里面的一些信息,并设置ro.boot.相关属性

Android 设备树目录(Device Tree Directory)主要用于存储设备树(Device Tree)相关的信息。设备树是一种描述硬件设备信息和配置的数据结构,用于在启动时传递硬件信息给内核和操作系统。Android 设备树目录的主要作用包括:

  • 描述硬件信息:设备树用于描述系统中的硬件设备,包括处理器、内存、外设等的配置信息,以便系统能够正确地识别和与这些硬件设备进行交互。
  • 硬件配置:设备树包含了硬件设备的配置信息,如中断控制器、时钟频率、设备地址等,这些信息对于系统的正常运行至关重要。
  • 与内核交互:设备树在系统启动时被加载到内存中,内核通过解析设备树来获取硬件信息并配置相应的驱动程序,从而实现对硬件设备的管理和控制。
  • 跨平台兼容性:设备树的使用可以使得相同的内核可以在不同硬件平台上运行,只需相应修改设备树即可适配不同硬件配置,提高了系统的可移植性和兼容性。

流程2:ProcessKernelCmdline

解析kernel传递过来的cmdline参数(路径/proc/cmdline),并设置ro.boot.相关的属性。如下逻辑

//system/system/core/init/util.cpp
void ImportKernelCmdline(const std::function<void(const std::string&, const std::string&)>& fn) {
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) {
        std::vector<std::string> pieces = android::base::Split(entry, "=");
        if (pieces.size() == 2) {
            fn(pieces[0], pieces[1]);
        }
    }
}
//system/system/core/init/property_service.cpp
constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
static void ProcessKernelCmdline() {
    //解析kernel的cmdline,读取/proc/cmdline文件内容
    ImportKernelCmdline([&](const std::string& key, const std::string& value) {
        //解析cmdline内容,如果是以androidboot开头的参数,就进入判断设置属性
        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
            //设置属性的时候将其拼接变成ro.boot.
            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
        }
    });
}

cmdline为bootloader传递给kernel的参数,cat cmdline和获取ro.boot属性如下,即我们平时的这类属性都是从cmdline中获取的

流程3:ProcessBootconfig

解析kernel传递过来的config配置(路径/proc/bootconfig),并设置ro.boot.相关的属性。如下逻辑

//system/system/core/init/util.cpp
void ImportBootconfig(const std::function<void(const std::string&, const std::string&)>& fn) {
    std::string bootconfig;
    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);

    for (const auto& entry : android::base::Split(bootconfig, "\n")) {
        std::vector<std::string> pieces = android::base::Split(entry, "=");
        if (pieces.size() == 2) {
            // get rid of the extra space between a list of values and remove the quotes.
            std::string value = android::base::StringReplace(pieces[1], "\", \"", ",", true);
            value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
            fn(android::base::Trim(pieces[0]), android::base::Trim(value));
        }
    }
}
//system/system/core/init/property_service.cpp
constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
static void ProcessBootconfig() {
    ImportBootconfig([&](const std::string& key, const std::string& value) {
        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
        }
    });
}

config同上面一致,在/proc/bootconfig文件里面,但是我在这台机器上面并没有获取出来:

流程4:ExportKernelBootProps

设置其他ro.boot.等值,这里的逻辑像是先去GetProperty了一遍,如果没有值就设置默认值

2)如何自定义kernel参数?

kernel的参数传递,在不同平台不一致,这里分别介绍一下MTK和高通是如何实现的。

1)MTK平台

MTK平台的定制在mt_boot.c文件,大概逻辑如下:

/aosp?vendor/mediatek/proprietary/bootable/bootloader/lk/app/mt_boot/mt_boot.c
//流程1:入口函数
APP_START(mt_boot)
.init = mt_boot_init,
 APP_END
//流程2:执行mt_boot_init
void mt_boot_init(const struct app_descriptor *app){
    //.....
	boot_linux_from_storage();
    //.....
}
//流程3:执行boot_linux_from_storage
int boot_linux_from_storage(void){
    //.....
    boot_linux((void *)kernel_target_addr,(unsigned *)tags_target_addr,board_machtype(),(void *)ramdisk_target_addr,ramdisk_real_sz);
	return 0;
}
//流程4:执行boot_linux
void boot_linux(void *kernel, unsigned *tags, unsigned machtype, void *ramdisk, unsigned ramdisk_sz){
#ifdef DEVICE_TREE_SUPPORT
	boot_linux_fdt((void *)kernel, (unsigned *)tags, machtype, (void *)ramdisk, ramdisk_sz);
	panic("%s Fail to enter EL1\n", __func__);
#endif
}
//流程5:执行boot_linux_fdt
int boot_linux_fdt(void *kernel, unsigned *tags,  unsigned machtype,  void *ramdisk, unsigned ramdisk_sz){
    //....
	get_reboot_reason(boot_reason);
    //....
}

其中有待研究的一些kernel参数如下:

2)高通平台

高通平台的定制在UpdateCmdLine.c文件,大概逻辑如下:

3)原生平台

google提供了一套原生的方式来添加自定义kernel cmd键值对,使用编译宏控BOARD_KERNEL_CMDLINE,如下代码通常在device目录下配置:

此宏控为什么生效?是因为编译脚本又如下配置:

/build/make/core/board_config.mk 
INTERNAL_KERNEL_CMDLINE := $(BOARD_KERNEL_CMDLINE)
ifneq (,$(BOARD_BOOTCONFIG))
  INTERNAL_KERNEL_CMDLINE += bootconfig
  INTERNAL_BOOTCONFIG := $(BOARD_BOOTCONFIG)
endif

3、初始化所有系统属性

4、PropertyInit函数没有执行完?

虽然流程如上,但是在实际开发调试过程中,我发现PropertyInit函数的日志只打印了一点点,如下调试:

当前还不是很清楚为什么,init进程在这里突然嘎然而止了呢?针对此种现象在第二章会重点说明一下。

二、系统属性服务的启动

这里介绍一下系统属性是如何被设置,并且某个属性被设置的时候,init触发器机制是如何监听的?

其实它是依赖于init进程的一个后台任务,因为它平时并不运行,但是通过了linux socket机制可以对齐进行唤醒。我们先来看看StartPropertyService(同样被init进程在启动的第二阶段),字面意思就是开启系统属性服务,其实并不是真正意义上的服务,源码如下:

//system/system/core/init/property_service.cpp
void StartPropertyService(int* epoll_socket) {
    InitPropertySet("ro.property_service.version", "2");
    LOG(INFO) << "----SHEN: property_service StartPropertyService start";
    int sockets[2];
    //流程1:创建一对 ‌双向通信的 Unix 域套接字‌(AF_UNIX),用于 init 进程与属性服务间的 IPC。SOCK_SEQPACKET 保证消息顺序,SOCK_CLOEXEC 防止子进程继承
    if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
        PLOG(FATAL) << "Failed to socketpair() between property_service and init";
    }
    //双向套接字类似于TCP,例如向sockets[0]写数据,sockets[1]可以直接读取到,实现一个全双工
    //sockets[0] 赋值给函数参数epoll_socket,这个指针其实是init进程传递进来,那么init进程可以拿到这个socket[0],来直接合socket[1]进行通信
    //sockets[1] 赋值给init_socket,用于接收 init 进程的消
    *epoll_socket = from_init_socket = sockets[0];
    init_socket = sockets[1];
    StartSendingMessages();
    //流程2:创建属性服务主Socket,路径为/dev/socket/property_service
    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, /*passcred=*/false, /*should_listen=*/false, 0666, /*uid=*/0, /*gid=*/0, /*socketcon=*/{});
        result.ok()) {
        property_set_fd = *result;
    } else {
        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
    }
    LOG(INFO) << "----SHEN: property_service StartPropertyService listen property_set_fd";
    //流程3:监听property_set_fd,最大连接队列为8
    listen(property_set_fd, 8);
    LOG(INFO) << "----SHEN: property_service StartPropertyService property_service_thread";
    //流程4:创建独立线程运行 PropertyServiceThread,处理属性设置请求(如 property_set() 调用)
    auto new_thread = std::thread{PropertyServiceThread}; //此行代码会创建并启动线程,线程呗启动后立即执行PropertyServiceThread函数
    property_service_thread.swap(new_thread);//交换线程的管理权给property_service_thread控制
    auto async_persist_writes = android::base::GetBoolProperty("ro.property_service.async_persist_writes", false);
    if (async_persist_writes) {
        persist_write_thread = std::make_unique<PersistWriteThread>();
    }
    LOG(INFO) << "----SHEN: property_service StartPropertyService end";
}

1、属性服务与init之间的通信

在StartPropertyService函数中,主要几个核心流程如下:

  • 创建了一对双向通信的Unix域套接字‌(socketpair):用于和init主进程进行通信
  • 创建系统服务套接字(Socket):用于监听属性存储文件/dev/socket/property_service的数据变化
  • 监听系统服务套接字(listen):监听系统服务套接字并设置最大连接队列8
  • 创建系统服务线程(PropertyServiceThread):注册监听器并进入loop轮询

1)双向套接字sockets[0]

socketpair是Linux/UNIX系统中用于创建一对相互连接的匿名套接字的系统调用,主要服务于本地进程间通信(IPC)。通常使用场景:父进程与子进程之间的通信;多线程之间的通信;替代复杂的C/S通信模式。有如下几个特点:

  • 写入sv[0]的数据只能从sv[1]读取,反之亦然
  • 若未写入数据时读取会阻塞
  • 需显式关闭未使用的描述符以避免资源泄漏

sockets[0]被赋值给epoll_socket,他又是StartPropertyService函数传递进来的参数,这个函数在init主函数中被调用,即sockets[0]被init进程持有:

根据双向socket可以知道,init进程持有之后,拿来做什么呢?

即SendLoadPersistentPropertiesMessage函数来给这个sockets[0]发送消息,从消息的名字set_load_persistent_properties来看用来加载persistent属性的动作。即这个时候sockets[1]就能够监听得到这个消息,后续会讲解他在系统服务线程PropertyServiceThread中处理这个消息。

这里继续讨论一下,init进程什么时候会调用SendLoadPersistentPropertiesMessage函数来发送消息呢?

即在post-fs-data的事件触发的时候,通知init进程去进行load_persist_props动作,去加载系统属性。

2)主套接字property_set_fd

接下来在看看上面这段代码,createsocket用来创建套接字,这里专门是为了属性服务进行创建的,经过研读此函数和PROP_SERVICE_NAME来看,其实就是在/dev/socket目录下面创建了一个名为property_service文件的特殊文件:

2、PropertyServiceThread线程轮询监听

并且后续还创建了一个PropertyServiceThread线程来对其进行监听和轮询:

//system/core/init/property_service.cpp 
static void PropertyServiceThread() {
    Epoll epoll;
    //创建并打开epoll:epoll是linux中用来同时管理监听上千个套接字,只要套接字中有事件触发,就会回调后面指定的函数指针
    if (auto result = epoll.Open(); !result.ok()) {
        LOG(FATAL) << result.error();
    }
    LOG(INFO) << "----SHEN: property_service PropertyServiceThread epoll.handle_property_set_fd";
    //注册函数handle_property_set_fd监听文件描述符property_set_fd
    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd);
        !result.ok()) {
        LOG(FATAL) << result.error();
    }
    //注册函数HandleInitSocket监听init_socket
    if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result.ok()) {
        LOG(FATAL) << result.error();
    }
    //进入轮询:从epoll中读取事件,epoll.Wait(std::nullopt)阻塞读取
    while (true) {
        auto epoll_result = epoll.Wait(std::nullopt);
        LOG(INFO) << "----SHEN: property_service PropertyServiceThread loop";
        if (!epoll_result.ok()) {
            LOG(ERROR) << epoll_result.error();
            LOG(INFO) << "----SHEN: property_service PropertyServiceThread loop " << epoll_result.error();
        }
    }
}

这里使用了linux的epoll机制。epoll是Linux内核提供的一种高效I/O多路复用机制,专门用于管理大量文件描述符(包括socket)。它可以‌同时监听数千甚至数万个socket‌,是现代高性能网络服务器的核心技术(如Nginx、Redis等)

从上面的逻辑来看,epoll同时监听了两个套接字:

  • property_set_fd:系统属性服务主套接字,在handle_property_set_fd函数中响应
  • sockets[1]:init_socket套接字就是前面提到的双向套接字的另一端,在HandleInitSocket函数中响应

1)双向套接字sockets[1]的响应:HandleInitSocket

本章第一小节提到,sockets[0]和sockets[1]是一堆双向套接字,在init主线程在post-fs-data阶段的时候会触发load_persist_props函数向sockets[0]发送消息,sockets[1]就会收到消息,并且在如下函数中响应,用来加载系统属性的一些初始值:

如上代码,即init进程拿到sockets[0]之后,在post-fs-data阶段,通过如上方式去触发,对系统属性的一些初始化。这种方式感觉怪怪的,为什么不直接在PropertyInit中执行?难道是因为系统属性服务相关依赖文件系统的挂载?

2)主套接字property_set_fd的响应:handle_property_set_fd

再来看看property_set_fd套接字的监听函数:根据如下的大概逻辑可以了解到,直接通过accept从套接字中读取数据,并讲结果转换层cmd命令,通常是设置系统属性。

3、系统属性的设置与监听

handle_property_set_fd里面对日志进行了设置和处理:

1)PropertySet属性的设置

属性的设置流程大概如上,主要还是通过__system_property_find和__system_property_update和__system_property_add这些调用来实现对/dev/__properties__文件目录的一些控制

值得注意的就是persist这类属性是写入到内存里面的,参考

2)NotifyPropertyChange属性的监听

4、property_service日志断层问题?

如上日志,init进程中间存在5秒的断档,这5秒期间应该有如下流程:

  • PropertyInit里面的ProcessKernelDt/ProcessKernelCmdline/ProcessBootconfig/ExportKernelBootProps/PropertyLoadBootDefaults的日志
  • StartPropertyService里面的HandleInitSocket日志

但是最后有奇迹般的出现StartPropertyService里面loop循环,最初以为是进程挂掉了,后面综合init整个进程的角度来看,我更加比较倾向中间这5秒无法输出日志?具体原因有待确定

三、总结与相关案例

1、流程图

如上流程图,我这里给出我的一些观后感:

  • PropertyInit函数被调用的时机太早,虽然文件系统已经准备好,但是只能访问dev目录,像data目录都还没有挂载,所以这个函数并不是从/data/property/persistent_properties中进行加载,而是从kernel参数以及启动参数去初始化属性
  • StartPropertyService函数主要目的是去启动属性服务,主要目的就是创建套接字,创建监听器,最后必须要进入轮询,为后续属性动态监听和响应init.rc来作准备

  • post-fs-data事件触发的时候才去调用LoadPersistenProperties函数去加载/data/property/persistent_properties中的属性。为什么要选择在这个时候去加载?因为这个时候才挂载好data目录

2、案例之ro属性实现允许被修改

在init进程中进行ro属性设置,如果此属性已经被设置过或者被初始化过,那么将会报如下异常,错误码为0xb,PROP_ERROR_READ_ONLY_PROPERTY被定义为0x0b。

但是我们可以更正这里的逻辑,让我们能够setprop已经存在的ro属性,参考案例如下:

3、案例之PRODUCT_PRODUCT_PROPERTIES :=和+=的区别

接着如上案例,因为ro.setupwizard.rotation_locked为ro属性,因此只能被赋值一次且无法修改,那么考虑使用PRODUCT_PRODUCT_PROPERTIES来进行初始化,在实验中发现如下两种有趣的现象:

  • 写法一:先进行+=对ro属性进行初始化,然后通过:=对ro属性进行重新设置,最后的结果是true,即:=没有对ro属性进行一个覆盖?第二行的:=居然指向失败?
#先初始化ro属性
PRODUCT_PRODUCT_PROPERTIES +=  ro.setupwizard.rotation_locked=true 
#重新设置ro属性,设置失败
PRODUCT_PRODUCT_PROPERTIES := ro.setupwizard.rotation_locked=false
#结论一:如果PRODUCT_PRODUCT_PROPERTIES对某ro属性定义了两次,并没有对属性的所有定义进行整合?即后面定义的值无法覆盖前文定义的值?那么init初始化属性的时候对其进行了两次设置?
#结论二:init初始化属性的时候在第二次设置的时候,针对ro属性直接设置失败,即ro属性只能被初始化一次
  • 写法二:先进行:=ro属性进行设置,然后通过+=对ro属性进行设置,最后结果是false
#先初始化ro属性
PRODUCT_PRODUCT_PROPERTIES := ro.setupwizard.rotation_locked=false 
#重新设置ro属性,设置失败
PRODUCT_PRODUCT_PROPERTIES +=  ro.setupwizard.rotation_locked=true
#结论一:因为已经存在值,所以+=直接无效

<think>嗯,用户想了解关于vendor和ODM的property_contexts必须使用vendor前缀属性的规定、原因和相关规范。首先,我需要明确Android系统中的property_contexts是什么。property_contexts是SELinux策略的部分,用于定义哪些进程可以访问特定的系统属性。每个属性都有个安全上下文,确保只有授权的进程才能修改或读取它们。 接下来,用户提到的vendor和ODM部分。在Android中,vendor和ODM(Original Design Manufacturer)通常负责设备特定的硬件和驱动,所以他们的属性需要与系统其他部分隔离,防止冲突。使用vendor前缀的属性,比如以“vendor.”开头,可以避免命名冲突,因为不同的厂商可能有相同的属性名,但加上前缀后就能唯标识。 然后是规范方面,Android兼容性文档(CDD)里应该有这样的规定,确保不同厂商的属性不会互相干扰。另外,SELinux的策略需要严格的安全控制,使用前缀能帮助明确权限范围,避免越权访问。比如,只有vendor分区的进程才能访问以vendor.开头的属性,这通过property_contexts文件中的规则来实现。 可能的原因还包括模块化设计,这样各厂商的代码和配置可以独立维护,升级时不会影响系统其他部分。此外,在Android 8.0之后,Treble架构要求模块化分离,vendor和ODM的组件需要与系统解耦,使用独立的前缀是这种架构的部分。 用户还可能想知道具体的实现方式,比如property_contexts文件中如何定义这些带前缀的属性,以及违反规定会导致什么问题,比如权限错误或系统不稳定。需要引用Android官方文档或CDD中的相关内容来支持这些解释。 最后,生成的相关问题需要围绕这些方面,比如具体规范文件、安全机制、Treble架构的影响、冲突案例以及如何检查合规性。确保问题覆盖用户可能进步想了解的点。</think>关于Android系统中vendor和ODM的`property_contexts`必须使用vendor前缀属性的规定,主要涉及以下技术规范和安全机制: ### 、核心规范要求 根据Android兼容性定义文档(CDD): 1. **属性命名空间隔离** vendor/ODM自定义属性必须采用`vendor.`、`odm.`或`persist.vendor.`等前缀,例如: $$ property_name vendor.custom.feature.enabled $$ 这种命名方式确保与AOSP原生属性隔离,避免命名冲突[^1]。 2. **SELinux策略约束** 在`property_contexts`文件中,vendor/ODM属性需通过前缀匹配定义访问规则: ``` vendor.custom.* u:object_r:vendor_prop:s0 ``` ### 二、技术限制原因 1. **Treble架构强制解耦** Android 8.0引入的Project Treble要求: - Vendor实现与框架层解耦 - 通过`vendor.img`独立分区实现 - 前缀属性作为接口标识符确保跨版本兼容 2. **安全边界控制** - 仅允许`vendor_init`进程修改vendor前缀属性 - 通过SELinux策略限制访问范围: ``` allow vendor_hal_process vendor_prop:property_service set; ``` ### 三、典型违规后果 1. **CTS/VTS测试失败** 未使用前缀的属性会导致兼容性测试失败: ```log [FAIL] CtsSecurityTestCases: PropertyNamespaceCheck ``` 2. **权限提升风险** 若系统服务可读写未隔离属性,可能引发: - 权限绕过漏洞(CVE-2020-0457) - 拒绝服务攻击(DoS) ### 四、实现示例 合规的`property_contexts`声明: ```text # vendor/odm属性 vendor.debug.trace u:object_r:debug_prop:s0 persist.odm.log.level u:object_r:odm_log_prop:s0 # 系统属性(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸神黄昏EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值