(連載)Android 8.0 : 系統啟動流程之init程式(二)

foxleezh發表於2018-03-05

這是一個連載的博文系列,我將持續為大家提供儘可能透徹的Android原始碼分析 github連載地址

前言

上一篇中講了init程式的第一階段,我們接著講第二階段,主要有以下內容

  • 建立程式會話金鑰並初始化屬性系統
  • 進行SELinux第二階段並恢復一些檔案安全上下文
  • 新建epoll並初始化子程式終止訊號處理函式
  • 設定其他系統屬性並開啟系統屬性服務

本文涉及到的檔案

platform/system/core/init/init.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
platform/system/core/init/property_service.cpp
複製程式碼

一、建立程式會話金鑰並初始化屬性系統

第二階段一開始會有一個is_first_stage的判斷,由於之前第一階段最後有設定INIT_SECOND_STAGE, 因此直接跳過一大段程式碼。從keyctl開始才是重點內容,我們一一展開來看

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

    //同樣進行ueventd/watchdogd跳轉及環境變數設定

    ...

    //之前準備工作時將INIT_SECOND_STAGE設定為true,已經不為nullptr,所以is_first_stage為false
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    //is_first_stage為false,直接跳過
    if (is_first_stage) {
        ...
    }

    // At this point we're in the second stage of init.
    InitKernelLogging(argv); //上一節有講,初始化日誌輸出
    LOG(INFO) << "init second stage started!";

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1); //初始化程式會話金鑰

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//建立 /dev/.booting 檔案,就是個標記,表示booting進行中

    property_init();//初始化屬性系統,並從指定檔案讀取屬性

    //接下來的一系列操作都是從各個檔案讀取一些屬性,然後通過property_set設定系統屬性

    // If arguments are passed both on the command line and in DT,
    // properties set in DT always have priority over the command-line ones.
    /*
     * 1.這句英文的大概意思是,如果引數同時從命令列和DT傳過來,DT的優先順序總是大於命令列的
     * 2.DT即device-tree,中文意思是裝置樹,這裡面記錄自己的硬體配置和系統執行引數,參考http://www.wowotech.net/linux_kenrel/why-dt.html
     */

    process_kernel_dt();//處理DT屬性
    process_kernel_cmdline();//處理命令列屬性

    // Propagate the kernel variables to internal variables
    // used by init as well as the current required properties.
    export_kernel_boot_props();//處理其他的一些屬性

    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));

    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);

    // Clean up our environment.
    unsetenv("INIT_SECOND_STAGE"); //清空這些環境變數,因為之前都已經存入到系統屬性中去了
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    ...

複製程式碼

1.1 keyctl

定義在platform/system/core/init/keyutils.h

keyctl將主要的工作交給__NR_keyctl這個系統呼叫,keyctl是Linux系統操縱核心的通訊金鑰管理工具

我們分析下 keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1)

  • KEYCTL_GET_KEYRING_ID 表示通過第二個引數的型別獲取當前程式的金鑰資訊
  • KEY_SPEC_SESSION_KEYRING 表示獲取當前程式的SESSION_KEYRING(會話金鑰環)
  • 1 表示如果獲取不到就新建一個

參考linux手冊

這裡並沒有拿返回值,估計就是為了新建會話金鑰環了,從註釋Set up a session keyring也可看出

static inline long keyctl(int cmd, ...) {
    va_list va;
    unsigned long arg2, arg3, arg4, arg5;

    //va_start,va_arg,va_end是配合使用的,用於將可變引數從堆疊中讀取出來
    va_start(va, cmd); //va_start是獲取第一個引數地址
    arg2 = va_arg(va, unsigned long); //va_arg 遍歷引數
    arg3 = va_arg(va, unsigned long);
    arg4 = va_arg(va, unsigned long);
    arg5 = va_arg(va, unsigned long);
    va_end(va); //va_end 恢復堆疊
    return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); //系統呼叫
}
複製程式碼

