Android O: init进程启动流程分析(阶段一)
Posted ZhangJianIsAStark
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android O: init进程启动流程分析(阶段一)相关的知识,希望对你有一定的参考价值。
目的
最近打算回顾一下以前研究Framework时遇到的问题。
自己发现android演进到8.0后,许多流程又都发生了改变,
于是打算在之前博客的基础上,结合新的代码重新梳理一遍。
本篇博客主要记录一下Android 8.0中的init流程。
背景
当linux内核启动之后,运行的第一个进程是init。
这个进程是一个守护进程,它的生命周期贯穿整个linux 内核运行的始终,
linux中所有其它的进程的共同始祖均为init进程。
Android系统是运作在linux内核上的,为了启动并运行整个android系统,
google实现了android系统的init进程。
Android init进程的入口文件在system/core/init/init.cpp中。
在main函数中,按顺序主要进行了以下工作:
一、判断及增加环境变量
int main(int argc, char** argv)
//根据参数,判断是否需要启动的ueventd和watchdogd
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);
//若紧急重启,则安装对应的消息处理器
if (REBOOT_BOOTLOADER_ON_PANIC)
install_reboot_signal_handlers();
//增加环境变量
add_environment("PATH", _PATH_DEFPATH);
二、创建文件系统目录并挂载相关的文件系统
//第一次进入时,is_first_stage为true,对应于初始化的第一阶段
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage)
//用于记录启动时间
boot_clock::time_point start_time = boot_clock::now();
//清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。
umask(0);
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest.
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
//这个是8.0新增的, 收紧了cmdline目录的权限
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline", 0440);
//8.0增加了个用户组
gid_t groups[] = AID_READPROC ;
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
//以下这部分也是8.0新增的
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
//提前创建了kmsg设备节点文件,用于输出log信息
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
...............
如上所示,该部分主要用于创建和挂载启动所需的文件目录。
需要注意的是,在编译Android系统源码时,在生成的根文件系统中,
并不存在这些目录,它们是系统运行时的目录,即当系统终止时,就会消失。
在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs这4类文件系统。
tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,
如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。
tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。
tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。
由于tmpfs是驻留在RAM的,因此它的内容是不持久的。
断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。
只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,
通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。
它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,
它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
三、屏蔽标准的输入输出并设置Kernel logger
if (is_first_stage)
.............
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
InitKernelLogging(argv);
前文生成/dev目录后,init进程将调用InitKernelLogging函数,屏蔽标准的输入输出。
InitKernelLogging函数会将标准输入、标准输出、标准错误输出全部重定向到__null__设备中,
然后设置Kernel logger。
3.1 屏蔽标准的输入输出
InitKernelLogging函数定义于system/core/init/log.cpp中。
void InitKernelLogging(char* argv[])
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/sys/fs/selinux/null", O_RDWR);
//若开启失败,则记录log
if (fd == -1)
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger, RebootAborter);
errno = saved_errno;
PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) close(fd);
android::base::InitLogging(argv, &android::base::KernelLogger, RebootAborter);
这里需要说明的是,dup2函数的作用是用来复制一个文件的描述符,
通常用来重定向进程的stdin、stdout和stderr。
它的函数原形是:
int dup2(int oldfd, int targetfd)
该函数执行后,targetfd将变成oldfd的复制品。
因此上述过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上。
因此init进程调用InitKernelLogging函数后,通过标准的输入输出无法输出信息。
3.2 设置kernel logger
我们来跟进一下InitLogging函数,该函数定义于system/core/base/logging.cpp中。
//此处设置的是KernelLogger
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter)
//设置logger
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));
if (gInitialized)
return;
gInitialized = true;
............
const char* tags = getenv("ANDROID_LOG_TAGS");
if (tags == nullptr)
return;
//根据TAG决定最小记录等级
std::vector<std::string> specs = Split(tags, " ");
for (size_t i = 0; i < specs.size(); ++i)
// "tag-pattern:[vdiwefs]"
std::string spec(specs[i]);
if (spec.size() == 3 && StartsWith(spec, "*:"))
switch (spec[2])
case 'v':
gMinimumLogSeverity = VERBOSE;
continue;
...........
.............
当需要输出日志时,KernelLogger函数就会被调用:
#if defined(__linux__)
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
const char* tag, const char*, unsigned int, const char* msg)
............
//打开log节点
static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
if (klog_fd == -1) return;
//决定log等级
int level = kLogSeverityToKernelLogLevel[severity];
char buf[1024];
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s\\n", level, tag, msg);
if (size > sizeof(buf))
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\\n",
level, tag, size);
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = size;
//通过iovec将log发送到dev/kmsg
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
#endif
四、挂载一些分区设备
我们继续回到init进程的main函数:
if (is_first_stage)
.........
//挂载特定的分区设备
if (!DoFirstStageMount())
LOG(ERROR) << "Failed to mount required partitions early ...";
//panic会尝试reboot
panic();
.........
我们跟进DoFirstStageMount看看:
// Mounts /system, /vendor, and/or /odm if they are present in the fstab provided by device tree.
bool DoFirstStageMount()
// Skips first stage mount if we're in recovery mode.
if (IsRecoveryMode())
LOG(INFO) << "First stage mount skipped (recovery mode)";
return true;
// Firstly checks if device tree fstab entries are compatible.
if (!is_android_dt_value_expected("fstab/compatible", "android,fstab"))
LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
return true;
//满足上述条件时,就会调用FirstStageMount的DoFirstStageMount函数
std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
if (!handle)
LOG(ERROR) << "Failed to create FirstStageMount";
return false;
//主要是初始化特定设备并挂载
return handle->DoFirstStageMount();
五、安全相关的初始化工作
接下来,init进程会进行一些安全相关的工作:
if (is_first_stage)
........
//此处应该是初始化安全框架:Android Verified Boot
//AVB主要用于防止系统文件本身被篡改,还包含了防止系统回滚的功能,
//以免有人试图回滚系统并利用以前的漏洞
SetInitAvbVersionInRecovery();
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true);
.......
AVB相关的信息目前还不太了解,不做深入分析。
此处,我们看看selinux_initialize相关的工作:
static void selinux_initialize(bool in_kernel_domain)
Timer t;
selinux_callback cb;
//用于打印log的回调函数
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
//用于检查权限的回调函数
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
//从注释来看,init进程的运行是区分用户态和内核态的
//first_stage运行在内核态
if (in_kernel_domain)
LOG(INFO) << "Loading SELinux policy";
//用于加载sepolicy文件
//该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件,后续的MAC才能开展起来。
if (!selinux_load_policy())
panic();
//内核中读取的信息
bool kernel_enforcing = (security_getenforce() == 1);
//命令行中得到的数据
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing)
//用于设置selinux的工作模式。selinux有两种工作模式:
//1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
//2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式
if(security_setenforce(is_enforcing))
........
//将重启进入recovery mode
security_failure();
if (!write_file("/sys/fs/selinux/checkreqprot", "0"))
security_failure();
// init's first stage can't set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration_ms()).c_str(), 1);
else
//在second stage调用时,初始化所有的handle
selinux_init_all_handles();
六、第一阶段收尾工作
现在我们再来看看init过程第一阶段的收尾工作:
if (is_first_stage)
...........
// We're in the kernel domain, so re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
//按selinux policy要求,重新设置init文件属性
if (restorecon("/init") == -1)
PLOG(ERROR) << "restorecon failed";
//失败的话会reboot
security_failure();
//设置变量INIT_SECOND_STAGE为true
setenv("INIT_SECOND_STAGE", "true", 1);
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
//记录初始化时的时间
setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);
char* path = argv[0];
char* args[] = path, nullptr ;
//再次调用init的main函数,启动用户态的init进程
execv(path, args);
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(ERROR) << "execv(\\"" << path << "\\") failed";
// 内核态的进程不应该退出,若退出则会重启
security_failure();
第一阶段结束时,会复位一些信息,并设置一些环境变量,
最后启动用户态的用户态的init进程,进入init阶段二。
我们在下一篇博客继续分析init阶段二相关的流程。
以上是关于Android O: init进程启动流程分析(阶段一)的主要内容,如果未能解决你的问题,请参考以下文章