天天看點

Android P: 系統啟動流程之init程序II1. 概述2. 建立程序會話密鑰并初始化屬性系統3. 進行SELinux第二階段并恢複一些檔案安全上下文4. 建立 epoll 并初始化子程序終止信号處理函數5. 設定其他系統屬性并開啟系統屬性服務6. 總結

Android P: 系統啟動流程之init程序II

  • 1. 概述
  • 2. 建立程序會話密鑰并初始化屬性系統
    • 2.1 keyctl
    • 2.2 property_init
    • 2.3 CreateSerializedPropertyInfo
    • 2.4 __system_property_area_init
    • 2.5 process_kernel_dt();
    • 2.6 property_set
    • 2.7 process_kernel_cmdline();
    • 2.8 export_kernel_boot_props();//其他屬性設定
  • 3. 進行SELinux第二階段并恢複一些檔案安全上下文
    • 3.1 selinux_android_file_context_handle
    • 3.2 selabel_open
    • 3.3 initfuncs
    • 3.4 SelinuxRestoreContext();
  • 4. 建立 epoll 并初始化子程序終止信号處理函數
    • 4.1 epoll_create1
    • 4.2 sigchld_handler_init
    • 4.3 handle_signal
    • 4.4 ReapOneProcess
  • 5. 設定其他系統屬性并開啟系統屬性服務
    • 5.1 設定其他系統屬性
    • 5.2 start_property_service
    • 5.3 handle_property_set_fd
    • 5.4 HandlePropertySet
  • 6. 總結

1. 概述

前一篇中講了 init 程序的第一階段,我們接着講第二階段,主要有以下内容

A.建立程序會話密鑰并初始化屬性系統

B.進行 SELinux 第二階段并恢複一些檔案安全上下文

C.建立 epoll 并初始化子程序終止信号處理函數

D.設定其他系統屬性并開啟系統屬性服務
           
SRC:
	mt6779-p/system/core/init/init.cpp
	mt6779-p/system/core/init/keyutils.h
	mt6779-p/system/core/init/property_service.cpp
	mt6779-p/external/selinux/libselinux/src/label.c
	mt6779-p/system/core/init/signal_handler.cpp
	mt6779-p/system/core/init/service.cpp
	mt6779-p/system/core/init/property_service.cpp
           

2. 建立程序會話密鑰并初始化屬性系統

第二階段一開始會有一個 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_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();//初始化屬性系統,并從指定檔案讀取屬性
	
		// 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,中文意思是裝置樹,這裡面記錄自己的硬體配置和系統運作參數
		*/
		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();//處理其他的一些屬性
	
		// 将第一階段設定的環境變量設定為property屬性
		// 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");
	
		SELinux再次初始化
		// Now set up SELinux for second stage.
		SelinuxSetupKernelLogging();/ 将selinux日志重定向到核心日志輸出, 即/dev/kmsg	
		SelabelInitialize();
		SelinuxRestoreContext();// 恢複file_contexts中的初始配置
	
		// 建立EPOLL
		epoll_fd = epoll_create1(EPOLL_CLOEXEC);
		if (epoll_fd == -1) {
			PLOG(FATAL) << "epoll_create1 failed";
		}
		
		/*
		* 建立一個互相連接配接的套接字對
		* 接收SIGCHLD信号時往其中一個套接字寫
		* 另一個套接字的讀則注冊到EPOLL中
		*/	
		sigchld_handler_init();
	
		if (!IsRebootCapable()) {
			// If init does not have the CAP_SYS_BOOT capability, it is running in a container.
			// In that case, receiving SIGTERM will cause the system to shut down.
			InstallSigtermHandler();
		}
	
		LoadRscRoProps();
		property_load_boot_defaults();// 加載系統啟動屬性
		export_oem_lock_status();
		start_property_service();// 啟動屬性系統服務
		set_usb_controller();// 設定sys.usb.controller屬性值
		...
	}
           

2.1 keyctl

SRC:mt6779-p/system/core/init/keyutils.h

keyctl 将主要的工作交給 __NR_keyctl 這個系統調用,keyctl 是Linux系統操縱核心的通訊密鑰管理工具

我們分析下 keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);--> return keyctl(KEYCTL_GET_KEYRING_ID, id, create);