1.2 property_init

定義在 platform/system/core/init/property_service.cpp

直接交給 __system_property_area_init 處理

void property_init() {
    if (__system_property_area_init()) {
        LOG(ERROR) << "Failed to initialize property area";
        exit(1);
    }
}
複製程式碼

__system_property_area_init 定義在/bionic/libc/bionic/system_properties.cpp

看名字大概知道是用來初始化屬性系統區域的,應該是分門別類更準確些,首先清除快取,這裡主要是清除幾個連結串列以及在記憶體中的對映,新建property_filename目錄,這個目錄的值為 /dev/_properties_ 然後就是呼叫initialize_properties載入一些系統屬性的類別資訊,最後將載入的連結串列寫入檔案並對映到記憶體

int __system_property_area_init() {
  free_and_unmap_contexts();//清除一些快取
  mkdir(property_filename, S_IRWXU | S_IXGRP | S_IXOTH);//新建目錄property_filename,許可權是rwx-x-x
  if (!initialize_properties()) { //讀取一些檔案,把鍵值資訊存入到連結串列中
    return -1;
  }
  bool open_failed = false;
  bool fsetxattr_failed = false;
  list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
    if (!l->open(true, &fsetxattr_failed)) {
    //將contexts連結串列中的資料寫入到property_filename目錄下檔案中,每種context對應一個檔案,並通過mmap對映進記憶體中
      open_failed = true;
    }
  });
  if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {//增加 properties_serial的對映,跟contexts中的一樣
    free_and_unmap_contexts();//對映失敗清除快取
    return -1;
  }
  initialized = true;
  return fsetxattr_failed ? -2 : 0;
}
複製程式碼

1.3 initialize_properties

定義在/bionic/libc/bionic/system_properties.cpp

交給 initialize_properties_from_file 處理,指定了一些檔案路徑

static bool initialize_properties() {
  // If we do find /property_contexts, then this is being
  // run as part of the OTA updater on older release that had
  // /property_contexts - b/34370523
  if (initialize_properties_from_file("/property_contexts")) {
    return true;
  }

  // Use property_contexts from /system & /vendor, fall back to those from /
  if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
    if (!initialize_properties_from_file("/system/etc/selinux/plat_property_contexts")) {
      return false;
    }
    // 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.
    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;
}
複製程式碼

1.4 initialize_properties_from_file

定義在/bionic/libc/bionic/system_properties.cpp

這個函式主要工作是解析屬性類別檔案,對屬性做一下分類,具體就是一行行解析,過濾 # 開頭的、只讀到key的、從ctl.開頭的,然後將解析出來的鍵值對放到兩個連結串列中

prefixes連結串列存放key(其實是一些key的字首),contexts連結串列存放value(其實是對應key應當屬於那些類別的資訊),這樣的好處是將龐雜的屬性根據字首分類,儲存到不同的context中, 查詢和修改是非常高效的,類似map的做法

