• Android属性系统


    基于AndroidO 分析

    Android属性系统初始化流程

    1. property_init

      init进程的第二个阶段 调用property_init初始化 属性系统。

      此阶段属性文件还未解析,解析的是property_contexts文件。

      // system/core/init/property_service.cpp
      void property_init() {
          if (__system_property_area_init()) {
              exit(1); 
          }
      }
      

      解析错误将直接退出init进程,哈哈哈,可见属性系统的重要性。

      __system_property_area_init() 主要工作如下:

      • 创建/dev/__properties__文件夹。

      • 解析property_contexts文件。

        Android 版本 在 N 及N以前, 对应的 文件路径是 /property_contexts;Android O开始,对应的文件路径是/system/etc/selinux/plat_property_contexts/vendor/etc/selinux/nonplat_property_contexts

      • 解析过程会创建两个链表, contextsprefixes。解析完成后,会遍历contexts链表,为其中的每一个context_node创建一个关联文件,并且打开该文件将其映射到进程的虚拟地址空间,并在该地址空间上构建一个prop_area对象。

      解析之后的各个属性之间之间的关系如下:
      安卓属性系统流程图
      对照真实设备的/dev/__properties目录:

      image-20210401174758800

    2. process_kernel_dt

      解析dts中,内核配置的属性。 目录是/proc/device-tree/firmware/android/

    3. process_kernel_cmdline

      处理/proc/cmdline中的属性,由顺序可以知道,dts中的properties 的优先级大于command-line

    4. export_kernel_boot_props

      将前面内核comand-line中的属性转换成init 进程需要的属性。这种操作不是很懂暂时。

    5. property_load_boot_defaults

      解析默认的属性文件,这些文件包括:

      /system/etc/prop.default
      /odm/default.prop
      /vendor/default.prop
      
    6. start_property_service

      打开属性服务,这里会创建一个 unix domain soket(/dev/socket/property_service),然后设置为监听模式,加入到init 进程的 epoll实例中,其他进程可以通过 /dev/socket/property_service 通知 property service 设置属性。

      属性的设置需要交由 property_service来完成,这样才能实现on property: ***这样的action,以及一些全局的属性更改的监听。获取属性则无需通过property_service,进程在打开libc 库的时候就会自动完成相应属性prop_area的映射。

    到这里,init进程的main函数中关于属性系统的初始化操作基本结束。但是/system/build.prop在哪里加载呢???

    既然是在/system分区下,就肯定要等到/system分区被挂载才能解析:

    on post-fs                                                                    
        # Load properties from                                                    
        #     /system/build.prop,                                                 
        #     /odm/build.prop,                                                    
        #     /vendor/build.prop and                                              
        #     /factory/factory.prop                                                    
        load_system_props                                                   
        # start essential services                               
        start logd                                                              
        start servicemanager                        
        start hwservicemanager                             
        start vndservicemanager
    

    对应的load_system_props就是用于解析build.propbuiltin命令,其实现位于system/core/init/builtins.cpp@do_load_system_props中。

    此外,还有一种persist属性,其能够持久的保存属性值,哪怕是我们重启系统,解析persist属性也是在init.rc中触发,触发顺序如下:

    on late-init 
    # Load persist properties and override properties (if enabled) from /data.              
        trigger load_persist_props_action
    
    on load_persist_props_action                                                    
        load_persist_props                                                                      
        start logd                                                                              
        start logd-reinit 
    

    对应的load_persist_props就是用于解析build.propbuiltin命令,其实现位于system/core/init/builtins.cpp@do_load_persist_props中。

    这些持久化的属性都保存在/data/property目录下,属性名对应文件名,文件内容对应属性值。

    image-20210401210616705

    再看一下load_persist_props的注释。

    /* When booting an encrypted system, /data is not mounted when the
     * property service is started, so any properties stored there are
     * not loaded.  Vold triggers init to load these properties once it
     * has mounted /data.
     */
    

    属性系统的内存管理

    1. prop_bt

      ro.build.product为例,将该属性key 以.分割成robuildproduct三个部分,每个部分都对应一个prop_bt

      // Represents a node in the trie.
      struct prop_bt {
        uint32_t namelen; 			// 名称长度
        atomic_uint_least32_t prop;	// prop_info 的地址
        atomic_uint_least32_t left;	// 兄弟节点地址  兄弟节点按照名称从小到大排列。
        atomic_uint_least32_t right;  // 兄弟节点地址
      
        atomic_uint_least32_t children; // 子节点地址
        char name[0]; // 名称 `ro` `build` `product`
      };
      
    2. prop_info

      prop_bt构成的三叉树中的叶子节点。记录属性值。

      struct prop_info {
        atomic_uint_least32_t serial;
       
        char value[PROP_VALUE_MAX];	// 固定value的长度,哈哈哈,更新属性值得时候就简单多了。。。。	
        char name[0];
      
        prop_info(const char* name, uint32_t namelen, const char* value, uint32_t valuelen) {
          memcpy(this->name, name, namelen);
          this->name[namelen] = '';
          atomic_init(&this->serial, valuelen << 24);
          memcpy(this->value, value, valuelen);
          this->value[valuelen] = '';
        }
      };
      
    3. prop_area

      描述一个内存区域,通过mmapprop_area/dev/__properties/<file>映射起来。

      class prop_area {
        uint32_t bytes_used_;
        atomic_uint_least32_t serial_;
        uint32_t magic_;
        uint32_t version_;
        uint32_t reserved_[28];
        char data_[0];		// 这里开始就是 prop_bt了。
      }
      

    总结一下这三个数据结构的关系。以源码注释中的为例:

    /*
     * Properties are stored in a hybrid trie/binary tree structure.
     * Each property's name is delimited at '.' characters, and the tokens are put
     * into a trie structure.  Siblings at each level of the trie are stored in a
     * binary tree.  For instance, "ro.secure"="1" could be stored as follows:
     *
     * +-----+   children    +----+   children    +--------+
     * |     |-------------->| ro |-------------->| secure |
     * +-----+               +----+               +--------+
     *                       /                    /   |
     *                 left /       right   left /    |  prop   +===========+
     *                     v        v            v     +-------->| ro.secure |
     *                  +-----+   +-----+     +-----+            +-----------+
     *                  | net |   | sys |     | com |            |     1     |
     *                  +-----+   +-----+     +-----+            +===========+
     */
    

    放到连续的地址空间中的布局大概如下:

    属性系统内存布局

    属性名被.分割成多个segment,每一个segment对应一个prop_bt。这些prop_bt会组织成一棵三叉树,兄弟节点之间按照大小关系从右到左排列。

    当设置属性时,首先根据属性key查找prefixes链表,找到匹配的context_node,进而就能找到关联的prop_area。然后按照上面的规则将属性插入到三叉树中。

    三叉树的叶子节点一般都是prop_info,其完整的记录了属性名和属性值。具体的实现细节参考property_set的实现

    初始化过程详细代码

    *property_init

    主要代码位于bionic/libc/bionic/system_properties.cpp中。

    property_init主要作用是解析property_contexts文件,并创建不同属性对应的缓冲区文件及完成缓冲区文件和prop_area的内存映射。

    int __system_property_area_init() {
      free_and_unmap_contexts();
      // [1] 创建 /dev/__properties__ 
      mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);
    
      // [2] 解析  <noplat/plat>_property_contexts !创建 属性前缀 链表 和 context 链表, 并 建立 prefix 和 context 之间的关联。
      if (!initialize_properties()) {
        return -1;
      }
      bool open_failed = false;
      bool fsetxattr_failed = false;
      
    
      // 遍历 contexts 链表, 对每一个 context_node 执行 open 操作(创建 context file,映射到用户进程空间,在改地址空间上构造 prop_area)
      list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
        if (!l->open(true, &fsetxattr_failed)) {
          open_failed = true;
        }
      });
    
      // map_system_property_area 创建  properties_serial  文件 及 u:object_r:properties_serial:s0 context
      if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {
        free_and_unmap_contexts();
        return -1;
      }
      initialized = true;
      return fsetxattr_failed ? -2 : 0;
    }
    
    1. initialize_properties

      首先从/system/vendor目录下加载property_contexts , 如果不存在,就去根目录下加载。

      static bool initialize_properties() {
         // androidN 及之前的版本存在该 文件
        if (initialize_properties_from_file("/property_contexts")) {
          return true;
        }
      
        if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {  // exist
          if (!initialize_properties_from_file("/system/etc/selinux/plat_property_contexts")) {
            return false;
          }
          initialize_properties_from_file("/vendor/etc/selinux/nonplat_property_contexts");
        } else {
          if (!initialize_properties_from_file("/plat_property_contexts")) {
            return false;
          }
          initialize_properties_from_file("/nonplat_property_contexts");
        }
      
        return true;
      }
      

      首先看一下property_contexts 的格式。

      *                       u:object_r:default_prop:s0
      bluetooth.              u:object_r:bluetooth_prop:s0
      config.                 u:object_r:config_prop:s0
      ctl.                    u:object_r:ctl_default_prop:s0
      ctl.bootanim            u:object_r:ctl_bootanim_prop:s0
      ctl.bugreport           u:object_r:ctl_bugreport_prop:s0
      ctl.console             u:object_r:ctl_console_prop:s0
      ctl.dumpstate           u:object_r:ctl_dumpstate_prop:s0
      ...
      

      每一行分为两部分,属性前缀和对应的 Selinux Context。在解析代码中,属性前缀使用prefix_node表示,Selinux Context使用context_node表示。

      initialize_properties_from_file将相应的 property_contexts 文件 解析成 prefix_node* prefixescontext_node* contexts两个链表。这里,对我来说比较难理解的就是链表的添加过程了,其按照prefix_node->prefix_len按照从大到小的顺序排列,即头结点的prefix_len最大,此外,由于*属于通配符号,所以其需要放到最后。

      class context_node {
       public:
        context_node(context_node* next, const char* context, prop_area* pa)
            : next(next), context_(strdup(context)), pa_(pa), no_access_(false) {
          lock_.init(false);
        }
        context_node* next;
      };
      
      struct prefix_node {
        prefix_node(struct prefix_node* next, const char* prefix, context_node* context)
            : prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) {
        }
        struct prefix_node* next;
      };
      
      // 支持 prefix_node 和 context_node的添加
      template <typename List, typename... Args>
      //注意这里,List 是一个二级指针
      static inline void list_add(List** list, Args... args) {
        *list = new List(*list, args...);
      }
      
      // list 是一个二级指针
      static void list_add_after_len(prefix_node** list, const char* prefix, context_node* context) {
        size_t prefix_len = strlen(prefix);
      
        auto next_list = list;
      
        while (*next_list) {
          if ((*next_list)->prefix_len < prefix_len || (*next_list)->prefix[0] == '*') {
             	// 进入到这里,next_list = &curr_node->next;,模板函数展开就是:
              // curr_node->next = new prefix_node(curr_node->next, prefix, context);
              // 这样就完成了 节点插入操作。这段代码在用到 链表时值得借鉴。
              list_add(next_list, prefix, context);
            return;
          }
          // 指向当前节点的 next 的地址
          next_list = &(*next_list)->next;
        }
        // 在这里, *next_list = nullptr; 第一次插入时满足该情况。此时next_list == &prefixs
        // 在尾部添加时也满足,此时,next_list ==  &curr_node->next
        list_add(next_list, prefix, context);
      }
      

      理解了上面这段代码,initialize_properties_from_file就不足畏惧了:

      
      static bool initialize_properties_from_file(const char* filename) {
        // 打开文件
        FILE* file = fopen(filename, "re");
      
        char* buffer = nullptr;
        size_t line_len;
        char* prop_prefix = nullptr;
        char* context = nullptr;
        // 逐行读取文件
        while (getline(&buffer, &line_len, file) > 0) {
          // 读取 prefix 和 context
          int items = read_spec_entries(buffer, 2, &prop_prefix, &context);
          if (items <= 0) {
            continue;
          }
          
          // ctl. 开头的属性属于控制属性,不需要保存到内存中。
          if (!strncmp(prop_prefix, "ctl.", 4)) {
            free(prop_prefix);
            free(context);
            continue;
          }
            
          // 先查找 context链表
          auto old_context =
              list_find(contexts, [context](context_node* l) { return !strcmp(l->context(), context); });
          if (old_context) {
            //context 存在,就直接将 prop_prefix 添加到链表中。
            list_add_after_len(&prefixes, prop_prefix, old_context);
          } else {
            // context 不存在,先将context添加到 contexts链表
            list_add(&contexts, context, nullptr);
            // 再将 prop_prefix 添加到 prefixes链表中
            list_add_after_len(&prefixes, prop_prefix, contexts);
          }
        }
        return true;
      }
      
    2. list_foreach(contexts)

      前面根据preoperty_context文件创建了两个链表,这里就遍历contexts链表,并且为每一个context_node创建一个128KB的文件,然后通过mmap将其映射到用户进程地址空间中,然后再在该地址空间上构建一个prop_area对象。

      list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
          if (!l->open(true, &fsetxattr_failed)) {
            open_failed = true;
          }
      });
      
      // context_node::open 函数是关键
      bool context_node::open(bool access_rw, bool* fsetxattr_failed) {
        char filename[PROP_FILENAME_MAX];  // 形如 /dev/__properties__/u:object_r:audio_prop:s0
        int len = async_safe_format_buffer(filename, sizeof(filename), "%s/%s", property_filename,
                                           context_);
      
        if (access_rw) {  // 初始化时为 true
          // 打开 context file,通过mmap映射到用户空间,并且在这段地址空间上 构造一个 prop_area 对象
          pa_ = map_prop_area_rw(filename, context_, fsetxattr_failed);
        } else {
          pa_ = map_prop_area(filename);
        }
        lock_.unlock();
        return pa_;
      }
      
      
      static prop_area* map_prop_area_rw(const char* filename, const char* context,
                                         bool* fsetxattr_failed) {
        //打开文件,没有就创建
        const int fd = open(filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
      
        if (context) {
          // 设置 selinux 信息,不太懂,感觉 调用者进程 selinux属于当前 context 就不能获取该context 文件中对应的属性。
          if (fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0) != 0) {
          }
        }
        // 将文件大小设置为 128K
        ftruncate(fd, PA_SIZE);
        
        //这里就和我们前面的示意图对上了。
        pa_size = PA_SIZE;
        pa_data_size = pa_size - sizeof(prop_area);
       
        // mmap
        void* const memory_area = mmap(nullptr, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        
        //在 mmap返回的地址上 构建 prop_area对象。
        prop_area* pa = new (memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
      
        close(fd);
        return pa;
      }
      

      关于 map_fd_ro之后遇到了用到的情况再说,整体流程差不多。

    process_kernel_dt

    获取内核设备树中的android 相关的信息,并设置到属性系统中。

    通过get_android_dt_dir()获取android的设备树信息路径。

    const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android/");
    
    static std::string init_android_dt_dir() {
        // Use the standard procfs-based path by default
        std::string android_dt_dir = kDefaultAndroidDtDir;
        // The platform may specify a custom Android DT path in kernel cmdline
        import_kernel_cmdline(false,
                              [&](const std::string& key, const std::string& value, bool in_qemu) {
                                  if (key == "androidboot.android_dt_dir") {
                                      android_dt_dir = value;
                                  }
                              });
        LOG(INFO) << "Using Android DT directory " << android_dt_dir;
        return android_dt_dir;
    }
    
    const std::string& get_android_dt_dir() {
        // Set once and saves time for subsequent calls to this function
        static const std::string kAndroidDtDir = init_android_dt_dir();
        return kAndroidDtDir;
    }
    

    默认路径就是/proc/device-tree/firmware/android/,然后设置过程就是该目录下除了compatiblename的其他文件的内容。

    process_kernel_cmdline

    处理内核启动参数中的属性信息。

    static void process_kernel_cmdline() {
        import_kernel_cmdline(false, import_kernel_nv);
        if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
    }
    
    void import_kernel_cmdline(bool in_qemu,
                               const std::function<void(const std::string&, const std::string&, bool)>& fn) {
        std::string cmdline;
        android::base::ReadFileToString("/proc/cmdline", &cmdline);
    	// /proc/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], in_qemu);
            }
        }
    }
    
    static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
        if (key.empty()) return;
    
        if (for_emulator) {  // 模拟器
            // In the emulator, export any kernel option with the "ro.kernel." prefix.
            property_set("ro.kernel." + key, value);
            return;
        }
    
        if (key == "qemu") {
            strlcpy(qemu, value.c_str(), sizeof(qemu));
        } else if (android::base::StartsWith(key, "androidboot.")) {
            property_set("ro.boot." + key.substr(12), value);
        }
    }
    

    可以看到,/proc/cmdline中,androidboot.*属性信息,会被更改为ro.boot.*的形式设置到属性系统中。

    export_kernel_boot_props

    static void export_kernel_boot_props() {
        char cmdline[1024];
        char* s3;
    
        struct {
            const char *src_prop;
            const char *dst_prop;
            const char *default_value;
        } prop_map[] = {
           // { "ro.boot.serialno",   "ro.serialno",   "", },
           // { "ro.boot.mode",       "ro.bootmode",   "unknown", },
            { "ro.boot.baseband",   "ro.baseband",   "unknown", },
            { "ro.boot.bootloader", "ro.bootloader", "unknown", },
            { "ro.boot.hardware",   "ro.hardware",   "unknown", },
            { "ro.boot.revision",   "ro.revision",   "0", },
        };
    
        proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) );
        s3 = strstr(cmdline, "androidboot.oem_unlocked=1");
    	
        
        if (s3 == NULL){
    	    //oem_unlocked is 0 or not set.
    #ifdef DISABLE_VERIFY
    	    property_set("ro.boot.verifiedbootstate","unsupported");
    #else
    	    property_set("ro.boot.verifiedbootstate","green");
    #endif
        }else{
    	    property_set("ro.boot.verifiedbootstate","orange");
        }
    
        if(strstr(cmdline, "storagemedia=") != NULL){
            // 额,这里并没有设置啊啊啊
            LOG(INFO) << "Set ro.boot.storagemedia!";
        } else {
            LOG(INFO) << "Set ro.boot.storagemedia to unknown, may booting from unknown device";
            property_set("ro.boot.storagemedia", "unknown");
        }
    
        for (size_t i = 0; i < arraysize(prop_map); i++) {
            std::string value = GetProperty(prop_map[i].src_prop, "");
            property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
        }
    }
    

    /proc/cmdline 中有这么一个 androidboot.hardware=rk30board字符串,process_kernel_cmdline 处理后,属性系统中就会添加一个ro.boot.hardware=rk30board的属性,回到export_kernel_boot_props,该函数值得作用就是将prop_map数组中的那些内核属性,更改为android标准属性,对于cmdline中没有指定的属性,就设置为默认值。

    内核属性 android标准属性 默认值(cmdline中未设置)
    ro.boot.baseband ro.baseband unknown
    ro.boot.bootloader ro.bootloader unknown
    ro.boot.hardware ro.hardware unknown
    ro.boot.revision ro.revision 0

    记录这些主要还是为了混个眼熟,以后遇到这些属性,知道他们的值从哪来的。

    property_load_boot_defaults

    顾名思义,加载启动阶段的默认属性。

    
    void property_load_boot_defaults() {
        if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
            // Try recovery path
            if (!load_properties_from_file("/prop.default", NULL)) {
                // Try legacy path
                load_properties_from_file("/default.prop", NULL);
            }
        }
        load_properties_from_file("/odm/default.prop", NULL);
        load_properties_from_file("/vendor/default.prop", NULL);
    
        update_sys_usb_config();
    }
    

    load_properties_from_file这里就不做贴代码了,就是打开属性文件逐行读取,然后将每一行分割为keyvalue两部分,再调用property_set设置属性。

    这些默认属性都有哪些?

    RK平台:

    1|rk3399:/ # cat default.prop                                                  
    #
    # ADDITIONAL_DEFAULT_PROPERTIES
    #
    ro.secure=1
    security.perf_harden=1
    ro.allow.mock.location=0
    ro.debuggable=1
    #
    # BOOTIMAGE_BUILD_PROPERTIES
    #
    ro.bootimage.build.date=Mon Mar 29 15:53:44 CST 2021
    ro.bootimage.build.date.utc=1617004424
    ro.bootimage.build.fingerprint=rockchip/rk3399/rk3399:8.1.0/OPM8.190605.005/155344:userdebug/test-keys
    persist.sys.usb.config=adb
    
    rk3399:/ # cat /vendor/default.prop
    #
    # ADDITIONAL VENDOR DEFAULT PROPERTIES
    #
    ro.zygote=zygote64_32
    tombstoned.max_tombstone_count=50
    dalvik.vm.image-dex2oat-Xms=64m
    dalvik.vm.image-dex2oat-Xmx=64m
    dalvik.vm.dex2oat-Xms=64m
    dalvik.vm.dex2oat-Xmx=512m
    ro.dalvik.vm.native.bridge=0
    dalvik.vm.usejit=true
    dalvik.vm.usejitprofiles=true
    dalvik.vm.dexopt.secondary=true
    dalvik.vm.appimageformat=lz4
    pm.dexopt.first-boot=quicken
    pm.dexopt.boot=verify
    pm.dexopt.install=quicken
    pm.dexopt.bg-dexopt=speed-profile
    pm.dexopt.ab-ota=speed-profile
    pm.dexopt.inactive=verify
    pm.dexopt.shared=speed
    debug.atrace.tags.enableflags=0
    ro.enable_boot_charger_mode=0
    ro.board.platform=rk3399
    ro.target.product=tablet
    ro.oem_unlock_supported=1
    ro.adb.secure=0
    

    update_sys_usb_config的作用是啥?

    // persist.sys.usb.config values can't be combined on build-time when property
    // files are split into each partition.
    // So we need to apply the same rule of build/make/tools/post_process_props.py
    // on runtime.
    static void update_sys_usb_config() {
        bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
        std::string config = android::base::GetProperty("persist.sys.usb.config", "");
        if (config.empty()) {
            property_set("persist.sys.usb.config", is_debuggable ? "adb" : "none");
        } else if (is_debuggable && config.find("adb") == std::string::npos &&
                   config.length() + 4 < PROP_VALUE_MAX) {
            config.append(",adb");
            property_set("persist.sys.usb.config", config);
        }
    }
    

    从注释看说 persist.sys.usb.config 不能再编译期间就组合起来,所以再启动时,按照 build/make/tools/post_process_props.py的规则设置该属性。代码的大致意思是,如果 ro.debuggable == 1,即 eng 版本,那么先获取一下 persist.sys.usb.config 的值,如果 adb 不存在于这个值里面,就把adb 加进去。然后再设置一下 persist.sys.usb.config 这个值。如果 ro.debuggable == 0 的话,就不操作,如果无法获取 persist.sys.usb.config 的值,就把它置为 “none”。

    persist.sys.usb.config的作用是用来配置 usb 的模式,比如常用的 adb、MTP等

    *start_property_service

    void start_property_service() {
        // 设置 属性服务器版本,在AndroidO中,好像有两套 协议,后面分析就默认这里的 verison2
        property_set("ro.property_service.version", "2");
    	
        // 创建一个 unix domain socket, 该操作会在生成 /dev/socket/property_service,其它进程通过 unix socket api打开该文件就能建立和
        // property_service的连接
        property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                       false, 0666, 0, 0, nullptr, sehandle);
      
        listen(property_set_fd, 8);
        // 注册到 epoll中。
        register_epoll_handler(property_set_fd, handle_property_set_fd);
    }
    

    属性系统的设置操作,仅有init进程完成,所以其他进程就需要通过socket就来告知init进程设置相关的属性,这也是init进程实现on property:...Action的基础。

    handle_property_set_fd

    static void handle_property_set_fd() {
        static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
    	
        int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    
        struct ucred cr;
        socklen_t cr_size = sizeof(cr);
        getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size);
    
        SocketConnection socket(s, cr);
        uint32_t timeout_ms = kDefaultSocketTimeout;
    
        uint32_t cmd = 0;
        socket.RecvUint32(&cmd, &timeout_ms);
    
        switch (cmd) { 
         //只靠考虑 version2
        case PROP_MSG_SETPROP2: {
            std::string name;
            std::string value;
            if (!socket.RecvString(&name, &timeout_ms) || //获取属性 name
                !socket.RecvString(&value, &timeout_ms)) { //获取属性 value
              socket.SendUint32(PROP_ERROR_READ_DATA);
              return;
            }
    		// 关键函数
            handle_property_set(socket, name, value, false);
            break;
          }
        }
    }
    

    handle_property_set,可以猜测内部也是调用的property_set,不过还有执行很多操作,比如执行action,或者处理ctl.开头的属性。

    static void handle_property_set(SocketConnection& socket,
                                    const std::string& name,
                                    const std::string& value,
                                    bool legacy_protocol) {
      // 校验属性名称是否符合要求
      if (!is_legal_property_name(name)) {
        socket.SendUint32(PROP_ERROR_INVALID_NAME);
        return;
      }
    
      struct ucred cr = socket.cred();
      char* source_ctx = nullptr;
      getpeercon(socket.socket(), &source_ctx);
    	
      if (android::base::StartsWith(name, "ctl.")) {
        // 校验远程进程是否有权限
        if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
          // 执行控制命令
          handle_control_message(name.c_str() + 4, value.c_str());
        }
      } else {
          // 校验远程进程是否有权限
        if (check_mac_perms(name, source_ctx, &cr)) {
          // 设置属性
          uint32_t result = property_set(name, value);
        }
      }
    
      freecon(source_ctx);
    }
    
    uint32_t property_set(const std::string& name, const std::string& value) {
        if (name == "selinux.restorecon_recursive") {
            return PropertySetAsync(name, value, RestoreconRecursiveAsync);
        }
    
        return PropertySetImpl(name, value);
    }
    

    *do_load_system_props

    该函数在on post-fstrigge后执行,这里和property_load_boot_defaults的实现基本一致,只是加载的文件不同而已。

    void load_system_props() {
        load_properties_from_file("/system/build.prop", NULL);
        load_properties_from_file("/odm/build.prop", NULL);
        load_properties_from_file("/vendor/build.prop", NULL);
        load_properties_from_file("/factory/factory.prop", "ro.*");
        load_recovery_id_prop();
    }
    

    *do_load_persist_props

    /* When booting an encrypted system, /data is not mounted when the
     * property service is started, so any properties stored there are
     * not loaded.  Vold triggers init to load these properties once it
     * has mounted /data.
     */
    void load_persist_props(void) {
        load_override_properties();
        /* Read persistent properties after all default values have been loaded. */
        load_persistent_properties();
        property_set("ro.persistent_properties.ready", "true");
    }
    
    static void load_override_properties() {
        if (ALLOW_LOCAL_PROP_OVERRIDE) {
            load_properties_from_file("/data/local.prop", NULL);
        }
    }
    
    
    #define PERSISTENT_PROPERTY_DIR  "/data/property"
    static void load_persistent_properties() {
        persistent_properties_loaded = 1;
    	// 遍历  /data/property目录
        std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(PERSISTENT_PROPERTY_DIR), closedir);
        struct dirent* entry;
        while ((entry = readdir(dir.get())) != NULL) {
            if (strncmp("persist.", entry->d_name, strlen("persist."))) {
                continue;
            }
            if (entry->d_type != DT_REG) {
                continue;
            }
    
            // Open the file and read the property value.
            int fd = openat(dirfd(dir.get()), entry->d_name, O_RDONLY | O_NOFOLLOW);
            
            struct stat sb;
            if (fstat(fd, &sb) == -1) {
                continue;
            }
    
            // File must not be accessible to others, be owned by root/root, and
            // not be a hard link to any other file.
            // 只允许 root 用户读写 属性文件
            if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) || sb.st_uid != 0 || sb.st_gid != 0 || sb.st_nlink != 1) {
                continue;
            }
    
            char value[PROP_VALUE_MAX];
            int length = read(fd, value, sizeof(value) - 1);
            if (length >= 0) {
                value[length] = 0;
                // 又回到了 这里。。。。
                property_set(entry->d_name, value);
            }
            close(fd);
        }
    }
    
    

    /data/property 目录下,每一个文件对应一个 persist 属性。

    property_set实现

    libcutils中的property_set开始。

    // system/core/libcutils/include/cutils/properties.h
    /* 
     * property_set: returns 0 on success, < 0 on failure
     */
    int property_set(const char *key, const char *value);
    
    // system/core/libcutils/include/cutils/properties.cpp
    int property_set(const char *key, const char *value) {
        return __system_property_set(key, value);
    }
    

    既然属性的设置操作都是在init进程中完成的,那就说明__system_property_set的实现应该是通过unix domain socket按照属性协议通知init@propperty_service完成属性设置操作。

    //bionic/libc/bionic/system_properties.cpp
    // 只考虑 Protocol version 2
    int __system_property_set(const char* key, const char* value) {
        // Use proper protocol
        // 使用 /dev/socket/property_service 创建 连接到 property_service 的 socket
        PropertyServiceConnection connection;
        // 用于处理数据发送逻辑
        SocketWriter writer(&connection);
        // 将 key 和 value 通过socket 发送给 init进程。
        if (!writer.WriteUint32(PROP_MSG_SETPROP2).WriteString(key).WriteString(value).Send()) {
          return -1;
        }
    
        int result = -1;
        if (!connection.RecvInt32(&result)) {
          return -1;
        }
        if (result != PROP_SUCCESS) {
          return -1;
        }
        return 0;
    }
    

    start_property_service中已经介绍过,在接收到socket数据后,property_service经过一系列的属性格式和调用进程的权限校验后,最终调用property-service.cpp@PropertySetImpl来执行属性设置操作。

    
    static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
        size_t valuelen = value.size();
        // [1] 查找属性是否已经存在
        prop_info* pi = (prop_info*) __system_property_find(name.c_str());
        if (pi != nullptr) {
            // ro属性不允许修改
            if (android::base::StartsWith(name, "ro.")) {
                return PROP_ERROR_READ_ONLY_PROPERTY;
            }
            // 更新已经存在的属性
            __system_property_update(pi, value.c_str(), valuelen);
        } else {
            // 添加新属性
            int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
            if (rc < 0) {
                return PROP_ERROR_SET_FAILED;
            }
        }
    
        // Don't write properties to disk until after we have read all default
        // properties to prevent them from being overwritten by default values.
        if (persistent_properties_loaded && android::base::StartsWith(name, "persist.")) {
            // persist 类型的属性 持久化
            write_persistent_property(name.c_str(), value.c_str());
        }
    
        // [2] 属性更改通知,执行 on property:.... 
        property_changed(name, value);
        return PROP_SUCCESS;
    }
    

    有了属性名称,我们就能根据前缀获取到与之关联的prop_area,然后再该区域内部查找该属性关联 prop_info

    1. __system_property_update

      当 属性已经存在时,用于更新 非只读属性的值。回顾一下prop_info,其保存属性值得value字段长度固定为PROP_VALUE_MAX,更新时只要简单的覆盖就行了。

    2. __system_property_add

      当属性不存在时,添加属性,先在prop_area中查找属性,找不到就创建prop_bt最后创建prop_info,完成属性树的构建。

    3. property_changed

      // system/core/init/init.cpp
      // on property
      void property_changed(const std::string& name, const std::string& value) {
          // If the property is sys.powerctl, we bypass the event queue and immediately handle it.
          // This is to ensure that init will always and immediately shutdown/reboot, regardless of
          // if there are other pending events to process or if init is waiting on an exec service or
          // waiting on a property.
          // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
          // commands to be executed.
          if (name == "sys.powerctl") {
              // Despite the above comment, we can't call HandlePowerctlMessage() in this function,
              // because it modifies the contents of the action queue, which can cause the action queue
              // to get into a bad state if this function is called from a command being executed by the
              // action queue.  Instead we set this flag and ensure that shutdown happens before the next
              // command is run in the main init loop.
              // TODO: once property service is removed from init, this will never happen from a builtin,
              // but rather from a callback from the property service socket, in which case this hack can
              // go away.
              shutdown_command = value;
              do_shutdown = true;
          }
      
          if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
      
          if (waiting_for_prop) {
              if (wait_prop_name == name && wait_prop_value == value) {
                  ResetWaitForProp();
              }
          }
      }
      

    property_get 实现

    int property_get(const char *key, char *value, const char *default_value) {
        int len = __system_property_get(key, value);
        if (len > 0) {
            return len;
        }
        // 获取失败,设置默认值
        if (default_value) {
            len = strnlen(default_value, PROPERTY_VALUE_MAX - 1);
            memcpy(value, default_value, len);
            value[len] = '';
        }
        return len;
    }
    
    //bionic/libc/bionic/system_properties.cpp
    int __system_property_get(const char* name, char* value) {
      const prop_info* pi = __system_property_find(name);
    
      if (pi != 0) {
        // 找到属性对应的 prop_info,
        return __system_property_read(pi, nullptr, value);
      } else {
        value[0] = 0;
        return 0;
      }
    }
    
    int __system_property_read(const prop_info* pi, char* name, char* value) {
      while (true) {
        uint32_t serial = __system_property_serial(pi);  // acquire semantics
        size_t len = SERIAL_VALUE_LEN(serial);
        memcpy(value, pi->value, len + 1);
        // TODO: Fix the synchronization scheme here.
        // There is no fully supported way to implement this kind
        // of synchronization in C++11, since the memcpy races with
        // updates to pi, and the data being accessed is not atomic.
        // The following fence is unintuitive, but would be the
        // correct one if memcpy used memory_order_relaxed atomic accesses.
        // In practice it seems unlikely that the generated code would
        // would be any different, so this should be OK.
        atomic_thread_fence(memory_order_acquire);
        if (serial == load_const_atomic(&(pi->serial), memory_order_relaxed)) {
          if (name != nullptr) {
            // 简单的拷贝
            size_t namelen = strlcpy(name, pi->name, PROP_NAME_MAX);
          }
          return len;
        }
      }
    }
    

    问题来了,属性系统读端是在android系统中各个进程中进行的,写端都是在init进程,如何保证读写的同步?

    属性系统的同步操作

    futex

    ctl.*如何工作

    前面start_propperty_service中介绍了属性是如何被init处理的,在handle_property_set中,如果属性以ctl.开头,就会调用handle_control_message来处理控制消息:

    void handle_control_message(const std::string& msg, const std::string& name) {
        Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
        if (msg == "start") {
            svc->Start();
        } else if (msg == "stop") {
            svc->Stop();
        } else if (msg == "restart") {
            svc->Restart();
        }
    }
    
  • 相关阅读:
    Java学习图形界面+网络编程案例---------网络简易通讯
    Java图形界面学习---------简易登录界面
    Python-Collections模块之Counter
    Python-面试题-字符串(str)
    Python-面试题-数学运算(math)
    Python-面试题-列表(list)
    python-数据类型-字符串(Str)
    Pytest框架实现一些前后置(固件、夹具)的处理
    Pytest框架运行方式(主函数、命令行、配置、执行顺序)
    Jenkins配置maven+Allure
  • 原文地址:https://www.cnblogs.com/liutimo/p/14611102.html
Copyright © 2020-2023  润新知