KEYCTL_GET_KEYRING_ID 表示通過第二個參數的類型擷取目前程序的密鑰資訊
KEY_SPEC_SESSION_KEYRING 表示擷取目前程序的SESSION_KEYRING(會話密鑰環)
1 表示如果擷取不到就建立一個
參考linux手冊

這裡并沒有拿傳回值,估計就是為了建立會話密鑰環了,從注釋Set up a session keyring也可看出
           
static long keyctl(int cmd, ...) {
		va_list va;
		//va_start,va_arg,va_end 是配合使用的,用于将可變參數從堆棧中讀取出來
		va_start(va, cmd);//擷取第一個參數位址
		unsigned long arg2 = va_arg(va, unsigned long);//va_arg 周遊參數
		unsigned long arg3 = va_arg(va, unsigned long);
		unsigned long arg4 = va_arg(va, unsigned long);
		unsigned long arg5 = va_arg(va, unsigned long);
		va_end(va);//va_end 恢複堆棧
		return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); //系統調用
	}
           

2.2 property_init

SRC:mt6779-psystem/core/init/property_service.cpp
	
	直接交給 __system_property_area_init 處理
	void property_init() {
		mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);//建立目錄 /dev/__properties__,權限是rwx-x-x
		CreateSerializedPropertyInfo();
		if (__system_property_area_init()) {
			LOG(FATAL) << "Failed to initialize property area";
		}
		if (!property_info_area.LoadDefaultPath()) {//加載預設路徑:
				LoadPath("/dev/__properties__/property_info");
			LOG(FATAL) << "Failed to load serialized property info file";
		}
	}
           

2.3 CreateSerializedPropertyInfo

void CreateSerializedPropertyInfo() {
		auto property_infos = std::vector<PropertyInfoEntry>();
		// 讀取 System Property 并加載到 property_infos
		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, 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();
		// 将加載的 System Property 序列化
		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";
		// 将序列化的System Property寫入檔案:/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);// 恢複property_info檔案的Context
	}
           

2.4 __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;//#define PROP_FILENAME "/dev/__properties__"
	}
	
	bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
		if (strlen(filename) > PROP_FILENAME_MAX) {
			return false;
		}
		strcpy(property_filename_, filename);//property_filename_ 為 /dev/__properties__
		
		contexts_ = new (contexts_data_) ContextsSerialized();//構造序列對象
		if (!contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
			return false;
		}
		initialized_ = true;
		return true;
	}	

	bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
		filename_ = filename;
		InitializeProperties();
			property_info_area_file_.LoadDefaultPath()
				return LoadPath("/dev/__properties__/property_info");// 加載System Property預設路徑
			InitializeContextNodes()// 初始化ContextNode
					auto num_context_nodes = property_info_area_file_->num_contexts();
					auto context_nodes_mmap_size = sizeof(ContextNode) * num_context_nodes;
				    void* const map_result = mmap(nullptr, context_nodes_mmap_size, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);//映射記憶體
					new (&context_nodes_[i]) ContextNode(property_info_area_file_->context(i), filename_);// 建立 ContextNode 對象
			
		if (writable) {
			mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);//建立目錄 /dev/__properties__
			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)) {// 将所有property對應的Security Context寫入檔案 
				open_failed = true;
			}
			}
			if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {// 将property序列化并保持至/dev/__properties/properties_serial
			FreeAndUnmap();
			return false;
			}
		} else {
			if (!MapSerialPropertyArea(false, nullptr)) {
			FreeAndUnmap();
			return false;
			}
		}
		return true;
	}
           

2.5 process_kernel_dt();

SRC:mt6779-p/system/core/init/init.cpp
	
	static void process_kernel_dt() {
		 //判斷 /proc/device-tree/firmware/android/compatible 檔案中的值是否為 android,firmware 
		if (!is_android_dt_value_expected("compatible", "android,firmware")) {// 檢測是否相容Android, 否則直接傳回
			return;
		}
	
		std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(get_android_dt_dir().c_str()), closedir);
		if (!dir) return;
	
		std::string dt_file;
		struct dirent *dp;
		while ((dp = readdir(dir.get())) != NULL) {
			if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
				continue;
			}
	
			std::string file_name = get_android_dt_dir() + dp->d_name;
	
			android::base::ReadFileToString(file_name, &dt_file);//讀取檔案内容
			std::replace(dt_file.begin(), dt_file.end(), ',', '.');
	
			property_set("ro.boot."s + dp->d_name, dt_file);// 将 ro.boot.檔案名 作為key,檔案内容為value,設定進屬性
		}
	}	
           