static bool initialize_properties_from_file(const char* filename) {
  FILE* file = fopen(filename, "re");
  if (!file) {
    return false;
  }

  char* buffer = nullptr;
  size_t line_len;
  char* prop_prefix = nullptr;
  char* context = nullptr;

  while (getline(&buffer, &line_len, file) > 0) { //一行一行讀取,然後將結果放到buffer中
    int items = read_spec_entries(buffer, 2, &prop_prefix, &context);
    //將buffer的資料,按空格作為區分,key賦值給prop_prefix,value賦值給context

    if (items <= 0) { //沒有讀取到,比如 # 這種是註釋
      continue;
    }
    if (items == 1) { //只讀取到key,釋放key的記憶體
      free(prop_prefix);
      continue;
    }
    /*
     * init uses ctl.* properties as an IPC mechanism and does not write them
     * to a property file, therefore we do not need to create property files
     * to store them.
     */
    if (!strncmp(prop_prefix, "ctl.", 4)) { //以ctl.開頭忽略掉,因為這個不屬於屬性,主要用於IPC機制
      free(prop_prefix);
      free(context);
      continue;
    }

    /*
     * C++中[ arg1,arg2,... ](T param, T param1,... ){ commond} 這個是lambda表示式,也可以看作一個函式指標
     * []中是引用外部引數
     *()中是引數定義,這個跟普通方法的()一樣
     * {}中是方法體
     */
    auto old_context =
        list_find(contexts, [context](context_node* l) { return !strcmp(l->context(), context); });

    // list_find主要是迴圈contexts這個連結串列,如果發現context的值在連結串列裡已經有,就將對應的連結串列結構context_node返回
    if (old_context) {
      list_add_after_len(&prefixes, prop_prefix, old_context);
      //list_add_after_len 主要作用是將prop_prefix和old_context按順序放到prefixes連結串列裡
    } else {
      list_add(&contexts, context, nullptr);//將context的值放到contexts連結串列裡
      list_add_after_len(&prefixes, prop_prefix, contexts);
    }
    free(prop_prefix); //釋放資源
    free(context);
  }

  free(buffer);
  fclose(file);

  return true;
}
複製程式碼

1.5 連結串列結構

定義在/bionic/libc/bionic/system_properties.cpp

之前我們看到有兩個重要的連結串列prefixs和contexts,frefixs存key(其實是一些key的字首),contexts存value(其實是對應key應當屬於那些類別的資訊),接下來我們看下這兩個連結串列的結構

context_node中有三個比較重要的屬性context_、_pa和next,context_用來存類別資訊,_pa是存具體key-value節點的,next是連結串列下一個節點

prefix_node中有三個重要屬性prefix,context和next,prefix用來存key,context用來存關聯的context_node,next是連結串列下一個節點

prop_area 這個在context_node裡引用,屬性data是具體key-value的資料庫,裡面是用 hybrid trie/binary tree(字典樹)這種結構儲存的,也就是一對多,我給張圖就明白了

(連載)Android 8.0 : 系統啟動流程之init程式(二)

prop_info 就是具體的key-value了,這個是從prop_area解析出來的

class context_node {
 public:
  /*
   * C++中建構函式後面接 :(冒號) 表示對屬性賦初始值
   */
  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;

 private:
  bool check_access();
  void unmap();

  Lock lock_;
  char* context_;
  prop_area* pa_;
  bool no_access_;
};

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) {
  }
  ~prefix_node() {
    free(prefix);
  }
  char* prefix;
  const size_t prefix_len;
  context_node* context;
  struct prefix_node* next;
};

class prop_area {
 public:
  prop_area(const uint32_t magic, const uint32_t version) : magic_(magic), version_(version) {
    atomic_init(&serial_, 0);
    memset(reserved_, 0, sizeof(reserved_));
    // Allocate enough space for the root node.
    bytes_used_ = sizeof(prop_bt);
  }

  ....

 private:

  ...

  uint32_t bytes_used_;
  atomic_uint_least32_t serial_;
  uint32_t magic_;
  uint32_t version_;
  uint32_t reserved_[28];
  char data_[0];

  DISALLOW_COPY_AND_ASSIGN(prop_area);
};

struct prop_info {
  atomic_uint_least32_t serial;
  // we need to keep this buffer around because the property
  // value can be modified whereas name is constant.
  char value[PROP_VALUE_MAX];
  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] = '\0';
    atomic_init(&this->serial, valuelen << 24);
    memcpy(this->value, value, valuelen);
    this->value[valuelen] = '\0';
  }

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(prop_info);
};
複製程式碼

之前有個list_add函式,這個函式是一個模板函式,與Java中的泛型類似,List 和 Args相當於T和T1,這個函式主要作用就是呼叫T的建構函式, 把list,可變引數args作為引數傳進去


