阅读 1704

Android启动-inIt进程(second stage)

前言

这一篇文章需要介绍在init进程中第二阶段做的工作,其实两个阶段都有关于selinux的相关内容,这一部分相对比较复杂,我们会单独出一篇文章去介绍selinux的相关内容,所以这里就将其略过了。在第二阶段中,init进程所做的工作主要下面几个方面:

  1. 初始化属性服务
  2. 初始化子进程终止信号处理函数
  3. 对rc文件的解析,并且启动Zygote进程

初始化属性服务

所谓的属性服务就是系统或者一些系统应用会存储一些属性配置,一般都是键值对,属性服务就是用来给这些键值对提供存储和读取的。我们通过:

adb shell getprop 
复制代码

命令就可以查看当前设备的属性值。

int main(int argc, char** argv) {

   ...
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
      ...
    }
		...
    //初始化属性服务
    property_init();
		...
    return 0;
}
复制代码

当某个进程A,通过property_set()修改属性值后,init进程会检查访问权限,当权限满足要求后,则更改相应的属性值,属性值一旦改变则会触发相应的触发器(即rc文件中的on开头的语句),在Android Shared Memmory(共享内存区域)中有一个_system_property_area_区域,里面记录着所有的属性值。对于进程A通过property_get()方法,获取的也是该共享内存区域的属性值。

property_init

void property_init() {
    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";
    }
}
复制代码

CreateSerializedPropertyInfo

void CreateSerializedPropertyInfo() {
    auto property_infos = std::vector<PropertyInfoEntry>();
    /**
    * access()会检查是否可以读/写某一已存在的文件
    * 返回值:若所有欲查核的权限都通过了检查则返回0 值,表示成功,只要有一权限被禁止则返回-1。
    */
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {//从文件中加载property信息
            return;
        }
        // Don't check for failure here, so we always have a sane list of properties.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
                                     &property_infos);
        }
    } else {
        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/nonplat_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);
}
复制代码

CreateSerializedPropertyInfo的作用是读取一些文件,将读取的属性值存储在一个动态数组中。

LoadPropertyInfoFromFile

bool LoadPropertyInfoFromFile(const std::string& filename,
                              std::vector<PropertyInfoEntry>* property_infos) {
    auto file_contents = std::string();
    if (!ReadFileToString(filename, &file_contents)) {
        PLOG(ERROR) << "Could not read properties from '" << filename << "'";
        return false;
    }

    auto errors = std::vector<std::string>{};
    ParsePropertyInfoFile(file_contents, property_infos, &errors);//解析Propertiy文件
    // Individual parsing errors are reported but do not cause a failed boot, which is what
    // returning false would do here.
    for (const auto& error : errors) {
        LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
    }

    return true;
}
复制代码

ParsePropertyInfoFile

void ParsePropertyInfoFile(const std::string& file_contents,
                           std::vector<PropertyInfoEntry>* property_infos,
                           std::vector<std::string>* errors) {
  // Do not clear property_infos to allow this function to be called on multiple files, with
  // their results concatenated.
  errors->clear();

  for (const auto& line : Split(file_contents, "\n")) {//逐行的解析property文件
    auto trimmed_line = Trim(line);
    if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
      continue;
    }

    auto property_info_entry = PropertyInfoEntry{};
    auto parse_error = std::string{};
    if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
      errors->emplace_back(parse_error);
      continue;
    }

    property_infos->emplace_back(property_info_entry);
  }
}
复制代码

_system_property_area_init

int __system_property_area_init() {
  bool fsetxattr_failed = false;
  return system_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && !fsetxattr_failed ? 0 : -1;
}
复制代码

初始化属性内存区域

SystemProperties::AreaInit

bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
  if (strlen(filename) > PROP_FILENAME_MAX) {
    return false;
  }
  strcpy(property_filename_, filename);

  contexts_ = new (contexts_data_) ContextsSerialized();
  if (!contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
    return false;
  }
  initialized_ = true;
  return true;
}
复制代码