2.6 property_set

SRC:mt6779-p/bionic/libc/bionic/system_properties.cpp
	
	property_set 用的地方特别多,作用是設定系統屬性值,	
	
	另外就是調用property_changed方法觸發trigger,trigger後續講.rc解析時再詳細講,trigger可以觸發一系列活動

	uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
	
	uint32_t InitPropertySet(const std::string& name, const std::string& value) {
		result = HandlePropertySet(name, value, kInitContext.c_str(), cr, &error);	
			return PropertySet(name, value, error);
	}
	
	static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
		size_t valuelen = value.size();
	
		if (!IsLegalPropertyName(name)) {//檢查 key 合法性,大概就是 xx.xx.xx 這種 ,xx隻能是字母、數字、_、-、@
			*error = "Illegal property name";
			return PROP_ERROR_INVALID_NAME;
		}
	
		if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {//不能超過最大長度 92 并不以 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());//找到key對應節點
		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.")) {//如果加載了 /data/property/persistent_properties 并屬性key 以/persist.開頭
			//将值寫入檔案
			WritePersistentProperty(name, value);
		}
		property_changed(name, value);//觸發trigger
	
		// MTK add log
		if (!(android::base::StartsWith(name, "ro.") ||
			android::base::StartsWith(name, "persist.log.tag") ||
			android::base::StartsWith(name, "vendor.debug.mtk.aee") ||
			android::base::StartsWith(name, "init.svc."))) {
			LOG(INFO) << "PropSet [" << name << "]=[" << value << "] Done";
		}
	
		return PROP_SUCCESS;
	}
           

2.7 process_kernel_cmdline();

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);// 讀取/proc/cmdline, 将androidboot.name=value 的鍵值對儲存為ro.boot.name=value屬性
			android::base::ReadFileToString("/proc/cmdline", &cmdline);
			import_kernel_nv(key, value)
				property_set("ro.boot." + key.substr(12), value)
		if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
	}	
           

2.8 export_kernel_boot_props();//其他屬性設定

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 : prop_map[i].default_value);
		}
	}
           

3. 進行SELinux第二階段并恢複一些檔案安全上下文

SelabelInitialize();//第二階段初始化SELinux policy
    SelinuxRestoreContext();//恢複安全上下文

	void SelabelInitialize() { 其實就是傳遞不同的檔案路徑給 selabel_open
		sehandle = selinux_android_file_context_handle();//建立context的處理函數
		selinux_android_set_sehandle(sehandle);/将剛剛建立的處理指派給 fc_sehandle
	}
           

3.1 selinux_android_file_context_handle

struct selabel_handle* selinux_android_file_context_handle(void)
	{
		struct selinux_opt seopts_file[MAX_FILE_CONTEXT_SIZE];
		int size = 0;
		unsigned int i;
		for (i = 0; i < ARRAY_SIZE(seopts_file_plat); i++) {
			if (access(seopts_file_plat[i].value, R_OK) != -1) {
				seopts_file[size++] = seopts_file_plat[i];
				break;
			}
		}
		for (i = 0; i < ARRAY_SIZE(seopts_file_vendor); i++) {
			if (access(seopts_file_vendor[i].value, R_OK) != -1) {
				seopts_file[size++] = seopts_file_vendor[i];
				break;
			}
		}
		for (i = 0; i < ARRAY_SIZE(seopts_file_odm); i++) {
			if (access(seopts_file_odm[i].value, R_OK) != -1) {
				seopts_file[size++] = seopts_file_odm[i];
				break;
			}
		}
		return selinux_android_file_context(seopts_file, size);
	}	

	static struct selabel_handle* selinux_android_file_context(const struct selinux_opt *opts,
														unsigned nopts)
	{
		struct selabel_handle *sehandle;
		struct selinux_opt fc_opts[nopts + 1];
	
		memcpy(fc_opts, opts, nopts*sizeof(struct selinux_opt));
		fc_opts[nopts].type = SELABEL_OPT_BASEONLY;
		fc_opts[nopts].value = (char *)1;
	
		sehandle = selabel_open(SELABEL_CTX_FILE, fc_opts, ARRAY_SIZE(fc_opts));
		if (!sehandle) {
			selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n",
					__FUNCTION__, strerror(errno));
			return NULL;
		}
		if (!compute_file_contexts_hash(fc_digest, opts, nopts)) {
			selabel_close(sehandle);
			return NULL;
		}
	
		selinux_log(SELINUX_INFO, "SELinux: Loaded file_contexts\n");
	
		return sehandle;
	}
           