template <typename List, typename... Args>
static inline void list_add(List** list, Args... args) {
  *list = new List(*list, args...);
}

複製程式碼

1.6 process_kernel_dt

定義在platform/system/core/init/init.cpp

讀取DT(裝置樹)的屬性資訊,然後通過 property_set 設定系統屬性

static void process_kernel_dt() {
    if (!is_android_dt_value_expected("compatible", "android,firmware")) {
    //判斷 /proc/device-tree/firmware/android/compatible 檔案中的值是否為 android,firmware
        return;
    }

    std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(kAndroidDtDir.c_str()), closedir);
    // kAndroidDtDir的值為/proc/device-tree/firmware/android

    if (!dir) return;

    std::string dt_file;
    struct dirent *dp;
    while ((dp = readdir(dir.get())) != NULL) { //遍歷dir中的檔案
        if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
            //跳過 compatible和name檔案
            continue;
        }

        std::string file_name = kAndroidDtDir + dp->d_name;

        android::base::ReadFileToString(file_name, &dt_file); //讀取檔案內容
        std::replace(dt_file.begin(), dt_file.end(), ',', '.'); //替換 , 為 .

        std::string property_name = StringPrintf("ro.boot.%s", dp->d_name);
        property_set(property_name.c_str(), dt_file.c_str()); // 將 ro.boot.檔名 作為key,檔案內容為value,設定進屬性
    }
}
複製程式碼

1.7 property_set

定義在/bionic/libc/bionic/system_properties.cpp

property_set用的地方特別多,作用是設定系統屬性,具體就是通過遍歷之前的prefixs連結串列找到對應的context_node,然後通過context_node的_pa屬性找到對應key-value節點prop_info,能找到就更新value,找不到就設定新值, 另外就是呼叫property_changed方法觸發trigger,trigger後續講.rc解析時再詳細講,trigger可以觸發一系列活動

uint32_t property_set(const std::string& name, const std::string& value) {
    size_t valuelen = value.size();

    if (!is_legal_property_name(name)) { //檢查key合法性,大概就是 xx.xx.xx 這種 ,xx只能是字母、數字、_、-、@
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX) {//不能超過最大長度 92
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                   << "value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    if (name == "selinux.restorecon_recursive" && valuelen > 0) { // 跳過selinux,不允許修改
        if (restorecon(value.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) {
            LOG(ERROR) << "Failed to restorecon_recursive " << value;
        }
    }

    prop_info* pi = (prop_info*) __system_property_find(name.c_str()); //找到key對應節點
    if (pi != nullptr) { //如果對應節點存在就更新
        // ro.* properties are actually "write-once".
        if (android::base::StartsWith(name, "ro.")) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "property 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) {
            LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                       << "__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 && android::base::StartsWith(name, "persist.")) {
    //如果以persist開頭的,將值寫入檔案
        write_persistent_property(name.c_str(), value.c_str());
    }
    property_changed(name, value); //觸發trigger
    return PROP_SUCCESS;
}
複製程式碼

1.8 其他屬性設定

後續的一些函式或程式碼都是直接或間接呼叫 property_set 設定系統屬性

static void process_kernel_cmdline() {
    // The first pass does the common stuff, and finds if we are in qemu.
    // The second pass is only necessary for qemu to export all kernel params
    // as properties.
    import_kernel_cmdline(false, import_kernel_nv);
    if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}

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(StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str());
        return;
    }

    if (key == "qemu") {
        strlcpy(qemu, value.c_str(), sizeof(qemu));
    } else if (android::base::StartsWith(key, "androidboot.")) {
        property_set(StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(), value.c_str());
    }
}

複製程式碼
static void export_kernel_boot_props() {
    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", },
    };
    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.c_str() : prop_map[i].default_value);
    }
}
複製程式碼

二、進行SELinux第二階段並恢復一些檔案安全上下文

    // Now set up SELinux for second stage.
    selinux_initialize(false); //第二階段初始化SELinux policy
    selinux_restore_context();//恢復安全上下文
複製程式碼

2.1 selinux_initialize

定義在platform/system/core/init/init.cpp

第二階段只是執行 selinux_init_all_handles

static void selinux_initialize(bool in_kernel_domain) {

    ... //和之前一樣設定回撥函式

    if (in_kernel_domain) {//第二階段跳過
       ...
    } else {
        selinux_init_all_handles();
    }
}
複製程式碼

2.2 selinux_init_all_handles

定義在platform/system/core/init/init.cpp

這裡是建立SELinux的處理函式,selinux_android_file_context_handle和selinux_android_prop_context_handle內部實現差不多,其實就是傳遞不同的檔案路徑給selabel_open

static void selinux_init_all_handles(void)
{
    sehandle = selinux_android_file_context_handle(); //建立context的處理函式
    selinux_android_set_sehandle(sehandle);//將剛剛新建的處理賦值給fc_sehandle
    sehandle_prop = selinux_android_prop_context_handle();//建立prop的處理函式
}
複製程式碼

2.2 selabel_open

定義在platform/external/selinux/libselinux/src/label.c

首先建立一個selabel_handle結構體,然後根據backend的型別將處理函式對映給initfuncs陣列中的值,將引數opts傳遞過去

這個opts只是包含一個簡單的路徑,比如 /system/etc/selinux/plat_file_contexts ,而initfuncs負責去解析它

struct selabel_handle *selabel_open(unsigned int backend,
				    const struct selinux_opt *opts,
				    unsigned nopts)
{
	struct selabel_handle *rec = NULL;

	if (backend >= ARRAY_SIZE(initfuncs)) {
		errno = EINVAL;
		goto out;
	}

	if (!initfuncs[backend]) {
		errno = ENOTSUP;
		goto out;
	}

	rec = (struct selabel_handle *)malloc(sizeof(*rec));
	if (!rec)
		goto out;

	memset(rec, 0, sizeof(*rec));
	rec->backend = backend;
	rec->validating = selabel_is_validate_set(opts, nopts);

	rec->subs = NULL;
	rec->dist_subs = NULL;
	rec->digest = selabel_is_digest_set(opts, nopts, rec->digest);

	if ((*initfuncs[backend])(rec, opts, nopts)) { //
		selabel_close(rec);
		rec = NULL;
	}
out:
	return rec;
}
複製程式碼

2.3 initfuncs

定義在platform/external/selinux/libselinux/src/label.c

這些陣列對應backend的6種可能的值

/* file contexts */
#define SELABEL_CTX_FILE	0
/* media contexts */
#define SELABEL_CTX_MEDIA	1
/* x contexts */
#define SELABEL_CTX_X		2
/* db objects */
#define SELABEL_CTX_DB		3
/* Android property service contexts */
#define SELABEL_CTX_ANDROID_PROP 4
/* Android service contexts */
#define SELABEL_CTX_ANDROID_SERVICE 5
複製程式碼

initfuncs陣列中每一項都對應一個init函式,init函式主要作用是解析傳進來的檔案,這些傳進來的檔案定義了哪些程式可以訪問哪些檔案,執行哪些操作 SELinux的內容比較多,由於篇幅就暫時不深入了 可以參考老羅的SEAndroid安全機制框架分析

static selabel_initfunc initfuncs[] = {
	&selabel_file_init,
	CONFIG_MEDIA_BACKEND(selabel_media_init),
	CONFIG_X_BACKEND(selabel_x_init),
	CONFIG_DB_BACKEND(selabel_db_init),
	CONFIG_ANDROID_BACKEND(selabel_property_init),
	CONFIG_ANDROID_BACKEND(selabel_service_init),
};
複製程式碼

2.3 selinux_restore_context

定義在 platform/system/core/init/init.cpp

