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檔案的解析