3.2 selabel_open

SRC:mt6779-p/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->digest = selabel_is_digest_set(opts, nopts, rec->digest);
	
		if ((*initfuncs[backend])(rec, opts, nopts)) {
			selabel_close(rec);
			rec = NULL;
		}
	out:
		return rec;
	}

           

3.3 initfuncs

SRC:mt6779-p/external/selinux/libselinux/src/label.c
	
	這些數組對應 backend 的6種可能的值	
	
	nitfuncs 數組中每一項都對應一個init函數,init函數主要作用是解析傳進來的檔案,這些傳進來的檔案定義了哪些程序可以通路哪些檔案,執行哪些操作
	
	SELinux的内容比較多,由于篇幅就暫時不深入了

	static selabel_initfunc initfuncs[] = {
		CONFIG_FILE_BACKEND(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),
	};
           

3.4 SelinuxRestoreContext();

SRC:mt6779-p/system/core/init/init.cpp

	主要就是恢複這些檔案的安全上下文,因為這些檔案是在SELinux安全機制初始化前建立,是以需要重新恢複下安全性
	void SelinuxRestoreContext() {
		LOG(INFO) << "Running restorecon...";
		selinux_android_restorecon("/dev", 0);
		selinux_android_restorecon("/dev/kmsg", 0);
		if constexpr (WORLD_WRITABLE_KMSG) {
			selinux_android_restorecon("/dev/kmsg_debug", 0);
		}
		selinux_android_restorecon("/dev/socket", 0);
		selinux_android_restorecon("/dev/random", 0);
		selinux_android_restorecon("/dev/urandom", 0);
		selinux_android_restorecon("/dev/__properties__", 0);
	
		selinux_android_restorecon("/plat_file_contexts", 0);
		selinux_android_restorecon("/nonplat_file_contexts", 0);
		selinux_android_restorecon("/vendor_file_contexts", 0);
		selinux_android_restorecon("/plat_property_contexts", 0);
		selinux_android_restorecon("/nonplat_property_contexts", 0);
		selinux_android_restorecon("/vendor_property_contexts", 0);
		selinux_android_restorecon("/plat_seapp_contexts", 0);
		selinux_android_restorecon("/nonplat_seapp_contexts", 0);
		selinux_android_restorecon("/vendor_seapp_contexts", 0);
		selinux_android_restorecon("/plat_service_contexts", 0);
		selinux_android_restorecon("/nonplat_service_contexts", 0);
		selinux_android_restorecon("/vendor_service_contexts", 0);
		selinux_android_restorecon("/plat_hwservice_contexts", 0);
		selinux_android_restorecon("/nonplat_hwservice_contexts", 0);
		selinux_android_restorecon("/vendor_hwservice_contexts", 0);
		selinux_android_restorecon("/sepolicy", 0);
		selinux_android_restorecon("/vndservice_contexts", 0);
	
		selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
		selinux_android_restorecon("/dev/device-mapper", 0);
	
		selinux_android_restorecon("/sbin/mke2fs_static", 0);
		selinux_android_restorecon("/sbin/e2fsdroid_static", 0);
	
		selinux_android_restorecon("/sbin/mkfs.f2fs", 0);
		selinux_android_restorecon("/sbin/sload.f2fs", 0);
	}	

           

4. 建立 epoll 并初始化子程序終止信号處理函數

epoll_fd = epoll_create1(EPOLL_CLOEXEC);//建立epoll執行個體,并傳回epoll的檔案描述符
    if (epoll_fd == -1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }
           

4.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
           

4.2 sigchld_handler_init

