本文基于Android10.0源码分析
按下电源的那一刻
当按下电源的时候,引导芯片会从ROM预定义的位置(这里一般会是一个独立的ROM:Boot ROM,存着BootLoader的代码)将BootLoader加载到RAM中,BootLoader职责相当于PC的BIOS,也即引导程序。BootLoader的终极目标就是将操作系统拉起运行,即将kernel加载到内存中,并跳转到固定的kernel入口,从那里开始初始化。
Kernel
当BootLoader将kernel加载到RAM后,便从固定的入口开始执行内核代码。这里我尝试去找了下固定的入口是哪,没找到...这里我们对kernel初始化的代码要被迫忽略细节了。那么忽略细节后,根据其他文章,在启动Init进程之前,还有两个进程被启动:
- swapper进程(0号进程):又称为idle进程,系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理,加载Binder Driver、Display、Camera Driver等相关工作。
- kthreadd进程(2号进程):Linux系统的内核进程,是所有内核进程的鼻祖,会创建内核工作线程kworkder,软中断线程ksoftirqd,thermal等内核守护进程。
Init进程-第一个用户进程
Linux内核加载完毕后,会首先启动Init进程,并从下面这段代码开始,源码位于system/core/init/main.cpp
int main(int argc, char** argv) {
// init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
// 进入InitLogging 和SubcontextMain
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
// 加载selinux规则,并设置selinux日志,完成SELinux相关工作
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// 启动第二阶段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 启动第一阶段
return FirstStageMain(argc, argv);
}
并按照FirstStageMain -> SetupSeLinux -> SecondStageMain -> ueventd_main顺序执行
FirstStageMain
int FirstStageMain(int argc, char** argv) {
// Clear the umask.
umask(0);
//初始化系统环境变量
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 挂载及创建基本的文件系统,并设置合适的访问权限
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
...
// 不要将原始命令行公开给非特权进程
CHECKCALL(chmod("/proc/cmdline", 0440));
...
//将内核的stdin/stdout/stderr 全都重定向/dev/null,关闭默认控制台输出
SetStdioToDevNull(argv);
//初始化内核log
InitKernelLogging(argv);
//挂载 system、cache、data 等系统分区
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
...
//进入下一步,SetupSelinux
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
return 1;
}
我们来总结一下,FirstStageMain到底做了哪些重要的事情:
- 挂载及创建基本的文件系统,并设置合适的访问权限
- 关闭默认控制台输出,并初始化内核级log。
- 挂载 system、cache、data 等系统分区
- execv跳转到SetupSelinux执行
SetupSelinux主要是SELinux安全策略,跟本文无关,略过,这里最后是调用execv跳转到了SecondStageMain
SecondStageMain
int SecondStageMain(int argc, char** argv) {
// 禁止OOM killer 杀死该进程以及它的子进程
if (auto result = WriteFile("/proc/1/oom_score_adj", "-1000"); !result) {...}
//初始化属性服务,并从指定文件读取属性
property_init();
...
//初始化Epoll,android这里对epoll做了一层封装
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
//epoll 中注册signalfd,主要是为了创建handler处理子进程终止信号
InstallSignalFdHandler(&epoll);
...
//epoll 中注册property_set_fd,设置其他系统属性并开启系统属性服务
StartPropertyService(&epoll);
...
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
//解析init.rc等文件,建立rc文件的action 、service,启动其他进程, 非常关键的一步
LoadBootScripts(am, sm);
...
// 省略一些init.rc中触发器的调用
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) {
...
// 循环等待事件发生
if (auto result = epoll.Wait(epoll_timeout); !result) {
LOG(ERROR) << result.error();
}
}
return 0;
}
第二阶段做了以下这些比较重要的事情:
- 初始化属性服务,并从指定文件读取属性(重要)
- 初始化epoll
- 解析init.rc文件,并按rc里的定义去启动服务(最重要)
- 开启死循环,用于接收epoll的事件
init.rc
关于rc(run command)文件我们不做介绍,init.rc完成了一些非常重要的动作,我们只需要知道做了哪些事情就好。
在调用init action时,会启动一些核心服务,其中包括logd和servicemanager
on init
...
start logd
start servicemanager
...
在调用late-init时,会执行zygote-start服务
on late-init
...
trigger zygote-start
...
zygote-start指向对应的zygote.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system //创建socket
socket usap_pool_primary stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver //当zygote重启时,则会重启audioserver, 下同
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
zygote.rc是如何fork zyogte进程的呢?zygote.rc文件最终会被转化成一个service对象(如下代码),
将rc里配置的内容全部一一对应写到了service里,并在合适的时机调用service.start,start里便会fork新的进程,子进程即为zyogte进程,并通过exec跳转到/system/bin/app_process去执行。
Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
const std::vector<gid_t>& supp_gids, unsigned namespace_flags,
const std::string& seclabel, Subcontext* subcontext_for_restart_commands,
const std::vector<std::string>& args)
: name_(name),
classnames_({"default"}),
flags_(flags),
pid_(0),
crash_count_(0),
uid_(uid),
gid_(gid),
supp_gids_(supp_gids),
namespace_flags_(namespace_flags),
seclabel_(seclabel),
onrestart_(false, subcontext_for_restart_commands, "<Service '" + name + "' onrestart>", 0,
"onrestart", {}),
ioprio_class_(IoSchedClass_NONE),
ioprio_pri_(0),
priority_(0),
oom_score_adjust_(-1000),
start_order_(0),
args_(args) {}
那么此时呢,作为子进程的zygote已经跳转到了/system/bin/app_process,而init进程便在死循环中等待epoll消息,并在zygote子进程意外退出后,提供重启能力(避免其成为僵尸进程)。我们一般处理僵尸进程是通过父进程wait,而这里没有,因为父进程还需要在死循环中等待epoll消息,所以这里用了另外的手段,至于什么手段我们忽略,那么此时呢,Init可以说是已经完成他的工作,将目光拉到了Zygote这个更重要的进程上。
OK,我们整体总结下这里本篇文章提到的Init的流程
- 启动电源,通过
Boot Rom将BootLoader载入到RAM中 BootLoader将kernel载入到RAM中,并从跳转到kernel的入口去执行- kernel创建第一个进程:swapper(idle)进程,并由swapper进程初始化进程管理、内存管理、加载驱动的工作
- kernel创建第二个进程:kthreadd进程,作为内核进程,并由其fork出
kworkder工作线程,ksoftirqd软中断线程,thermal等内核守护进程。 - 启动第一个用户进程Init
- Init进程将初始化步骤细分按照
FirstStageMain->SetupSeLinux->SecondStageMain顺序执行 - FirstStageMain挂载及创建基本的文件系统,并设置合适的访问权限
- SetupLinux初始化SELinux的安全策略
- SecondStageMain初始化属性服务、epoll、运行init.rc、Init进程开始死循环等待epoll数据。
- 通过init.rc,初始化各种核心服务进程(logd、servicemanager)、启动进程zygote,并exec跳转到zygote进程入口
最后我们用一张流程图来再次描述下本过程。
Ok,接下来介绍Zygote进程
感谢大佬们的文章: