本文根据 AOSP android-10.0.0_r41 版别讲解,内核版别 android-goldfish-4.14-gchips
在上一节,咱们知道了体系的整个发动流程,对于 Framework 层,咱们首要关心应用层的整个流程。
1.内核发动 init 进程
内核完结发动后,会经过 run_init_process
函数,发动用户空间的首个进程 init:
// 模拟器内核源码
// init/main.c
static int __ref kernel_init(void *unused)
{
// ......
// ramdisk_execute_command = "/init"
// 在根文件体系中找可履行文件 /init,假如有就履行它
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)n",
ramdisk_execute_command, ret);
}
//......
// 假如 /init 可履行文件没有,就找其他地方的 init 可履行文件,并履行它
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
内核会在各个路径寻觅 init 可履行文件,并履行它。我手上刷了 AOSP android-10.0.0_r41 的 pixel4 其 init 程序预制在 /init
。
2.init 进程
init 可履行文件对应源码在 system/core/init/main.cpp
:
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#endif
// 可履行文件名为 ueventd
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (argc > 1) {
// 榜首个参数为 subcontext
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
}
// 榜首个参数为 selinux_setup
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// 榜首个参数为 second_stage
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 没有参数履行这个函数
return FirstStageMain(argc, argv);
}
这儿会检测 main 函数的参数,上一节中咱们看到内核发动 init 进程时,没有带参数,所以这儿会直接履行到 FirstStageMain
。
3.init 进程榜首阶段 FirstStageMain 函数剖析
榜首阶段 FirstStageMain 函数的作业首要是挂载一些虚拟文件体系,还有挂载两个体系最中心的分区 system, vendor。
这两步都是为了后续的作业做准备。比如设置 selinux 需要从 system, vendor 分区中读取 sepolicy。 SecondStageMain 需要从 system, vendor 分区中读取 property 信息,需要发动体系中心服务。
3.1 FirstStageMain 函数榜首部分
FirstStageMain 函数很长,咱们先看榜首部分:
// system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
// init crash 时重启引导加载程序
// 这个函数首要效果将各种信号量,如 SIGABRT,SIGBUS 等的行为设置为 SA_RESTART,一旦监听到这些信号即履行重启体系
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
boot_clock::time_point start_time = boot_clock::now();
std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x)
if (x != 0) errors.emplace_back(#x " failed", errno);
InstallRebootSignalHandlers()
将各种信号量,如 SIGABRT,SIGBUS 等的行为设置为 SA_RESTART,一旦监听到这些信号即履行重启体系:
void InstallRebootSignalHandlers() {
// Instead of panic'ing the kernel as is the default behavior when init crashes,
// we prefer to reboot to bootloader on development builds, as this will prevent
// boot looping bad configurations and allow both developers and test farms to easily
// recover.
// 当init溃散时,不会像默许行为那样惊惧内核,咱们更喜欢在开发构建时重启到bootloader,由于这将防止引导过错的配置,让开发人员和测验人员都能够轻松地进行配置康复。
struct sigaction action;
memset(&action, 0, sizeof(action));
sigfillset(&action.sa_mask);
action.sa_handler = [](int signal) {
// These signal handlers are also caught for processes forked from init, however we do not
// want them to trigger reboot, so we directly call _exit() for children processes here.
// 从 init 派生的进程也会捕获这些信号处理程序,但咱们不会期望它们触发重启,所以咱们在这儿直接对子进程调用_exit()
if (getpid() != 1) {
_exit(signal);
}
// Calling DoReboot() or LOG(FATAL) is not a good option as this is a signal handler.
// RebootSystem uses syscall() which isn't actually async-signal-safe, but our only option
// and probably good enough given this is already an error case and only enabled for
// development builds.
// 调用DoReboot()或LOG(FATAL)不是一个好的挑选,由于这是一个信号处理程序。重启体系运用syscall(),这实际上不是异步信号安全的,但咱们唯一的挑选,考虑到这已经是一个过错状况,而且只在开发构建时启用,这可能已经足够了。
InitFatalReboot();
};
action.sa_flags = SA_RESTART;
sigaction(SIGABRT, &action, nullptr);
sigaction(SIGBUS, &action, nullptr);
sigaction(SIGFPE, &action, nullptr);
sigaction(SIGILL, &action, nullptr);
sigaction(SIGSEGV, &action, nullptr);
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, &action, nullptr);
#endif
sigaction(SIGSYS, &action, nullptr);
sigaction(SIGTRAP, &action, nullptr);
}
3.2 FirstStageMain 函数第二部分
// Clear the umask.
umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 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.
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));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
// This is needed for log wrapper, which gets called before ueventd runs.
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
// These below mounts are done in first stage init so that first stage mount can mount
// subdirectories of /mnt/{vendor,product}/. Other mounts, not required by first stage mount,
// should be done in rc files.
// Mount staging areas for devices managed by vold
// See storage config details at http://source.android.com/devices/storage/
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
// /mnt/vendor is used to mount vendor-specific partitions that can not be
// part of the vendor partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that can not be
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// /apex is used to mount APEXes
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
这部分内容首要是挂载分区,创立设备节点和一些要害目录。
挂载的文件体系首要有四类:
- tmpfs: 一种虚拟内存文件体系,它会将所有的文件存储在内存中。由于 tmpfs 是驻留在 RAM 的,因而它的内容是不持久的。断电后,tmpfs 的内容就消失了,这也是被称作 tmpfs 的根本原因。
- devpts: 为伪终端提供了一个规范接口,它的规范挂接点是
/dev/pts
。只要 pty(pseudo-tty, 虚拟终端)的主复合设备 /dev/ptmx 被翻开,就会在 /dev/pts 下动态的创立一个新的 pty 设备文件。 - proc: 也是一个虚拟文件体系,它能够看作是内核内部数据结构的接口,经过它咱们能够获得体系的信息,一起也能够在运行时修改特定的内核参数。
- sysfs: 与 proc 文件体系类似,也是一个不占有任何磁盘空间的虚拟文件体系。它一般被挂接在 /sys 目录下。
3.3 FirstStageMain 函数第三部分
SetStdioToDevNull(argv);
其完成如下:
// SetStdioToDevNull 函数的效果是将规范输入(stdin)、规范输出(stdout)和规范过错(stderr)重定向到特殊的设备文件 "/dev/null"
void SetStdioToDevNull(char** argv) {
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/dev/null", O_RDWR);
if (fd == -1) {
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
errno = saved_errno;
PLOG(FATAL) << "Couldn't open /dev/null";
}
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);
}
这儿首要是把 stdin stdout stderr 重定向到 /dev/null
,也便是说 print 函数是看不到输出的。
3.4 FirstStageMain 函数第四部分
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
// Android体系 init 进程发动的时分,log 子体系没有发动起来, 可是咱们依然能够能够运用 logcat -b kernel 看到 init 进程的日志, 这是怎样做到的呢?其实是经过日志重定向来完结的
InitKernelLogging(argv);
//.....
LOG(INFO) << "init first stage started!";
Android 体系 init 进程发动的时分,log 子体系没有发动起来, 可是咱们依然能够能够运用 logcat -b kernel 看到 init 进程的日志, 这是怎样做到的呢?其实是经过日志重定向来完结的。
咱们先看看 InitKernelLogging
函数的具体完成:
void InitKernelLogging(char** argv) {
SetFatalRebootTarget(); // 空完成
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
}
接着调用 InitLogging
:
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));
if (gInitialized) {
return;
}
gInitialized = true;
// Stash the command line for later use. We can use /proc/self/cmdline on
// Linux to recover this, but we don't have that luxury on the Mac/Windows,
// and there are a couple of argv[0] variants that are commonly used.
if (argv != nullptr) {
SetDefaultTag(basename(argv[0]));
}
const char* tags = getenv("ANDROID_LOG_TAGS");
if (tags == nullptr) {
return;
}
// 根据日志内容,设置 gMinimumLogSeverity 的值
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;
case 'd':
gMinimumLogSeverity = DEBUG;
continue;
case 'i':
gMinimumLogSeverity = INFO;
continue;
case 'w':
gMinimumLogSeverity = WARNING;
continue;
case 'e':
gMinimumLogSeverity = ERROR;
continue;
case 'f':
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
continue;
// liblog will even suppress FATAL if you say 's' for silent, but that's
// crazy!
case 's':
gMinimumLogSeverity = FATAL_WITHOUT_ABORT;
continue;
}
}
LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
<< ")";
}
}
- 调用 SetLogger 和 SetAborter 来设置两个变量的值
- 根据日志内容,设置 gMinimumLogSeverity 的值
// 调用进程
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));
// 具体完成
// system/core/base/logging.cpp
void SetLogger(LogFunction&& logger) {
std::lock_guard<std::mutex> lock(LoggingLock());
Logger() = std::move(logger);
}
void SetAborter(AbortFunction&& aborter) {
std::lock_guard<std::mutex> lock(LoggingLock());
Aborter() = std::move(aborter);
}
static LogFunction& Logger() {
#ifdef __ANDROID__
static auto& logger = *new LogFunction(LogdLogger());
#else
static auto& logger = *new LogFunction(StderrLogger);
#endif
return logger;
}
static AbortFunction& Aborter() {
static auto& aborter = *new AbortFunction(DefaultAborter);
return aborter;
}
能够看出,这儿便是给两个静态变量赋值。
咱们要点重视下 SetLogger 函数设置的 KernelLogger:
#if defined(__linux__)
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
const char* tag, const char*, unsigned int, const char* msg) {
// clang-format off
static constexpr int kLogSeverityToKernelLogLevel[] = {
[android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
// level)
[android::base::DEBUG] = 7, // KERN_DEBUG
[android::base::INFO] = 6, // KERN_INFO
[android::base::WARNING] = 4, // KERN_WARNING
[android::base::ERROR] = 3, // KERN_ERROR
[android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
[android::base::FATAL] = 2, // KERN_CRIT
};
// clang-format on
static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
static int klog_fd = OpenKmsg();
if (klog_fd == -1) return;
int level = kLogSeverityToKernelLogLevel[severity];
// The kernel's printk buffer is only 1024 bytes.
// TODO: should we automatically break up long lines into multiple lines?
// Or we could log but with something like "..." at the end?
char buf[1024];
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %sn", level, tag, msg);
if (size > sizeof(buf)) {
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printkn",
level, tag, size);
}
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = size;
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
}
#endif
KernelLogger 是一个函数,其内部完成便是翻开 /dev/dmsg
,拿到 fd,然后把日志信息格式化一下,接着把日志 write 到前面拿到的 fd 中。
在 init 中一般运用 LOG 宏来写日志:
#define LOG(severity) LOG_TO(DEFAULT, severity)
#define LOG_TO(dest, severity) LOGGING_PREAMBLE(severity) && LOG_STREAM_TO(dest, severity)
#define LOG_STREAM_TO(dest, severity)
::android::base::LogMessage(__FILE__, __LINE__, ::android::base::dest,
SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1)
.stream()
LOG 宏最终会构建一个 LogMessage 对象,在 LogMessage 的析构函数中会调用上面的 KernelLogger 函数写日志到 /dev/dmsg 中。
LogMessage::~LogMessage() {
// Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
if (!WOULD_LOG(data_->GetSeverity())) {
return;
}
// Finish constructing the message.
if (data_->GetError() != -1) {
data_->GetBuffer() << ": " << strerror(data_->GetError());
}
std::string msg(data_->ToString());
if (data_->GetSeverity() == FATAL) {
#ifdef __ANDROID__
// Set the bionic abort message early to avoid liblog doing it
// with the individual lines, so that we get the whole message.
android_set_abort_message(msg.c_str());
#endif
}
{
// Do the actual logging with the lock held.
std::lock_guard<std::mutex> lock(LoggingLock());
if (msg.find('n') == std::string::npos) {
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
data_->GetTag(), msg.c_str());
} else {
msg += 'n';
size_t i = 0;
while (i < msg.size()) {
size_t nl = msg.find('n', i);
msg[nl] = '';
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetId(), data_->GetSeverity(),
data_->GetTag(), &msg[i]);
// Undo the zero-termination so we can give the complete message to the aborter.
msg[nl] = 'n';
i = nl + 1;
}
}
}
// Abort if necessary.
if (data_->GetSeverity() == FATAL) {
Aborter()(msg.c_str());
}
}
代码很多,中心便是调用 LogLine 来打印日志:
void LogMessage::LogLine(const char* file, unsigned int line, LogId id, LogSeverity severity,
const char* tag, const char* message) {
if (tag == nullptr) {
std::lock_guard<std::recursive_mutex> lock(TagLock());
if (gDefaultTag == nullptr) {
gDefaultTag = new std::string(getprogname());
}
Logger()(id, severity, gDefaultTag->c_str(), file, line, message);
} else {
Logger()(id, severity, tag, file, line, message);
}
}
这儿就会经过 logger 函数获取到咱们前面设置的 KernelLogger,然后调用它,把日志信息写入到 /dev/dmsg
中。
在析构函数中来打印日志,能够有用的避免打印日志对 init 发动时间的影响。
3.5 FirstStageMain 函数第五部分
咱们接着看 FirstStageMain 函数第五部分:
// ......
/*
* 首要效果是去解析 fstab 文件,
* 然后得到 "/system", "/vendor", "/odm" 三个目录的挂载信息
* 装载 fstab 中指定的分区
*/
if
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
bool DoFirstStageMount() {
// Skips first stage mount if we're in recovery mode.
if (IsRecoveryMode()) {
LOG(INFO) << "First stage mount skipped (recovery mode)";
return true;
}
// AVB校验
std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
if (!handle) {
LOG(ERROR) << "Failed to create FirstStageMount";
return false;
}
return handle->DoFirstStageMount();
}
先看榜首点 auto fsm = FirstStageMount::Create();,该方法首要用于AVB校验,AVB校验能够去看看 google 文档:
std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
// 读取 fstab, file system table,里边包含了要挂载的逻辑分区
auto fstab = ReadFirstStageFstab();
// 判断device tree(fstabl)中是否有vbmeta/compatible结构,值是android,vbmeta
// 创立FirstStageMountVBootV1或许FirstStageMountVBootV2实例,取决于
// IsDtVbmetaCompatible(fstab)的返回值,假如支持vbmeta,则运用FirstStageMountVBootV2,反之FirstStageMountVBootV1
if (IsDtVbmetaCompatible(fstab)) {
return std::make_unique<FirstStageMountVBootV2>(std::move(fstab));
} else {
return std::make_unique<FirstStageMountVBootV1>(std::move(fstab));
}
}
以上首要是创立 V1 或许 V2 版别的 AVB 校验,AV B校验首要是针对分区进行校验,对于要发动的 Android 版别中包含的所有可履行代码和数据,发动时验证均要求在运用前以加密方式对其进行验证。包括内核(从 boot 分区加载)、设备树(从 dtbo 分区加载)、system 分区和 vendor 分区等
- 对于 boot 和 dtbo 这类仅读取一次的小分区,一般是经过将整个内容加载到内存中,然后计算其哈希值来进行验证
- 内存装不下的较大分区(如文件体系)可能会运用哈希树;
- 假如在某个时刻计算出的根哈希值与预期根哈希值不一致,体系便不会运用相应数据,无法发动 Android 体系
咱们继续看 handle->DoFirstStageMount()
的完成:
bool FirstStageMount::DoFirstStageMount() {
if (!IsDmLinearEnabled() && fstab_.empty()) {
// Nothing to mount.
LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
return true;
}
if (!InitDevices()) return false;
if (!CreateLogicalPartitions()) return false;
if (!MountPartitions()) return false;
return true;
}
这儿的逻辑很明晰,初始化设备,创立逻辑分区,最终挂载分区。
里边具体的完成咱们就不在剖析了,这个不是咱们重视的要点。
3.6 FirstStageMain 函数第六部分
// .....
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(FATAL) << "execv("" << path << "") failed";
return 1;
}
最终部分,经过 selinux_setup
参数,execv 再次履行 init 可履行文件,进入 init 进程的第二阶段。