主要就是恢復這些檔案的安全上下文,因為這些檔案是在SELinux安全機制初始化前建立,所以需要重新恢復下安全性

static void selinux_restore_context() {
    LOG(INFO) << "Running restorecon...";
    restorecon("/dev");
    restorecon("/dev/kmsg");
    restorecon("/dev/socket");
    restorecon("/dev/random");
    restorecon("/dev/urandom");
    restorecon("/dev/__properties__");

    restorecon("/file_contexts.bin");
    restorecon("/plat_file_contexts");
    restorecon("/nonplat_file_contexts");
    restorecon("/plat_property_contexts");
    restorecon("/nonplat_property_contexts");
    restorecon("/plat_seapp_contexts");
    restorecon("/nonplat_seapp_contexts");
    restorecon("/plat_service_contexts");
    restorecon("/nonplat_service_contexts");
    restorecon("/plat_hwservice_contexts");
    restorecon("/nonplat_hwservice_contexts");
    restorecon("/sepolicy");
    restorecon("/vndservice_contexts");

    restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
    restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
    restorecon("/dev/device-mapper");
}
複製程式碼

三、新建epoll並初始化子程式終止訊號處理函式


    epoll_fd = epoll_create1(EPOLL_CLOEXEC);//建立epoll例項,並返回epoll的檔案描述符

    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }

    signal_handler_init();//主要是建立handler處理子程式終止訊號,建立一個匿名socket並註冊到epoll進行監聽

複製程式碼

3.1 epoll_create1

EPOLL類似於POLL,是Linux中用來做事件觸發的,跟EventBus功能差不多

linux很長的時間都在使用select來做事件觸發,它是通過輪詢來處理的,輪詢的fd數目越多,自然耗時越多,對於大量的描述符處理,EPOLL更有優勢

epoll_create1是epoll_create的升級版,可以動態調整epoll例項中檔案描述符的個數 EPOLL_CLOEXEC這個引數是為檔案描述符新增O_CLOEXEC屬性,參考http://blog.csdn.net/gqtcgq/article/details/48767691

3.2 signal_handler_init

定義在platform/system/core/init/signal_handler.cpp

這個函式主要的作用是註冊SIGCHLD訊號的處理函式

init是一個守護程式,為了防止init的子程式成為殭屍程式(zombie process), 需要init在子程式在結束時獲取子程式的結束碼,通過結束碼將程式表中的子程式移除, 防止成為殭屍程式的子程式佔用程式表的空間(程式表的空間達到上限時,系統就不能再啟動新的程式了,會引起嚴重的系統問題)

在linux當中,父程式是通過捕捉SIGCHLD訊號來得知子程式執行結束的情況,SIGCHLD訊號會在子程式終止的時候發出,瞭解這些背景後,我們來看看init程式如何處理這個訊號

首先,呼叫socketpair,這個方法會返回一對檔案描述符,這樣當一端寫入時,另一端就能被通知到, socketpair兩端既可以寫也可以讀,這裡只是單向的讓s[0]寫,s[1]讀

然後,新建一個sigaction結構體,sa_handler是訊號處理函式,指向SIGCHLD_handler, SIGCHLD_handler做的事情就是往s[0]裡寫個"1",這樣s1就會收到通知,SA_NOCLDSTOP表示只在子程式終止時處理, 因為子程式在暫停時也會發出SIGCHLD訊號

sigaction(SIGCHLD, &act, 0) 這個是建立訊號繫結關係,也就是說當監聽到SIGCHLD訊號時,由act這個sigaction結構體處理

ReapAnyOutstandingChildren 這個後文講

最後,register_epoll_handler的作用就是註冊一個監聽,當signal_read_fd(之前的s[1])收到訊號,觸發handle_signal

終上所述,signal_handler_init函式的作用就是,接收到SIGCHLD訊號時觸發handle_signal


