Android 9 (P)之init进程启动源码分析指南之二


Android 9 (P) 系统启动及进程创建源码分析目录:

Android 9 (P)之init进程启动源码分析指南之一
Android 9 (P)之init进程启动源码分析指南之二
Android 9 (P)之init进程启动源码分析指南之三
Android 9 (P)核心服务和关键进程启动
Android 9 (P)Zygote进程启动源码分析指南一
Android 9 (P)Zygote进程启动源码分析指南二
Android 9 (P)系统启动之SystemServer大揭秘上
Android 9 (P)系统启动之SystemServer大揭秘下
Android 9 (P)应用进程创建流程大揭秘



引言

  在上一篇章Android 9 (P)之init进程启动源码分析指南之一中我们讲解了init启动的第一阶段工作,在第一阶段中init主要做了如下几方面的工作:

  • ueventd/watchdogd跳转判断以及其它初始化
  • 创建并挂载相关的文件系统
  • 初始化内核Log系统
  • 文件系统挂载
  • SELinux Init初始化
  • 第一阶段收尾和第二阶段准备工作

在本篇章中我们将要讲解init的第二阶段相关内容。

注意:本文演示的代码是Android P高通msm8953平台源码。涉及的源码如下:

system/core/init/init.cpp
system/core/libkeyutils/include/keyutils.h
system/core/libkeyutils/keyutils.cpp
bionic/libc/bionic/system_property_api.cpp
bionic/libc/include/sys/_system_properties.h
bionic/libc/system_properties/system_properties.cpp
system/core/init/util.cpp
external/selinux/libselinux/src/android/android_platform.c
system/core/init/selinux.cpp
system/core/init/init/sigchld_handler.h
system/core/init/init/sigchld_handler.cpp
system/core/init/init/init.cpp



一. Init启动第二阶段(用户态)

  通过前面的篇章我们了解到init启动的第一阶段还处于内核态,而进入init第二阶段则进入了用户态,init进程的第二阶段仍然从main函数开始入手(继续分析main函数剩余源码)。好吗,让我们开干,read the fucking code!其内核打印流程日志大致如下:

14,1287,13153937,-;init: init second stage started!
14,1288,13212668,-;init: Using Android DT directory /proc/device-tree/firmware/android/
14,1289,13221989,-;selinux: SELinux: Loaded file_contexts\x0a
14,1290,13222060,-;init: Running restorecon...
11,1291,13254676,-;init: waitid failed: No child processes
12,1292,13272393,-;init: Couldn't load property file '/odm/default.prop': open() failed: No such file or directory: No such file or directory
14,1293,13280671,-;init: Created socket '/dev/socket/property_service', mode 666, user 0, group 0
14,1294,13286239,-;init: Forked subcontext for 'u:r:vendor_init:s0' with pid 409
14,1295,13293752,-;init: Forked subcontext for 'u:r:vendor_init:s0' with pid 410
14,1296,13298801,-;init: Parsing file /init.rc...
14,1297,13448446,-;ueventd: ueventd started!
14,1298,13455793,-;selinux: SELinux: Loaded file_contexts\x0a

1.1 创建进程会话密钥

int main(int argc, char** argv) {
	......
	 // ueventd 和 watchdog 跳转的判断以及其它初始化的设置
	 bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
	 //跳过这个阶段
	 if (is_first_stage) {
	 	......
	 }
	     // At this point we're in the second stage of init.
    InitKernelLogging(argv);//初始化内核log系统,具体参见篇章一的介绍
    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进行中
	......
}

  由于我么在init启动的第一阶段设置了INIT_SECOND_STAGE 值,所以会跳过is_first_stage直接进入第二阶段,InitKernelLogging的功能在第一阶段介绍了就不介绍了,这里我们简单分析一下keyctl_get_keyring_ID函数的作用,通过它的注释作用大概如下:

设置所有进程都可以访问的会话密钥环。 它将保留FBE加密密钥之类的内容。 不应覆盖任何过程其会话密钥环。

1.1.1 keyctl_get_keyring_ID

  本代码源码路径如下system/core/libkeyutils/keyutils.cpp,其基本逻辑如下:

// Deliberately not exposed. Callers should use the typed APIs instead.
static long keyctl(int cmd, ...) {
  va_list va; 
   //va_start,va_arg,va_end是配合使用的,用于将可变参数从堆栈中读取出来
  va_start(va, cmd);//va_start是获取第一个参数地址
  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);//val_end va_end 恢复堆栈
  return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);//syscall内核调用
}


