Android Init启动流程(AOSP 11)

Android Init启动流程(AOSP 11)

在Init启动之前

当用户按下电源键时,硬件设备引导芯片的代码会从出厂固定的地方开始执行,加载指定位置的BootLoader到RAM中执行,BootLoader是Android系统启动前的一个程序,它的任务是拉起整个系统的启动。BootLoader之后是Linux Kernel的启动,Kernel启动完成后,会在系统文件中找到init.rc文件,启动init进程。

Android系统本质是运行于Linux内核之上的操作系统,系统的启动是从Linux内核启动开始的。

Linux内核启动的入口是/linux/init/main.c中的start_kernel,其中会通过start_kernel –> rest_init –> cpu_startup_entry->do_idle(在start_kernel的最后,通过一个死循环, 调用do_Idle,始终交出CPU的使用权,这样就启动了内核态的第一个进程idle进程,进程号是0)。

每一个CPU核心都会有一个idle进程,idle进程是当系统没有调度CPU资源的时候,会进入idle进程,而idle进程的作用就是不使用CPU,以此达到省电的目的。

Linux Kernel在启动完成之后,随后是找到要启动的init程序并启动init进程,在Android中,init进程的入口是在:system/core/init中,入口是main.cpp:

int main(int argc, char** argv) {
    // ...
   	// 设置当前进程的优先级
    setpriority(PRIO_PROCESS, 0, -20);
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}
复制代码

setpriority

setpriority是一个Unix系统调用,用于设置进程,进程组,用户进程的优先级,定义如下,niceval的取值越小,设置的优先级越高。其中niceval的取值介于-20 至20 之间,代表进程执行优先权,数值越低代表有较高的优先次序,执行会较频繁,此优先权默认是0, 而只有root用户允许降低此值。

long setpriority(int which,int who,int niceval)
// which取值如下:
// define PRIO_PROCESS    0   //进程
// define PRIO_PGRP   1   //进程组
// define PRIO_USER   2   //用户进程
复制代码

当which为PRIO_PROCESS时,如果参数who为0,则设置当前进程的进程优先级;如果参数who不为0,则设置进程号为who的进程的优先级。

当which为PRIO_PGRP时,如果参数who为0,则设置当前进程组的优先级;参数who不为0,则设置进程组号为who的进程组的优先级。

当which为PRIO_USER时,如果参数who为0,则设置当前用户进程的优先级;参数who不为0,则设置ID为who的用户进程的优先级。

在设置完init进程的优先级为最高级之后,init进程便享有了最高优先级,获得更多的CPU时间片。

第一阶段:FirstStageMain

继续回到main中,主要分为四个步骤:FirstStateMain->SetupSelinux->SecondStageMain->ueventd_main。

init启动的第一阶段:FirstStageMain:(core/init/first_stage_init.cpp)