void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { //建立socket並返回檔案描述符
        PLOG(ERROR) << "socketpair failed";
        exit(1);
    }

    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler; //act處理函式
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();//具體處理子程式終止訊號

    register_epoll_handler(signal_read_fd, handle_signal);//註冊signal_read_fd到epoll中
}



void register_epoll_handler(int fd, void (*fn)() ) {
    epoll_event ev;
    ev.events = EPOLLIN; //監聽事件型別,EPOLLIN表示fd中有資料可讀
    ev.data.ptr = reinterpret_cast<void*>(fn); //回撥函式賦值給ptr
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { //註冊事件
         PLOG(ERROR) << "epoll_ctl failed";
    }
}
複製程式碼

3.3 handle_signal

定義在platform/system/core/init/signal_handler.cpp

首先清空signal_read_fd中的資料,然後呼叫ReapAnyOutstandingChildren,之前在signal_handler_init中呼叫過一次, 它其實是呼叫ReapOneProcess

static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
複製程式碼

3.4 ReapOneProcess

定義在platform/system/core/init/service.cpp

這是最終的處理函式了,這個函式先用waitpid找出掛掉程式的pid,然後根據pid找到對應Service,最後呼叫Service的Reap方法清除資源,根據程式對應的型別,決定是否重啟機器或重啟程式

bool ServiceManager::ReapOneProcess() {
    int status;
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    //用waitpid函式獲取狀態發生變化的子程式pid
    //waitpid的標記為WNOHANG,即非阻塞,返回為正值就說明有程式掛掉了

    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        PLOG(ERROR) << "waitpid failed";
        return false;
    }

    Service* svc = FindServiceByPid(pid);//通過pid找到對應的Service

    std::string name;
    std::string wait_string;
    if (svc) {
        name = android::base::StringPrintf("Service '%s' (pid %d)",
                                           svc->name().c_str(), pid);
        if (svc->flags() & SVC_EXEC) {
            wait_string =
                android::base::StringPrintf(" waiting took %f seconds", exec_waiter_->duration_s());
        }
    } else {
        name = android::base::StringPrintf("Untracked pid %d", pid);
    }

    if (WIFEXITED(status)) {
        LOG(INFO) << name << " exited with status " << WEXITSTATUS(status) << wait_string;
    } else if (WIFSIGNALED(status)) {
        LOG(INFO) << name << " killed by signal " << WTERMSIG(status) << wait_string;
    } else if (WIFSTOPPED(status)) {
        LOG(INFO) << name << " stopped by signal " << WSTOPSIG(status) << wait_string;
    } else {
        LOG(INFO) << name << " state changed" << wait_string;
    }

    if (!svc) { //沒有找到,說明已經結束了
        return true;
    }

    svc->Reap();//清除子程式相關的資源

    if (svc->flags() & SVC_EXEC) {
        exec_waiter_.reset();
    }
    if (svc->flags() & SVC_TEMPORARY) {
        RemoveService(*svc);
    }

    return true;
}
複製程式碼

四、設定其他系統屬性並開啟系統屬性服務

    property_load_boot_defaults();//從檔案中載入一些屬性,讀取usb配置
    export_oem_lock_status();//設定ro.boot.flash.locked 屬性
    start_property_service();//開啟一個socket監聽系統屬性的設定
    set_usb_controller();//設定sys.usb.controller 屬性

複製程式碼

4.1 設定其他系統屬性

property_load_boot_defaults,export_oem_lock_status,set_usb_controller這三個函式都是呼叫property_set設定一些系統屬性

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();
}

static void export_oem_lock_status() {
    if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
        return;
    }

    std::string value = GetProperty("ro.boot.verifiedbootstate", "");

    if (!value.empty()) {
        property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
    }
}

static void set_usb_controller() {
    std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
    if (!dir) return;

    dirent* dp;
    while ((dp = readdir(dir.get())) != nullptr) {
        if (dp->d_name[0] == '.') continue;

        property_set("sys.usb.controller", dp->d_name);
        break;
    }
}
複製程式碼