key_serial_t keyctl_get_keyring_ID(key_serial_t id, int create) {
  return keyctl(KEYCTL_GET_KEYRING_ID, id, create);
}

//将参数带入得到如下代码逻辑
keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1);

  这里使用到的是内核提供给用户空间使用的 密钥保留服务 (key retention service),它的主要意图是在 Linux 内核中缓存身份验证数据。远程文件系统和其他内核服务可以使用这个服务来管理密码学、身份验证标记、跨域用户映射和其他安全问题。它还使 Linux 内核能够快速访问所需的密钥,并可以用来将密钥操作(比如添加、更新和删除)委托给用户空间。关于这块的介绍具体参考博客Linux 密钥保留服务入门,里面有比较详细的介绍。

  让我们将参数带入,会得到下面的代码逻辑,其中各个参数逻辑如下:

  • KEYCTL_GET_KEYRING_ID:表示通过第二个参数的类型获取当前进程的密钥信息
  • KEY_SPEC_SESSION_KEYRING:表示获取当前进程的 SESSION_KEYRING
  • 1:表示如果获取不到就新建一个会话密钥环

可以看到keyctl最后是通过syscall 与系统内核的通讯密钥管理工具交互的,这个大家了解一下即可。


1.2 初始化属性服务以及加载各种属性域

  Android property 系统其实可以理解为键值对:属性名字和属性值。大部分 property是记录在某些文件中的, init 进程启动的时候,会加载这些文件,完成 property 系统初始化工作。其代码逻辑如下:

    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.
    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);

1.2.1 property_init 初始化属性域

  该函数主要作用是初始化属性域,在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。初始化这个过程有点多,不是本章讨论的重点,大家可以参见下面的关系调用图分析,这篇博客Android O属性服务源码详解写得不错,各位可以看看。
在这里插入图片描述

1.2.2 process_kernel_dt

  该函数定义在system/core/init/init.cpp中,其主要作用是读取设备树(DT)上的属性设置信息,在Android P系统中device-tree 的目录为/proc/device-tree/firmware/android/compatible,如下所示:

E800:/proc/device-tree/firmware/android # ls
compatible fstab name vbmeta
E800:/proc/device-tree/firmware/android #

然后按照一定规则查找系统属性,然后通过 property_set 设置系统属性。

static void process_kernel_dt() {
    // 判断 /proc/device-tree/firmware/android/compatible 文件中的值是否为 android,firmware
    if (!is_android_dt_value_expected("compatible", "android,firmware")) {
        return;
    }
	//kAndroidDtDir的值为/proc/device-tree/firmware/android,其中get_android_dt_dir的源码在system/core/init/util.cpp里面
    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) {//遍历dir中的文件
        if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
            //跳过compatible和name文件
            continue;
        }

        std::string file_name = get_android_dt_dir() + dp->d_name;
  		// 读取文件内容,将 , 替换为 . 
        // 然后设置属性以 <ro.boot.文件名 file_value> 格式存储
        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);
    }
}
1.2.3 process_kernel_dt

  该函数牵涉的代码定义在system/core/init/init.cpp和system/core/init/util.cpp里面,该函数解析 kernel 的 cmdline 文件提取以 “androidboot.” 字符串打头的字符串,通过 property_set 设置该系统属性,其中qemu 用于区分启动 android 的设备是否为模拟器,如果是模拟器的话,需要设置的系统属性以 “ro.kernel.” 打头,我们运行的是真机所以这个可以忽略。其中重点需要注意的是Android P的 kernel 的 cmdline 启动参数文件为 /proc/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);
    if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}

void import_kernel_cmdline(bool in_qemu,
                           const std::function<void(const std::string&, const std::string&, bool)>& fn) {
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);

    for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) {
        std::vector<std::string> pieces = android::base::Split(entry, "=");
		if(pieces.size() > 2){
			for(std::vector<std::string>::size_type i = 2; i < pieces.size(); i++){
				pieces[1] += "=" + pieces[i];
			}
		}
        if (pieces.size() >= 2) {
            fn(pieces[0], pieces[1], in_qemu);
        }
    }
}