ContextsSerialized::Initialize

bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
  filename_ = filename;
  if (!InitializeProperties()) {
    return false;
  }

  if (writable) {
    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
    bool open_failed = false;
    if (fsetxattr_failed) {
      *fsetxattr_failed = false;
    }

    for (size_t i = 0; i < num_context_nodes_; ++i) {
      if (!context_nodes_[i].Open(true, fsetxattr_failed)) {
        open_failed = true;
      }
    }
    if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
      FreeAndUnmap();
      return false;
    }
  } else {
    if (!MapSerialPropertyArea(false, nullptr)) {
      FreeAndUnmap();
      return false;
    }
  }
  return true;
}
复制代码

启动属性服务

int main(int argc, char** argv) {

   ...
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
      ...
    }
		...
  //启动属性服务 
  start_property_service();
		...
    return 0;
}
复制代码

其实这个属性服务是一个Socket服务,我们需要通过它来实现我们属性服务的设置,那为什么要启动一个Socket服务呢,因为不是所有的进程都能任意的修改这些系统的属性。Android将这一任务交给了init进程。而其他进程如果想修改属性服务需要通过Socket来通知init进程做处理。下面我们通过代码看看这个流程。

start_property_service

void start_property_service() {
    selinux_callback cb;
    cb.func_audit = SelinuxAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    property_set("ro.property_service.version", "2");
    //创建socket,返回socket fd
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, nullptr);
    if (property_set_fd == -1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }

    listen(property_set_fd, 8);//socket的连接数为8
    /**
    *注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd
    */
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}
复制代码

Socket 描述符 property_set_fd被创建后,放入了epoll中,用epoll来监听property_set_fd,当property_set_fd有数据到来时,init进程将调用handle_property_set_fd函数来进行处理。

epoll是Linux内核的可扩展I/O事件通知机制。也就是他能高效的监听文件描述符。提高CPU的利用率。

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);
    if (s == -1) {
        return;
    }

    ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) {
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {
    //设置prop属性
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
            !socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }

        break;
      }

    case PROP_MSG_SETPROP2: {
        std::string name;
        std::string value;
        if (!socket.RecvString(&name, &timeout_ms) ||
            !socket.RecvString(&value, &timeout_ms)) {
          PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        const auto& cr = socket.cred();
        std::string error;
        uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
                       << error;
        }
        socket.SendUint32(result);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}
复制代码

HandlePropertySet

uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr, std::string* error) {
    if (!IsLegalPropertyName(name)) {
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (StartsWith(name, "ctl.")) {//设置控制属性
        if (!CheckControlPropertyPerms(name, value, source_context, cr)) {
            *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
                                  value.c_str());
            return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
        }

        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }

    const char* target_context = nullptr;
    const char* type = nullptr;
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);

    if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {
        *error = "SELinux permission check failed";
        return PROP_ERROR_PERMISSION_DENIED;
    }

    if (type == nullptr || !CheckType(type, value)) {
        *error = StringPrintf("Property type check failed, value doesn't match expected type '%s'",
                              (type ?: "(null)"));
        return PROP_ERROR_INVALID_VALUE;
    }

    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
    }

    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }

    return PropertySet(name, value, error);//设置属性
}
复制代码

PropertySet

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    size_t valuelen = value.size();

    if (!IsLegalPropertyName(name)) {//判断属性名是否合法
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }

    prop_info* pi = (prop_info*) __system_property_find(name.c_str());//判断当前属性是否已经存在
    if (pi != nullptr) {
        // ro.* properties are actually "write-once".
        if (StartsWith(name, "ro.")) { //如果已经存在并且是只读的,则不允许再设置。
            *error = "Read-only property was already set";
            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) {
            *error = "__system_property_add failed";
            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 && StartsWith(name, "persist.")) {//把persist.开头的属性,持久化到disk
        WritePersistentProperty(name, value);
    }
    property_changed(name, value);
    return PROP_SUCCESS;
}
复制代码