SRC:mt6779-p/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 sigchld_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(FATAL) << "socketpair failed in sigchld_handler_init";
		}
	
		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.sa_flags = SA_NOCLDSTOP;
		sigaction(SIGCHLD, &act, 0);
	
		ReapAnyOutstandingChildren();
	
		register_epoll_handler(signal_read_fd, handle_signal);
	}
           

4.3 handle_signal

SRC:mt6779-p/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));讀取資料
	
		ReapAnyOutstandingChildren();
			ReapOneProcess()
	}
           

4.4 ReapOneProcess

SRC:mt6779-p/system/core/init/service.cpp
	
	這是最終的處理函數了,這個函數先用 waitpid 找出挂掉程序的 pid,然後根據pid找到對應 Service,
	
	最後調用 Service 的 Reap 方法清除資源,根據程序對應的類型,決定是否重新開機機器或重新開機程序
	static bool ReapOneProcess() {
		siginfo_t siginfo = {};
		// This returns a zombie pid or informs us that there are no zombies left to be reaped.
		// It does NOT reap the pid; that is done below.
		if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
			PLOG(ERROR) << "waitid failed";
			return false;
		}
	
		auto pid = siginfo.si_pid;
		if (pid == 0) return false;
	
		// At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
		// whenever the function returns from this point forward.
		// We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
		// want the pid to remain valid throughout that (and potentially future) usages.
		//用 waitpid 函數擷取狀态發生變化的子程序 pid
		 //waitpid的标記為 WNOHANG,即非阻塞,傳回為正值就說明有程序挂掉了
		auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
	
		std::string name;
		std::string wait_string;
		Service* service = nullptr;
	
		if (PropertyChildReap(pid)) {
			name = "Async property child";
		} else if (SubcontextChildReap(pid)) {
			name = "Subcontext";
		} else {
			service = ServiceList::GetInstance().FindService(pid, &Service::pid);//通過pid找到對應的Service
	
			if (service) {
				name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
				if (service->flags() & SVC_EXEC) {
					auto exec_duration = boot_clock::now() - service->time_started();
					auto exec_duration_ms =
						std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
					wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
				}
			} else {
				name = StringPrintf("Untracked pid %d", pid);
			}
		}
	
		if (siginfo.si_code == CLD_EXITED) {
			LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
		} else {
			LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
		}
	
		if (!service) return true; //沒有找到,說明已經結束了
	
		service->Reap(siginfo);//清除子程序相關的資源
	
		if (service->flags() & SVC_TEMPORARY) {
			ServiceList::GetInstance().RemoveService(*service);
		}
	
		return true;
	}
           

5. 設定其他系統屬性并開啟系統屬性服務

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

5.1 設定其他系統屬性

property_load_boot_defaults,export_oem_lock_status,set_usb_controller 這三個函數都是調用 property_set 設定一些系統屬性
           

5.2 start_property_service

SRC:mt6779-p/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() {
		selinux_callback cb;
		cb.func_audit = SelinuxAuditCallback;
		selinux_set_callback(SELINUX_CB_AUDIT, cb);
	
		property_set("ro.property_service.version", "2");
	
		//建立socket用于通信
		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);//監聽property_set_fd,設定最大并發數為8
	
		register_epoll_handler(property_set_fd, handle_property_set_fd);//注冊epoll事件
	}
           

5.3 handle_property_set_fd

SRC:mt6779-p/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;
		}
	
		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;
	
			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;
		}
	}
           

5.4 HandlePropertySet

SRC:mt6779-p/system/core/init/property_service.cpp

這就是最終的處理函數,以"ctl."開頭的key就做一些Service的Start,Stop,Restart操作,其他的就是調用 PropertySet 進行屬性設定,

不管是前者還是後者,都要進行SELinux安全性檢查,隻有該程序有操作權限才能執行相應操作
           
// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
	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)) {//檢查key的合法性
			*error = "Illegal property name";
			return PROP_ERROR_INVALID_NAME;
		}
	
		if (StartsWith(name, "ctl.")) {//對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") {//對key為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);其他屬性的處理。
	}		
           

6. 總結

init 程序第二階段主要工作是初始化屬性系統,解析SELinux的比對規則,處理子程序終止信号,

啟動系統屬性服務,可以說每一項都很關鍵,如果說第一階段是為屬性系統,SELinux做準備,

那麼第二階段就是真正去把這些落實的,後續我們将探索.rc檔案的解析
           

繼續閱讀