4.2 start_property_service

定義在platform/system/core/init/property_service.cpp

之前我們看到通過property_set可以輕鬆設定系統屬性,那幹嘛這裡還要啟動一個屬性服務呢?這裡其實涉及到一些許可權的問題,不是所有程式都可以隨意修改任何的系統屬性, Android將屬性的設定統一交由init程式管理,其他程式不能直接修改屬性,而只能通知init程式來修改,而在這過程中,init程式可以進行許可權控制,我們來看看這些是如何實現的

首先建立一個socket並返回檔案描述符,然後設定最大併發數為8,其他程式可以通過這個socket通知init程式修改系統屬性, 最後註冊epoll事件,也就是當監聽到property_set_fd改變時呼叫handle_property_set_fd

void start_property_service() {
    property_set("ro.property_service.version", "2");

    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);//建立socket用於通訊
    if (property_set_fd == -1) {
        PLOG(ERROR) << "start_property_service socket creation failed";
        exit(1);
    }

    listen(property_set_fd, 8);//監聽property_set_fd,設定最大併發數為8

    register_epoll_handler(property_set_fd, handle_property_set_fd);//註冊epoll事件
}

複製程式碼

4.3 handle_property_set_fd

定義在platform/system/core/init/property_service.cpp

這個函式主要作用是建立socket連線,然後從socket中讀取操作資訊,根據不同的操作型別,呼叫handle_property_set做具體的操作

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;
    }

    struct ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {//獲取連線到此socket的程式的憑據
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);// 建立socket連線
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if (!socket.RecvUint32(&cmd, &timeout_ms)) { //讀取socket中的操作資訊
        PLOG(ERROR) << "sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) { //根據操作資訊,執行對應處理,兩者區別一個是以char形式讀取,一個以String形式讀取
    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;

        handle_property_set(socket, prop_value, prop_value, true);
        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;
        }

        handle_property_set(socket, name, value, false);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break;
    }
}
複製程式碼

4.4 handle_property_set

定義在platform/system/core/init/property_service.cpp

這就是最終的處理函式,以"ctl."開頭的key就做一些Service的Start,Stop,Restart操作,其他的就是呼叫property_set進行屬性設定, 不管是前者還是後者,都要進行SELinux安全性檢查,只有該程式有操作許可權才能執行相應操作

static void handle_property_set(SocketConnection& socket,
                                const std::string& name,
                                const std::string& value,
                                bool legacy_protocol) {
  const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
  if (!is_legal_property_name(name)) { //檢查key的合法性
    LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal 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.")) { //如果以ctl.開頭,就執行Service的一些控制操作
    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {//SELinux安全檢查,有許可權才進行操作
      handle_control_message(name.c_str() + 4, value.c_str());
      if (!legacy_protocol) {
        socket.SendUint32(PROP_SUCCESS);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
                 << " service ctl [" << value << "]"
                 << " uid:" << cr.uid
                 << " gid:" << cr.gid
                 << " pid:" << cr.pid;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
      }
    }
  } else { //其他的屬性呼叫property_set進行設定
    if (check_mac_perms(name, source_ctx, &cr)) {//SELinux安全檢查,有許可權才進行操作
      uint32_t result = property_set(name, value);
      if (!legacy_protocol) {
        socket.SendUint32(result);
      }
    } else {
      LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
      if (!legacy_protocol) {
        socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
      }
    }
  }

  freecon(source_ctx);
}
複製程式碼

小結

init程式第二階段主要工作是初始化屬性系統,解析SELinux的匹配規則,處理子程式終止訊號,啟動系統屬性服務,可以說每一項都很關鍵,如果說第一階段是為屬性系統,SELinux做準備,那麼第二階段就是真正去把這些落實的,下一篇我們將講解.rc檔案的解析

相關文章