static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
    if (key.empty()) return;

    if (for_emulator) {
        // In the emulator, export any kernel option with the "ro.kernel." prefix.
        property_set(android::base::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(android::base::StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(),
                     value.c_str());
    }
#if 1
  else if (android::base::StartsWith(key, "mmc_erase_content")){
		property_set("ro.mmc_erase_content", value.c_str());
#endif
  } else if (android::base::StartsWith(key, "xxxdroid.sn")) {
		property_set("ro.fac.sn", value.c_str());
	} else if (android::base::StartsWith(key, "xxxdroid.exsn")) {
		property_set("ro.fac.exsn", value.c_str());
	} else if (android::base::StartsWith(key, "xxxdroid.aboot_version")){
		property_set("ro.fac.aboot_version", value.c_str());
	} else if (android::base::StartsWith(key, "xxxdroid.aboot_git")){
		property_set("ro.fac.aboot_version_git", value.c_str()); 
	}else if (android::base::StartsWith(key, "xxxdroid.configfile")) {
		int len;
		unsigned char *str;
		unsigned char *val_str;

		len = value.size();
		str = (unsigned char *)malloc(len+1);
		val_str = (unsigned char *)malloc(len+1);
		if (str == NULL || NULL == val_str) {
			/* badly */
		}
		strcpy((char* __restrict)val_str,(const char *)value.c_str());

		//ERROR("ganyx: len:%d, str:%s\n", len, str);
		//LOG(INFO)<<"ganyx1: len:"<<len;
		
		len = base64decode((const unsigned char *)val_str, len, str, len);
		
		str[len] = 0;
		if (strncmp((const char *)(str+len-16), "SIGNED_VER:", (size_t)(sizeof("SIGNED_VER:")-1)) ==0) {
			/* remove signed data */
			*(str+len-284) = 0;
		}
		free(val_str);
		parse_config_file((const char *)str);
		free(str);
	}    
}

其中cat /proc/cmdline的内容如下所示,当然不同机型和平台可能有差异,仅供参考作用。

core_ctl_disable_cpumask=0-7 
kpti=0 
console=ttyMSM0,115200,n8 
androidboot.console=ttyMSM0 
androidboot.hardware=qcom 
msm_rtb.filter=0x237 
ehci-hcd.park=3 
lpm_levels.sleep_disabled=1 
androidboot.bootdevice=7824900.sdhci 
earlycon=msm_serial_dm,0x78af000 
firmware_class.path=/vendor/firmware_mnt/image 
androidboot.usbconfigfs=true 
loop.max_part=7 
buildvariant=userdebug 
androidboot.emmc=true 
androidboot.pmi_mode=nopmi 
androidboot.verifiedbootstate=orange 
androidboot.keymaster=1 
root=PARTUUID=174c6ac6-7f74-f102-84b5-d04c631a160a 
androidboot.vbmeta.device=PARTUUID=23e2bc13-82ef-4123-566d-4486fac5eba5 
androidboot.vbmeta.avb_version=1.0 
androidboot.vbmeta.device_state=unlocked 
androidboot.vbmeta.hash_alg=sha256 
androidboot.vbmeta.size=5184 
androidboot.vbmeta.digest=c6514d102da4a393636dd2e9f9cb6c2faf358a629532d5bb02b5efae9b06947b 
androidboot.veritymode=disabled 
androidboot.serialno=42264871 
androidboot.baseband=msm 
mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_xxx_1920x1080_video:1:qcom,mdss_dsi_xxx_1920x1080_video_1:cfg:dual_dsi skip_initramfs rootwait ro 
init=/init 
androidboot.dtbo_idx=2
1.2.4 export_kernel_boot_props

  该函数定义在system/core/init/init.cpp,该函数的主要作用是额外设置一些属性,在章节1.2.2处理dt属性和1.2.3章节处理kernek cmd 系统属性的过程中引入了一些以 “ro.boot.” 为前缀的系统属性值,这里如果结构体prop_map中src_prop 对应的属性值不为空,则额外增加设置到 dst_prop 属性中。

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);
    }
}
1.2.5 设置其它零碎属性

  分析至此,属性加载设置基本完毕了,然后就是一些零碎的属性设置了,这个没有什么好说的了。

    // 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);

    // Set memcg property based on kernel cmdline argument
    bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false);
    if (memcg_enabled) {
       // root memory control cgroup
       mkdir("/dev/memcg", 0700);
       chown("/dev/memcg",AID_ROOT,AID_SYSTEM);
       mount("none", "/dev/memcg", "cgroup", 0, "memory");
       // app mem cgroups, used by activity manager, lmkd and zygote
       mkdir("/dev/memcg/apps/",0755);
       chown("/dev/memcg/apps/",AID_SYSTEM,AID_SYSTEM);
       mkdir("/dev/memcg/system",0550);
       chown("/dev/memcg/system",AID_SYSTEM,AID_SYSTEM);
    }

