目录

  • 背景
  • 问题
  • 分析
  • 总结


背景

在device.mk文件中新增了一个persist类的property,名字为persist.control,device.mk中将其默认值设定位0。如下所示:

PRODUCT_PROPERTY_OVERRIDES += persist.control=0

进程P根据该property的值来选择执行不同的逻辑,开发者可以通过setprop命令或者property_set接口改变persist.control的值:

setprop persist.control 1
 getprop persist.control
1
#include <cutils/properties.h>
char get_prop_crtl[PROPERTY_VALUE_MAX];
property_get("persist.control", get_prop_crtl, "0");

问题

从上述的背景看,是相当简单的需求,修改persist.control的值后,在命令行启动进程P,也确实可以获取到修改后的persist.control的值并执行对应的结果。于是将进程P放到init.rc作为daemon service启动,通过名字为S的service启动进程P,service S的启动条件是on init:

service S /system/bin/P
    class core
    critical
    disabled
    user shell
    group shell
    seclabel u:r:shell:s0
...
on init
    start S

但是重启系统后发现,进程P并没有执行修改后的persist.control的值对应的逻辑,而是执行了persist.control默认值(即device.mk中设定的0)对应的逻辑,但是通过getprop命令查看,persist.control的值确实被修改为1了。

分析

之前也有使用persist类property的经验,但是一直没有了解它的原理,趁这个机会翻了网上的资料和代码,发现persist类property的设置和获取大概是这样:

  1. 在mk文件设定persist类property的默认值;
  2. 编译后persist类property以及其默认值会被汇总到.prop文件,如build.prop和default.prop;
  3. 系统启动后,init读取.prop中的property定义以及默认值,加载至内存;
  4. 对于persist类的property,读取/data/property/persistent_properties中保存的修改后的值,并更新到内存中,这里修改后的值,就是通过setprop命令或者property_set接口更新的值,可以在这个文件中查到,每当修改persist.control的值后,文件中对应的值也会发生相应变化

相关的代码可以查阅system/core/init和system/core/toolbox

从上述流程可以看出,猜测是进程P被启动前,init还没执行第4步,因为进程P被执行的时机是on init,为了验证这一猜想,将进程P的执行时间点,即service S的执行时间点从on init修改为on property:persist.control=1,即等init执行了上述第4步操作后,才去启动进程P:

service S /system/bin/P
    class core
    critical
    disabled
    user shell
    group shell
    seclabel u:r:shell:s0
...
on property:persist.control=1
    start S

修改后,进程P确实运行了persist.control=1对应的逻辑,验证了猜想

除此之外,还有另一种修改方法,在system/core/init/property_service.cpp代码中可以看到,当通过/data/property/persistent_properties完成persist类property的值的更新后,会设置ro.persistent_properties.ready为true:

switch (init_message.msg_case()) {
        case InitMessage::kLoadPersistentProperties: {
            load_override_properties();
            // Read persistent properties after all default values have been loaded.
            auto persistent_properties = LoadPersistentProperties();
            for (const auto& persistent_property_record : persistent_properties.properties()) {
                InitPropertySet(persistent_property_record.name(),
                                persistent_property_record.value());
            }
            InitPropertySet("ro.persistent_properties.ready", "true");
            persistent_properties_loaded = true;
            break;
        }
        default:
            LOG(ERROR) << "Unknown message type from init: " << init_message.msg_case();
}

因此,也可以将启动service S的条件设置为on property:ro.persistent_properties.ready=true,即:

service S /system/bin/P
    class core
    critical
    disabled
    user shell
    group shell
    seclabel u:r:shell:s0
...
on property:ro.persistent_properties.ready=true
    start S

这里可能会有疑惑,为什么ro类的property可以重新设置,其实是因为ro.persistent_properties.ready这个property并没有在代码中被设定默认值,即代码中没有定义ro.persistent_properties.ready这个property,但是却有根据ro.persistent_properties.ready的值去触发操作的逻辑,这在Android系统中是允许的,即用setprop或者property_set接口去设置一个代码中未定义的property的值,注意ro类的property只能设置一次,如果想要重新设置,则需要重启机器,因为这类未在代码定义的property的值,是不会固化保存的(persist类除外,因为这类property是保存在data分区的)。

对于已经在代码中定义好初始值的ro类property,则无法修改对应的值,因为它们的初始值都被保存在system和vendor等只读分区,每次启动都会从对应的prop文件中读取默认值到内存。

从上面可以看出,所谓的ro类的property,严格来说并不是真的只读,而是在每次系统启动后,只有一次设置值的机会,对于已经在代码中定义好的property,都会由系统加载默认值到内存,所以这里的ro,贴切来说,应该是内存中的property只允许赋值一次。

总结

遇到这类系统原生机制导致的问题,还是要回到源码本身和机制的原理进行分析