int FirstStageMain(int argc, char** argv) {
    // ...
		std::vector<std::pair<std::string, int>> errors;
    // 定义一个CHECKCALL,如果内部执行的返回值是非0,就代表对应的步骤执行错误
    // 往errors中输出错误信息。
#define CHECKCALL(x) \
    if ((x) != 0) errors.emplace_back(#x " failed", errno);

    // umask用于设置文件的默认权限,0对于文件而言是666,对于目录而言是777
    // 这样后面的mkdir调用或者mount调用如果不指定权限,就是设置成默认的最大的权限
    umask(0);
		// 省略挂载or创建系统所需要的各种文件目录,设备节点,并设置上相关权限
  	CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    // ...
#undef CHECKCALL
		// 因为SeLinux的安全策略,这里需要把控制台输出console对应的描述符关闭掉
    SetStdioToDevNull(argv);
    // 初始化Kernel的Log系统
    InitKernelLogging(argv);
		// 上面的挂载节点等操作如果有错误,输出错误信息
    if (!errors.empty()) {
        for (const auto& [error_string, error_errno] : errors) {
            LOG(ERROR) << error_string << " " << strerror(error_errno);
        }
        LOG(FATAL) << "Init encountered errors starting first stage, aborting";
    }
  	// ...

    // 挂载系统分区
    if (!DoFirstStageMount(!created_devices)) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
		// ...
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
    // execv会停止执行当前的进程
    // 并且以path对应的应用进程替换被停止执行的进程,进程ID没有改变
    // 即让init进程重生,重新执行main.cpp,同时入参设置为selinux_setup
    execv(path, const_cast<char**>(args));
    // execv只会在传入的新程序中发生异常的时候返回,所以逻辑上是不会执行到这里的。
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
    return 1;
}

复制代码

第二阶段:设置Selinux

那么启动流程流转到了SetupSelinux,Selinux内核模块主要作用就是最大限度地减小系统中服务进程可访问的资源,在SetupSelinux中,做的事儿主要就是设置这个模块的各种安全策略,保证系统的安全性。

在SetupSelinux的最后,execv进程重生,重新回到init中的main函数,并设置入参为second_stage。

const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
复制代码

第三阶段:SecondStageMain

那么接下来就进入init启动的第二阶段:SecondStageMain:

int SecondStageMain(int argc, char** argv) {
    // 设置重启信号的handler
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
  	// ...
    // 1号进程是init进程,这里将1号进程的oom_score_adj写成-1000
    // 可以理解为,禁止OOM Killer杀死init进程及其子进程
    if (auto result =
                WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
        !result.ok()) {
        LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
                   << " to /proc/1/oom_score_adj: " << result.error();
    }
    // ...
    // 初始化系统属性服务,会创建属性服务的目录节点,
  	// 然后从本地各种属性配置文件中加载各种属性服务
    PropertyInit();

    // ...
    // 进行第二阶段的Selinux安全设置
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();
		// 创建Epoll句柄
    Epoll epoll;
    if (auto result = epoll.Open(); !result.ok()) {
        PLOG(FATAL) << result.error();
    }
		// 为上面创建的Epoll句柄注册fd,防止init的子进程成为僵尸进程,
  	// 如果init的子进程(比如zygote)异常挂了,
    // 通过下面的设置,init进程将会接收到SIGCHLD信号,
    // 然后调用到对应的handler进行处理,比如zygote挂了,会移除已经死亡的zygote进程,
  	// 并重新启动zygote进程
    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    // 开启属性服务
    // 开启属性服务的时候,会创建非阻塞性的socket,然后这个socket可以同时
    // 为8个client端提供服务,并最终创建一个PropertyServiceThread线程
    // 然后init及其子进程可以通过property_fd和属性服务进行通信了。
    StartPropertyService(&property_fd);

    // ...
		// 这里创建ActionManager和ServiceList两个容器类型的数据结构,
  	// 用于盛放解析init.rc之后的配置项
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

    // 解析init。rc配置文件,
    // 将init.rc中配置的action和service都解析到ActionManager和ServiceList中
    LoadBootScripts(am, sm);

    // ...
    // 执行init.rc中触发器为early-init的任务
    am.QueueEventTrigger("early-init");
		// ...
    // 执行init.rc中触发器为init的任务
    am.QueueEventTrigger("init");

    // ...
    // 因为下面就是按照init.rc的配置,启动各种系统服务,
    // 到这里需要将init进程的优先级恢复到0的状态,
    // 不然下面一个死循环,将会一直占用CPU执行时间片。
    setpriority(PRIO_PROCESS, 0, 0);
    while (true) {
        // ...
        // 获取到了关机命令,那么开始执行关机操作
        auto shutdown_command = shutdown_state.CheckShutdown();
        if (shutdown_command) {
            LOG(INFO) << "Got shutdown_command '" << *shutdown_command
                      << "' Calling HandlePowerctlMessage()";
            HandlePowerctlMessage(*shutdown_command);
            shutdown_state.set_do_shutdown(false);
        }
				// 如果主线程的循环不处于wait状态,同时ServiceManager没有在运行,
        // 那么就执行上面解析得到的action。
        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        // ...

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            // 如果还有action需要执行,epoll超时设置为0
            // 线程将继续循环任务
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
				
        // epoll等待超时结束或者有事件到来,才会唤醒线程
        auto pending_functions = epoll.Wait(epoll_timeout);
        // ...
        // 一些service进程重启的逻辑
        // ...
    }
    return 0;
}
复制代码

上面提到了一个僵尸进程的概念,所谓的僵尸进程就是父进程fork出子进程后,如果子进程终止了,但是父进程并不知道,那么系统的进程表中会仍然保留这个进程的一些信息,而进程表是一个很珍贵的有限资源,如果进程表被耗尽了,那么系统将无法创建新的进程。

SecondStageMain中最核心的部分就是解析和执行init.rc中定义的各种启动任务,LoadBootScripts中,创建了Parser以及找到目标init.rc文件(源码位置位于core/rootdir/init.rc),然后完成解析工作,这样am和sm中就加载了所有启动阶段要执行的action和service。

init.rc

Init.rc文件的组成主要由两种语句组成,action语句和service语句。

action

action语句的格式如下:

on <trigger触发条件>
   command1
   command2
   
eg:在early-init事件触发的时候,执行下面这些command
on early-init
    write /proc/sys/kernel/sysrq 0
    write /proc/sys/kernel/modprobe \n
    // ...
复制代码

当action的trigger被触发的时候,init进程会逐个执行command,command就是发生对应的action的时候要做的事儿,比如上面的write,就是往对应的文件里边写入指定的字符(如上面的0或者换行符)。

同时,trigger是可以有多个条件的,比如下面这个:

on early-init && property:ro.product.cpu.abilist32=*
    exec_start boringssl_self_test32
复制代码

在early-init被触发,且满足property:ro.product.cpu.abilist32=*这个条件(格式可以理解为name=value)的时候,才会执行这个action的command。

再比如:

on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
    exec_start boringssl_self_test_apex64
复制代码

这个action只有在两个条件都满足的时候才会执行对应的command。

service

action是在条件满足的时候,执行对应的command;而service语句其实就是在条件满足的时候,init将会运行对应的service程序,可以理解为service就是一个个可执行的程序。

service语句的格式如下:

service <name> <path> [<arguments>]
	options
	options
# name是service的名字,path是可执行文件的路径,arguments是启动参数
# options是服务的约束选项,影响了什么时候,如何启动service
	
eg:在init启动过程中的收尾阶段,会去启动ueventd这个守护进程
	
service ueventd /system/bin/ueventd
		# class约束,指定服务启动的class名,同一个class名字的服务必须同时启动或停止。
    class core
    # critical约束,四分钟内如果服务重启四次,设备将重新启动以恢复。
    critical
    # 在ueventd服务启动之前,设置好安全上下文
    seclabel u:r:ueventd:s0
    # shutdown约束,当进程被关闭的时候,执行对应的behavior,
    # 这里critical表示,ueventd在进程被关闭期间,是不会被杀死的
    shutdown critical
复制代码

更多的解释可以看这里:android.googlesource.com/platform/sy…

回到正题,在on early-init和on init这两个阶段的action中,主要做的事儿是创建各种目录,往各种目录中写入字符,或者修改各种目录的权限等工作,还有一些其它的工作,就不一一赘述。

而在on late-init这个action中:

# 挂载文件系统,启动核心系统服务
on late-init
    # trigger是指,在这个action中触发其它的trigger,执行其它action
    trigger early-fs
		# ...
		# 执行zygote-start这个command
    trigger zygote-start
		# ...
复制代码

init启动zygote:

zygote的启动脚本有三份:init.zygote32.rc(纯32位模式),init.zygote64.rc(纯64位模式),init.zygote64_32.rc(64位主模式、32位辅助模式)。(其实更早的版本中有32_64位模式,是将32位作为主模式,但推测在高系统版本中,64位cpu应该已经是标配了)

而在init.rc中:

import /system/etc/init/hw/init.${ro.zygote}.rc
复制代码

通过ro.zygote这个系统属性值,init可以知道应该用哪个zygote启动脚本去启动zygote进程。

而实际上,64位也好、32位也罢,核心差异在于service的name和执行程序的path有所不同,我们以init.zygote64_32.rc为例:

// 64位主模式的zygote名字是zygote
// 执行程序的path是/system/bin/app_process64
// 最后是启动参数
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
  	// zygote所属的class为main,同类的service还有比如storaged,installd
    class main
    // 进程优先级最高(-20)
    priority -20
    // 启动服务前,将用户切换为root用户
    user root
    // 启动服务前,将用户组切换为root用户组
    group root readproc reserved_disk
    // 创建一个名称为/dev/socket/zygote的socket,
    // 然后将socket的fd传递给启动zygote的进程,即init进程
    // socket的类型有三种选项:stream(面向连接的,保障数据可以有序送到接收方->TCP)
    // dgram(不保障消息可以送到接收方,消息不连续不可靠->UDP)
    // seqpacket(保障消息连续可靠)
    // socket的权限为660
    // 最后是user和group
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    // 当服务重启的时候,执行下面这些任务
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    task_profiles ProcessCapacityHigh MaxPerformance
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

// 类似64位的配置
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    task_profiles ProcessCapacityHigh MaxPerformance

复制代码

看完了zygote的启动脚本解析,那么init会在对应的时机,通过class_start main这个command,启动main类别的所有service,而class_start对应的function为do_class_start(具体可以看core/init/builtins.cpp中的定义):

static Result<void> do_class_start(const BuiltinArguments& args) {
    // ...
    // 遍历所有的service
    for (const auto& service : ServiceList::GetInstance()) {
        // 找到类名为main的service
        if (service->classnames().count(args[1])) {
            // 执行service的StartIfNotDisabled方法,启动service
            if (auto result = service->StartIfNotDisabled(); !result.ok()) {
                LOG(ERROR) << "Could not start service '" << service->name()
                           << "' as part of class '" << args[1] << "': " << result.error();
            }
        }
    }
    return {};
}
复制代码

Service::StartIfNotDisabled -> Service::Start,Start实现也比较长,取我们需要关注的逻辑:

// Service的启动最终在这里
Result<void> Service::Start() {
		// ...
    LOG(INFO) << "starting service '" << name_ << "'...";
    std::vector<Descriptor> descriptors;
    // 创建service启动参数中指定的socket
    for (const auto& socket : sockets_) {
        if (auto result = socket.Create(scon); result.ok()) {
            descriptors.emplace_back(std::move(*result));
        } else {
            LOG(INFO) << "Could not create socket '" << socket.name << "': " << result.error();
        }
    }
		// ...
  	// 判断要启动的service是否已经处于running状态,是就没必要再启动了
  	// ...
  	// 判断要启动的service的执行文件是否存在,不存在也没法启动
  	// ...

    // fork出对应的子进程,父进程的资源都将赋值给子进程
    // clone出对应的子进程,父进程的资源有选择性的复制给子进程
    pid_t pid = -1;
    if (namespaces_.flags) {
        // 这里clone创建出的子进程将共享父进程的信号处理表
        pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    // pid == 0表示子进程
    if (pid == 0) {
        // 设置文件权限
        umask(077);
        // ...
        // 内部调用execv函数,Service子进程启动的逻辑就会被执行
        if (!ExpandArgsAndExecv(args_, sigstop_)) {
            PLOG(ERROR) << "cannot execv('" << args_[0]
                        << "'). See the 'Debugging init' section of init's README.md for tips";
        }

        _exit(127);
    }
	// ...
}
复制代码

对于zygote进程而言,启动文件如zygote启动脚本描述,位于/system/bin/app_process64,源码位于/framework/base/cmds/app_process/app_main.cpp,其中的main函数有如下逻辑:

// ... 
// 创建ART实例
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// ...
	  while (i < argc) {
        const char* arg = argv[i++];
        // 如果启动参数中包含了--zygote参数,那么这次启动的就是zygote进程
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            // 如上面分析rc中,zygote启动参数包含了--start-system-server
            startSystemServer = true;
        } 
      // ...
    }
// ...
//         
				if (startSystemServer) {
            // 将要启动system-server的标记传递给zygote启动的过程
            args.add(String8("start-system-server"));
        }
// ...
    // 如果是zygote,那么调用runtime.start函数启动Zygote
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    }
// ...
复制代码

最后一步是ART.start,启动一个指定的Java类名的static main方法,那么zygote的启动,就是通过ZygoteInit这个Java Class来开始的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享