1.3 清空环境变量

    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");                 // 清除掉之前使用过的环境变量
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

1.4 进行SELinux第二阶段工作并恢复安全上下文

    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();//初始化内核log系统,具体参见篇章一的介绍
    SelabelInitialize();//电二阶段初始化SELinux
    SelinuxRestoreContext();//恢复安全上下文
1.4.1 SelabelInitialize

  这里牵涉到的代码路径为external/selinux/libselinux/src/android/android_platform.c和system/core/init/selinux.cpp,其代码逻辑如下:


// selinux_android_file_context_handle() takes on the order of 10+ms to run, so we want to cache
// its value.  selinux_android_restorecon() also needs an sehandle for file context look up.  It
// will create and store its own copy, but selinux_android_set_sehandle() can be used to provide
// one, thus eliminating an extra call to selinux_android_file_context_handle().
void SelabelInitialize() {
    sehandle = selinux_android_file_context_handle();//创建context的处理函数
    selinux_android_set_sehandle(sehandle);//将刚刚新建的处理赋值给fc_sehandle
}

static const struct selinux_opt seopts_file_plat[] = {
    { SELABEL_OPT_PATH, "/system/etc/selinux/plat_file_contexts" },
    { SELABEL_OPT_PATH, "/plat_file_contexts" }
}; 

static const struct selinux_opt seopts_file_odm[] = { 
    { SELABEL_OPT_PATH, "/odm/etc/selinux/odm_file_contexts" },
    { SELABEL_OPT_PATH, "/odm_file_contexts" }
}; 
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);
}

void selinux_android_set_sehandle(const struct selabel_handle *hndl)
{   
      fc_sehandle = (struct selabel_handle *) hndl;
}

  在init启动启动第一阶段内核态我们嗲用SelinuxInitialize函数初始化了SELinux,而在这一阶段主要调用selinux_android_file_context_handle初始化context_handle。
  根据Android SELinux开发入门指南博客中的描述,init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询 file_contexts & property_contexts 文件中的安全上下文。

  以系统属性为例,当其他进程通过 socket 与系统属性通信时请求访问某一项系统属性的值时,属性服务系统会通过 libselinux 提供的 selabel_lookup 函数到 property_contexts 文件中查找要访问属性的安全上下文,有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。

  所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。

1.4.2 SelinuxRestoreContext

  这个函数主要用户恢复按照SELinux指定的文件的安全上下文,这个理解起来也非常的简单因为这些文件是SELinux安全机制初始化之前创建的,所以需要重新恢复安全性,以保证指定的SELinux规则能生效。

// The files and directories that were created before initial sepolicy load or
// files on ramdisk need to have their security context restored to the proper
// value. This must happen before /dev is populated by ueventd.
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);
}

1.5 创建epoll句柄并初始化子进程终止信号处理函数

    epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,返回epoll描述符
    if (epoll_fd == -1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }

    sigchld_handler_init();//初始化sigchld_hander处理函数

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

  init是一个守护进程,Android的设计者为了防止init的进程成为僵尸进程(zombie process),需要init在子进程结束时获取该子进程的结束码,进而通过该结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

  在这里我们有必要简单介绍一下Linux进程的状态,我们知道Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态,其状态主要分为如下几种:

  • Linux进程状态:R (TASK_RUNNING),可执行状态&运行状态(在run_queue队列里的状态)

  • Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态, 可处理signal

  • Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态, 可处理signal, 有延迟

  • Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态, 不可处理signal, 因为根本没有时间片运行代码

  • Linux进程状态:Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。不可被kill, 即不响应任务信号, 无法用SIGKILL杀死

  对于上面的进程状态我们可以通过ps -A在运行的相关Android终端进行查看如下,可以看到大部分进程都处于S状态。

USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME
root             1     0   32248   3472 SyS_epoll_wait      0 S init
root             2     0       0      0 kthreadd            0 S [kthreadd]
root             3     2       0      0 smpboot_thread_fn   0 S [ksoftirqd/0]
root             5     2       0      0 worker_thread       0 S [kworker/0:0H]
root             7     2       0      0 rcu_gp_kthread      0 S [rcu_preempt]
root             8     2       0      0 rcu_gp_kthread      0 S [rcu_sched]
root             9     2       0      0 rcu_gp_kthread      0 S [rcu_bh]
root            10     2       0      0 rcu_nocb_kthread    0 S [rcuop/0]
root            11     2       0      0 rcu_nocb_kthread    0 S [rcuos/0]
root            12     2       0      0 rcu_nocb_kthread    0 S [rcuob/0]

  最后我们必须要知道的一点在Linux当中,父进程是通过捕捉 SIGCHLD 信号来得知子进程运行结束的情况,而子进程在进程终止时会发出该信号。关于这个可以延伸一下Android的tombstones,网易的云捕等相关的Native crash捕获工具基本逻辑也是如此。

1.5.1 epoll_create1

  在正式开始讲解该函数之前我们需要了解IO多路复用概念和以及技术发展史。
IO多路复用概念
  I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。有过Linux下网络编程经验的童靴应该知道,IO多路复用是随着网络编程需求的发展而进行者更新迭代和进步的,
  正是由于有了IO多路复用,当你的某个socket可读或者可写的时候。它能够给你一个通知。这样当配合非堵塞的socket使用时,仅仅有当系统通知我哪个描写叙述符可读了,我才去运行read操作。能够保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作相似。操作系统的这个功能通过select/poll/epoll之类的系统调用来实现。这些函数都能够同一时候监视多个描写叙述符的读写就绪状况,这样多个描写叙述符的I/O操作都能在一个线程内并发交替地顺序完毕,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。

IO多路技术发展史
(1) select

NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);


  历史的车轮滚滚向前,IO多路复用技术也是如此,在网络socket编程中很长一段时间使用的是select多路复用,虽然该技术能达到多路复用的功能,但是存在如下缺点:

  • 编写难度大
  • 同时处理的文件描述符是有上限的
  • 每次需要重新设定fd集合
  • 性能会随用户的增多而效率降低

(2) epoll

NAME
       poll, ppoll - wait for some event on a file descriptor

SYNOPSIS
       #include <poll.h>

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

(3) epoll

  终极改进版本,epoll是Linux多路服用IO接口select/poll的加强版,e对应的英文单词就是enhancement,中文翻译为增强,加强,提高,充实的意思。所以epoll模型会显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的 select 实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
  epoll机制一般使用epoll_create(int size)函数创建epoll句柄,size用来告诉内核这个句柄可监听的fd的数目。注意这个参数不同于select()中的第一个参数,在select中需给出最大监听数加1的值。
  此外,当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,能够看到创建出的fd,因此在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。上述代码使用的epoll_create1(EPOLL_CLOEXEC)来创建epoll句柄,该标志位表示生成的epoll fd具有“执行后关闭”特性。上述只是对IO多路复用的讲解,这个还是内容比较多的,大家如果有兴趣可以查阅相关的博客自行进行相关的学习。

NAME
      epoll_create, epoll_create1 - open an epoll file descriptor

SYNOPSIS
      #include <sys/epoll.h>

      int epoll_create(int size);
      int epoll_create1(int flags);

DESCRIPTION
      epoll_create()  creates  an  epoll(7)  instance.  Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; see
      NOTES below.

      epoll_create() returns a file descriptor referring to the new epoll instance.  This file descriptor is used for  all  the  subsequent
      calls  to  the  epoll  interface.   When no longer required, the file descriptor returned by epoll_create() should be closed by using
      close(2).  When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance  and  releases
      the associated resources for reuse.

  epoll_create1()
      If  flags  is 0, then, other than the fact that the obsolete size argument is dropped, epoll_create1() is the same as epoll_create().
      The following value can be included in flags to obtain different behavior:

      EPOLL_CLOEXEC
             Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor.  See the description of the O_CLOEXEC flag in open(2)  for
             reasons why this may be useful.

  有了上述的这番讲解,我想对于这里的epoll_create1就很容理解了,就是通过内核创建一个文件描述符,用于后续的IO多路复用。

1.5.2 sigchld_handler_init
//该函数定义在system/core/init/init/sigchld_handler.cpp
static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        PLOG(ERROR) << "write(signal_write_fd) failed";
    }
}

void sigchld_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    //利用sockerpair创建一对已经连接的socket文件描述符,分别作为信号的读/写端
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        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));
    //设置信号处理器对应的执行函数为SIGCHLD_handler
    //用于处理init子进程的SIGCHLD消息
    act.sa_handler = SIGCHLD_handler;
    act.sa_flags = SA_NOCLDSTOP;

	//调用信号处理函数sigaction,将监听的信号及对应的信号处理器注册到内核中
    sigaction(SIGCHLD, &act, 0);

    //用于终止出现问题的子进程
    ReapAnyOutstandingChildren();

	//注册信号处理函数handle_signal
    register_epoll_handler(signal_read_fd, handle_signal);
}