各个前缀的解释:

  • persist 前缀. 保存在 /data/property 目录, 重启后依然生效.在data解密后或者late-init阶段加载.
  • ro 前缀. 只读属性, 类似C/CPP中的常量定义, 它能且只能被设置1次, 这类属性一般尽可能早设置. 依据前文的加载顺序, 在有两个相同的ro前缀property时, 前面加载的生效, 而非ro前缀的property, 后面加载的生效.
  • ctl 前缀. 这是方便控制init中的服务而设立的 -- 通过把相关服务的名字设置为 ctl.start 和 ctl.stop的值, 就能方便地启动和停止指定服务. (命令行中的start和stop实际上就是通过该property来设置.) 这种能力是受selinux控制的具体的权限定义在 property.te, 有兴趣可以看下, 所有的property相关权限定义都在这个文件中.

系统属性

void property_init() {
    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";
    }
}

复制代码
  1. Android 的系统属性提供了一个可全局访问的配置设置仓库
  2. 各个文件的加载顺序是非常重要的, ro前缀的property早加载的生效, 而非ro前缀的property晚加载的生效.
  3. 上面我们看到, persist属性是写入 /data/property, 这也是为什么persist属性在重启后依然生效.

各个前缀的解释:

  • persist 前缀. 保存在 /data/property/persistent_properties中, 重启后依然生效.在data解密后或者late-init阶段加载.
  • ro 前缀. 只读属性, 类似C/CPP中的常量定义, 它能且只能被设置1次, 这类属性一般尽可能早设置. 依据前文的加载顺序, 在有两个相同的ro前缀property时, 前面加载的生效, 而非ro前缀的property, 后面加载的生效.
  • ctl 前缀. 这是方便控制init中的服务而设立的 -- 通过把相关服务的名字设置为 ctl.start 和 ctl.stop的值, 就能方便地启动和停止指定服务. (命令行中的start和stop实际上就是通过该property来设置.) 这种能力是受selinux控制的具体的权限定义在 property.te, 有兴趣可以看下, 所有的property相关权限定义都在这个文件中.

解析init.rc文件

int main(int argc, char** argv) {

   ...
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
      ...
    }
		...
   
    LoadBootScripts(am, sm);//解析init.rc文件
		...
    return 0;
}
复制代码
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");//解析init.rc文件
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}
复制代码

Init.rc是一个Android Init Language 编写的配置文件。主要包含5种 Action、Service、Command、Options、Imports

Action

通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:

  • on early-init; 在初始化早期阶段触发;
  • on init; 在初始化阶段触发;
  • on late-init; 在初始化晚期阶段触发;
  • on boot/charger: 当系统启动/充电时触发
  • on property:=: 当属性值满足条件时触发;

Service

服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。

例如 service ueventd /sbin/ueventd 进程名为ueventd 可执行文件的路径为/sbin/ueventd

Command

命令

  • chmod 修改文件权限
  • chown 修改文件拥有者
  • class_start 启动属于同一个class的所有服务
  • start 启动一个具体的服务

例如 chmod 0773 /data/misc/trace 修改权限

Options

Options是Service的可选项,与service配合使用

critical: 规定在4分钟内该service四次重启,则系统会重启并进入恢复模式

disabled: 不随class自动启动,只有根据service名才启动

Imports

Import 其他的rc文件

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
复制代码

启动Zygote服务

在init.rc文件中import

/init.${ro.zygote}.rc
复制代码

如果你是32位的系统,对应的是init.zygote32.rc文件

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
复制代码

这个rc文件就是启动zygote进程的配置文件,具体zygote的启动将在下一篇文章介绍。

总结

init作为Android系统在用户空间的第一个进程所做的工作包括: 作为大多数守护进程的样板, init代码的执行流程完全遵循建立服务的经典模式: 初始化, 然后陷入一个循环中, 而且永远不退出.

关于我

  • 公众号: CodingDev

qrcode_for_gh_0e16b0c63d2d_258.jpg

文章分类
Android
文章标签