Linux 信号机制
  在正式开始分析代码前,有些知识点我们必须了解,信号机制是Linux进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,…);另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到相关信号后,可以有三种不同的处理方式。

  • 忽略该信号。
  • .捕捉该信号并执行对应的信号处理函数(signal handler),这里采用的就是这种方法
  • 执行该信号的缺省操作(如 SIGTERM, 其缺省操作是终止进程)。
       #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sigaction(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

       siginfo_t: _POSIX_C_SOURCE >= 199309L

DESCRIPTION
       The  sigaction()  system call is used to change the action taken by a process on receipt of a specific signal.  (See signal(7) for an overview of
       signals.)

       signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.

       If act is non-NULL, the new action for signal signum is installed from act.  If oldact is non-NULL, the previous action is saved in oldact.

       The sigaction structure is defined as something like:

           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

  上述就是Linux信号机制牵涉到的函数和机构体,我们将上述相关知识代入到代码中来分析一把,

  • 首先,利用sockerpair创建一对已经连接的socket文件描述符,分别作为信号的读/写端,这样当一端写入时,另一端就能被通知到,socketpair 两端既可以写也可以读,这里只是单向的让 s[0] 写,s[1] 读。
  • 接着创建sigaction 结构体,这里注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
  • 接着调用sigaction(SIGCHLD, &act, 0)是信号绑定关系,也就是当监听到SIGCHLD信号时,由 act 这个 sigaction 结构体进行处理,最终交由SIGCHLD_handler函数处理。

  上述文字描述还是显得很单薄,空洞不是,那必须拿出必杀技了,有图才有真相不是,让我们通过示意图对整个逻辑把控一番,这样会更加得清晰明了,如果还是不明了,那只能说臣妾做不到了。
在这里插入图片描述
  让我们对上面的示意图捋一捋,顺一顺,总结总结:

  • init进程收到收到子进程 SIGCHLD 信号然后通过 sigaction 函数将信号处理过程转移到 sigaction 结构体

  • sigaction 成员变量 sa_flags 另外指定所关心的具体信号为 SA_NOCLDSTOP,也就是子进程终止信号成员变量 sa_handler 表明当子进程终止时,通过 SIGCHLD_handler 函数处理

  • SIGCHLD_handler 信号处理函数中通过 s[0] 文件描述符写了一个"1",由于 socketpair 的特性,s[1] 能接读到该字符串(后序讲解)

  • 通过 register_epoll_handler 将 s[1] 注册到 epoll 内核事件表中,handle_signal 是 s[1] 有数据到来时的处理函数(后续讲解)

1.5.3 SIGCHLD_handler
//该函数定义在system/core/init/signal_handler.cpp路径
static void SIGCHLD_handler(int) {
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        PLOG(ERROR) << "write(signal_write_fd) failed";
    }
}

  通过前面的代码分析我们可知,当init子进程终止产生SIGCHLD信号时,SIGCHLD_handler将对socketpair对中signal_write_fd执行写操作。由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。

1.5.4 register_epoll_handler

  通过前面的代码分析我么可以知道,在初始化信号监听函数的最后sigchld_handler_init函数调用了register_epoll_handler,该函数定义在system/core/init/init.cpp中,注意这里传入的两个参数分别为signal_read_fd和handle_signal。

void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.ptr = reinterpret_cast<void*>(fn);
    //epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理即调用handle_signal
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        PLOG(ERROR) << "epoll_ctl failed";
    }
}
1.5.5 handle_signal
static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    read(signal_read_fd, buf, sizeof(buf));

    ReapAnyOutstandingChildren();
}

  相关代码定义在system/core/init/signal_handler.cpp,其逻辑非常简单就是读取(清空)signal_read_fd的数据,然后调用ReapAnyOutstandingChildren函数进行相关处理。

1.5.6 ReapAnyOutstandingChildren
void ReapAnyOutstandingChildren() {
    while (ReapOneProcess()) {
    }
}


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.
    //waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束
    //这里waitid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
    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.
    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 {
    	//利用FindService找到pid对应的服务
    	//FindService主要是通过轮询init.rc解析生成的services_列表,找到pid与参数一致的service 
        service = ServiceList::GetInstance().FindService(pid, &Service::pid);

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

	// 根据svc的类型,决定后续的处理方式
    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;
}

  这里我们以一个实例举例,我们有一个进程xxxservice是由init进程启动的,然后我们执行kill -9 将其kill掉,然后在内核里面就能看到对应的如上代码生成的日志信息了。

<14>[  669.181500] init: Service 'xxxservice' (pid 712) received signal 9
<14>[  669.181558] init: Sending signal 9 to service 'xxxservice' (pid 712) process group...
1.5.7 Reap
void Service::Reap(const siginfo_t& siginfo) {
	//清除未携带SVC_ONESHOT 或 携带了SVC_RESTART标志的srvc的进程组
    if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
        KillProcessGroup(SIGKILL);
    }

    // Remove any descriptor resources we may have created.
    //清楚该service中创建出的任意描述符
    std::for_each(descriptors_.begin(), descriptors_.end(),
                  std::bind(&DescriptorInfo::Clean, std::placeholders::_1));

    for (const auto& f : reap_callbacks_) {
        f(siginfo);
    }

    if (flags_ & SVC_EXEC) UnSetExec();

	//清理工作完毕后,后面决定是否重启机器或重启服务
	//TEMPORARY服务不参与这种判断
    if (flags_ & SVC_TEMPORARY) return;

    pid_ = 0;
    flags_ &= (~SVC_RUNNING);
    start_order_ = 0;
    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    // 对于携带了SVC_ONESHOT并且未携带SVC_RESTART的service,将这类服务的标志置为SVC_DISABLED,不再自启动
    if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
        flags_ |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    if (flags_ & (SVC_DISABLED | SVC_RESET))  {
        NotifyStateChange("stopped");
        return;
    }

    // If we crash > 4 times in 4 minutes, reboot into recovery.
     // 未携带SVC_RESTART的关键服务,在规定的间隔内,crash字数过多时,会导致整机重启进入recovery;
    boot_clock::time_point now = boot_clock::now();
    if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
        if (now < time_crashed_ + 4min) {
            if (++crash_count_ > 4) {
                LOG(FATAL) << "critical process '" << name_ << "' exited 4 times in 4 minutes";
            }
        } else {
            time_crashed_ = now;
            crash_count_ = 1;
        }
    }
	// 将待重启service的标志位置为SVC_RESTARTING(init进程将根据该标志位,重启服务)
    flags_ &= (~SVC_RESTART);
    flags_ |= SVC_RESTARTING;

    // Execute all onrestart commands for this service.
    //重启在init.rc文件中带有onrestart选项的服务
    onrestart_.ExecuteAllCommands();

    NotifyStateChange("restarting");
    return;
}

  该代码定义在system/core/init/service.cpp中,该函数的主要作用就是清除问题进程相关的资源,然后根据进程对应的类型,决定是否重启机器或重启进程。这里还是以上个例子举例可以看到xxxservice被杀后,会清除进程信息并重启。

<14>[  669.181500] init: Service 'xxxservice' (pid 712) received signal 9
<14>[  669.181558] init: Sending signal 9 to service 'xxxservice' (pid 712) process group...
<14>[  669.182047] libprocessgroup: Successfully killed process cgroup uid 0 pid 712 in 0ms
<14>[  669.187762] init: starting service 'xxxservice'...
//服务配置选项
service xxxservice /system/bin/xxxservice
      class main
      user root
      group root
1.5.8 ExecuteCommand

  该代码定义在system/core/init/Action.cpp中的ExecuteAllCommands函数中:

void Action::ExecuteAllCommands() const {
    for (const auto& c : commands_) {
        ExecuteCommand(c);
    }
}

void Action::ExecuteCommand(const Command& command) const {
    android::base::Timer t;
    //进程重启时,将执行对应的函数
    auto result = command.InvokeFunc(subcontext_);
	//打印log
    auto duration = t.duration();

    // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
    // device, such as '/sys/class/leds/jogball-backlight/brightness'.  As of this writing, there
    // are 198 such failures on bullhead.  Instead of spamming the log reporting them, we do not
    // report such failures unless we're running at the DEBUG log level.
    bool report_failure = !result.has_value();
    if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
        result.error_errno() == ENOENT) {
        report_failure = false;
    }

    // Any action longer than 50ms will be warned to user as slow operation
    if (report_failure || duration > 50ms ||
        android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
        std::string trigger_name = BuildTriggersString();
        std::string cmd_str = command.BuildCommandString();

        LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
                  << ":" << command.line() << ") took " << duration.count() << "ms and "
                  << (result ? "succeeded" : "failed: " + result.error_string());
    }
}

  最后让我们来总结一下整个流程:

  • 调用epoll_create1创建epoll_fd句柄
  • 调用sigchld_handler_init函数,该函数的本质是本质就是监听子进程死亡的信息,然后进行对应的清理工作,并根据死亡进程的类型,决定是否需要重启进程或机器。

1.6 设置其它一些系统属性

    property_load_boot_defaults();
    export_oem_lock_status();
    set_usb_controller();

  property_load_boot_defaults,export_oem_lock_status,set_usb_controller这三个函数都是加载和设置一些系统属性。

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("/product/build.prop", NULL);
    load_properties_from_file("/odm/default.prop", NULL);
    load_properties_from_file("/vendor/default.prop", NULL);

    update_sys_usb_config();
}

//定义在system/core//init/property_service.cpp
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;
    }
}

1.7 开启系统属性服务

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,于是该socket变成了一个server
    listen(property_set_fd, 8);

	//监听server socket是是否有数据到来
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

  该代码定义在system/core/init/property_service.cpp中,通过前面的代码分析我们知道在init进程在共享内存区域中,创建并初始化属性域,然后通过property_set 可以轻松设置系统属性了,那这里为什么还要大费周章的启动一个属性服务呢?当然做这些都是有原因的,Android出于安全以及权限方面考虑,不是任何猫猫狗狗和随意的进程都可以肆意的修改任何的系统属性。Android为了达到这一目的,将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知 init 进程来修改,而在这过程中,init 进程可以进行权限检测控制,决定是否允许修改。
  那么start_property_service怎么做到上述的要求呢,主要通过如下几个步骤就OK了(这个的逻辑和sigchld_handler_init有点像):

  • 首先创建一个 socket 并返回文件描述符,然后设置最大并发数为 8,其他进程可以通过这个 socket 通知 init 进程修改系统属性
  • 最后注册 epoll 事件,也就是当监听到 property_set_fd 改变时调用 handle_property_set_fd

  在正式开始源码细节分析前,先奉上整体流程图,以供大家心里有个整体概括:
在这里插入图片描述

1.7.1 handle_property_set_fd

  这个函数的作用主要是调用accept4处理property_set_fd的scoket描述符中的数据信息,然后从 socket 中读取操作信息,根据不同的操作类型,调用 HandlePropertySet做具体的操作:

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);
    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;
    }
}
1.7.1 HandlePropertySet

  这个是最终的调用处理函数,set property msg 分为两类处理,msg name 以“ctl.”为起始的 msg 通过HandleControlMessage处理,主要是启动、停止、重启服务。修改其它 prop时会调用 property_get,然后通过 bionic 的__system_property_set 函数来实现,而这个函数会通过 socket 与 init 的 property service 取得联系。但是不管是前者还是后者,都要进行 SELinux 安全性检查,只有该进程有操作权限才能执行相应操作。

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.")) {//如果一ctl开头,就执行Service的一些控制操作
        if (!CheckControlPropertyPerms(name, value, source_context, cr)) {//SELinux安全检查,有权限才进行操作
            *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)) {//检查SElinux规则
        *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
}

  这段代码比较简单,整体的大概流程如下:

  • 通过IsLegalPropertyName检测要设置的是否是合法的属性名
  • 如果是以 “ctl.” 打头的属性名表明是控制命令(譬如ctl.start,ctl.stop,ctl.restart),如果能经过权限检测则调用HandleControlMessage进行处理
  • 其它种情况调用PropertySet处理



总结

  随着Android版本越高,init的工作量也是越来越大了,分析起来不得不使出吃奶的力气了,在init进程的第二阶段主要工作是主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务等等,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些落实的,由于牵涉内容太多了只求将主体讲透彻,细节就需要各位童靴自行把控了。并且这篇文章也是耗时最久的,因为东西太多了。




写在最后

  Android 9 (P)之init进程启动源码分析指南之二的告一段落了,不容易啊分析起来,在接下来的篇章我们将继续讲解init解析init.rc阶段相关工作,具体参见博客Android 9 (P)之init进程启动源码分析指南之三。如果对给位有帮助欢迎点赞一个,如果写得有问题也欢迎多多指正。未完待续,下个篇章再见。

Logo

更多推荐