上一篇中讲了 init 进程启动的第一阶段,也就是 init 进程的内核态执行,主要包含的工作内容有:
- 挂载分区 dev、system、vendor 等
- 创建设备节点及设备节点热插拔事件监听处理 (ueventd)
- 创建一些关键目录、初始化日志输出系统
- 启用 SELinux 安全策略
本章节的内容为 init 进程第二阶段执行的内容,也称为 init 进程的用户态执行。
一. 创建进程会话密钥
init 进程第二阶段执行时,判断 INIT_SECOND_STAGE 的值,直接进入第二阶段的内容
文件路径:source/system/core/init/init.cpp
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(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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
1.1 keyctl_get_keyring_ID
这里使用到的是内核提供给用户空间使用的 密钥保留服务 (key retention service),它的主要意图是在 Linux 内核中缓存身份验证数据。远程文件系统和其他内核服务可以使用这个服务来管理密码学、身份验证标记、跨域用户映射和其他安全问题。它还使 Linux 内核能够快速访问所需的密钥,并可以用来将密钥操作(比如添加、更新和删除)委托给用户空间。
详细的介绍可以点击 Linux 密钥保留服务入门
// 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);
key_serial_t keyctl_get_keyring_ID(key_serial_t id, int create) {
return keyctl(KEYCTL_GET_KEYRING_ID, id, create);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
新建进程会话密钥环,其中
- KEYCTL_GET_KEYRING_ID:表示通过第二个参数的类型获取当前进程的密钥信息
- KEY_SPEC_SESSION_KEYRING:表示获取当前进程的 SESSION_KEYRING
- 1:表示如果获取不到就新建一个会话密钥环
static inline long keyctl(int cmd, ...) {
va_list va;
unsigned long arg2, arg3, arg4, arg5;
va_start(va, cmd);
arg2 = va_arg(va, unsigned long);
arg3 = va_arg(va, unsigned long);
arg4 = va_arg(va, unsigned long);
arg5 = va_arg(va, unsigned long);
va_end(va); //va_end 恢复堆栈
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
可以看到程序最后通过 syscall 与系统内核的通讯密钥管理工具交互
二. 初始化属性服务
文件路径:source/system/core/init/init.cpp
// Indicate that booting is in progress to background fw loaders, etc.
// 创建 /dev/.booting 节点,表明当前正在开机过程中
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
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();
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);
// Clean up our environment.
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
属性服务是 Android 系统中非常重要的一个模块,这里另外在一篇笔记中属性服务相关的分析
2.1 process_kernel_dt
读取设备树(DT)上的属性设置信息,在 android 8.1 系统中 device-tree 的目录为 /proc/device-tree/firmware/android/compatible
查找系统属性,然后通过 property_set 设置系统属性
文件路径:source/system/core/init/init.cpp
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
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
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
2.2 process_kernel_cmdline
android 8.1 中 kernel 的 cmdline 启动参数文件为 /proc/cmdline 中
该函数解析 kernel 的 cmdline 文件提取以 “androidboot.” 字符串打头的字符串,通过 property_set 设置该系统属性
qemu 用于区分启动 android 的设备是否为模拟器,如果是模拟器的话,需要设置的系统属性以 “ro.kernel.” 打头。
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) {
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("ro.kernel." + key, value);
return;
}
if (key == "qemu") {
strlcpy(qemu, value.c_str(), sizeof(qemu));
} else if (android::base::StartsWith(key, "androidboot.")) {
property_set("ro.boot." + key.substr(12), value);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
2.3 export_kernel_boot_props
这个函数额外设置一些属性,在处理 dt 属性和 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.c_str() : prop_map[i].default_value);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
三. 进行 SELinux 第二阶段并恢复一些文件安全上下文
// Now set up SELinux for second stage.
selinux_initialize(false);
selinux_restore_context();
- 1
- 2
- 3
3.1 selinux_initialize
static void selinux_initialize(bool in_kernel_domain) {
... //和之前一样设置回调函数
if (in_kernel_domain) {//第二阶段跳过
...
} else {
selinux_init_all_handles();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
前面 init 内核态执行初始化了 selinux,在用户态执行主要是调用 selinux_init_all_handles 函数初始化 file_contexts 和 property_contexts 文件 handle。
根据 android 8.1 安全机制 — SEAndroid & SELinux 博客中的描述,init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询 file_contexts & property_contexts 文件中的安全上下文。
以系统属性为例,当其他进程通过 socket 与系统属性通信时请求访问某一项系统属性的值时,属性服务系统会通过 libselinux 提供的 selabel_lookup 函数到 property_contexts 文件中查找要访问属性的安全上下文,有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。
所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。
struct selabel_handle* selinux_android_file_context_handle(void)
{
if (selinux_android_opts_file_exists(seopts_file_split)) {
return selinux_android_file_context(seopts_file_split,
ARRAY_SIZE(seopts_file_split));
} else {
return selinux_android_file_context(seopts_file_rootfs,
ARRAY_SIZE(seopts_file_rootfs));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3.2 selinux_restore_context
这个函数恢复指定文件的安全上下文,因为这些文件是在 SELinux 安全机制初始化前创建,所以需要重新恢复下安全性
static void selinux_restore_context() {
LOG(INFO) << "Running restorecon...";
selinux_android_restorecon("/dev", 0);
selinux_android_restorecon("/dev/kmsg", 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("/plat_property_contexts", 0);
selinux_android_restorecon("/nonplat_property_contexts", 0);
selinux_android_restorecon("/plat_seapp_contexts", 0);
selinux_android_restorecon("/nonplat_seapp_contexts", 0);
selinux_android_restorecon("/plat_service_contexts", 0);
selinux_android_restorecon("/nonplat_service_contexts", 0);
selinux_android_restorecon("/plat_hwservice_contexts", 0);
selinux_android_restorecon("/nonplat_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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
四. 初始化子进程终止信号处理函数
epoll_fd = epoll_create1(EPOLL_CLOEXEC); // 创建 epoll 实例,返回 epoll 文件描述符
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
//主要是创建 handler 处理子进程终止信号,创建一个匿名 socket 并注册到 epoll 进行监听
signal_handler_init();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
init 是一个守护进程,为了防止 init 的子进程成为僵尸进程(zombie process),
需要 init 在子进程结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,
防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)
在 linux 当中,父进程是通过捕捉 SIGCHLD 信号来得知子进程运行结束的情况,SIGCHLD 信号会在子进程终止的时候发出。
4.1 epoll_create1
epoll 是 Linux 内核为处理大批量文件描述符事件触发而引入的,在 android init 代码中主要用于处理子进程的终止信号。
相对于 select 事件触发,取消了fd 轮询处理方式,将用户关心的文件描述符事件放在内核里的一个事件表中,因此 epoll 效率更高。
详情可以参考博客:linux 高并发事件触发处理 — epoll
epoll_create1 函数返回内核事件表的文件描述符,后续操作都是基于该内核事件表
4.2 signal_handler_init
void signal_handler_init() {
// Create a signalling mechanism for SIGCHLD.
int s[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
PLOG(ERROR) << "socketpair failed";
exit(1);
}
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));
act.sa_handler = SIGCHLD_handler;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0);
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
register_epoll_handler(signal_read_fd, handle_signal);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这个函数主要的作用是注册 SIGCHLD 信号的处理函数。
首先,调用 socketpair 获取一对文件描述符,这样当一端写入时,另一端就能被通知到,socketpair 两端既可以写也可以读,这里只是单向的让 s[0] 写,s[1] 读
然后,新建一个 sigaction 结构体,成员变量 sa_handler 是信号处理函数,指向 SIGCHLD_handler,SIGCHLD_handler 做的事情就是往 s[0] 中写一个”1”,这样 s[1] 就会收到通知。 成员变量 sa_flags 是指要处理的信号,这里的 SA_NOCLDSTOP 表示只有在子进程终止时处理。
sigaction(SIGCHLD, &act, 0) 是信号绑定关系,也就是当监听到 SIGCHLD 信号时,由 act 这个 sigaction 结构体进行处理。
整体示意图如下:
简单的缕一下过程就是
(1) init 进程接收到 SIGCHLD 信号
通过 sigaction 函数将信号处理过程转移到 sigaction 结构体
(2) sigaction 成员变量 sa_flags 另外指定所关心的具体信号为 SA_NOCLDSTOP,也就是子进程终止信号
成员变量 sa_handler 表明当子进程终止时,通过 SIGCHLD_handler 函数处理。
(3) SIGCHLD_handler 信号处理函数中通过 s[0] 文件描述符写了一个"1",
由于 socketpair 的特性,s[1] 能接读到该字符串。
(4) 通过 register_epoll_handler 将 s[1] 注册到 epoll 内核事件表中
handle_signal 是 s[1] 有数据到来时的处理函数 (后文说明)
(5) handle_signal: ReapAnyOutstandingChildren (后文说明)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4.3 register_epoll_handler
void register_epoll_handler(int fd, void (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = reinterpret_cast<void*>(fn);
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
PLOG(ERROR) << "epoll_ctl failed";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
epoll_ctl 是 epoll 的事件注册函数,用来操作内核事件表。fd 是上文 socketpair 创建的 s[1] 文件描述符,用于读 s[0] 写入的数据。
该段代码中 epoll_event ev 表示:事件类型为 EPOLLIN(fd 可读)时,data.ptr 事件处理函数为 fn。
通过 epoll_ctl 函数,EPOLL_CTL_ADD 表示添加注册到内核事件表。
4.4 ReapAnyOutstandingChildren
如下所示在 epoll 的触发事件中,调用 ReapAnyOutstandingChildren 进行最终处理。
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
read(signal_read_fd, buf, sizeof(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
ReapOneProcess 函数先用 waitpid 找出挂掉进程的 pid,然后根据 pid 找到对应 Service,最后调用 Service 的 Reap 方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程。
void ServiceManager::ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
bool ServiceManager::ReapOneProcess() {
siginfo_t siginfo = {};
// 用 waitpid 函数获取状态发生变化的子进程 pid
// waitpid 的标记为 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)); });
if (PropertyChildReap(pid)) {
return true;
}
// 通过 pid 找到对应的 Service
Service* svc = FindServiceByPid(pid);
std::string name;
std::string wait_string;
if (svc) {
name = StringPrintf("Service '%s' (pid %d)", svc->name().c_str(), pid);
if (svc->flags() & SVC_EXEC) {
wait_string = StringPrintf(" waiting took %f seconds",
exec_waiter_->duration().count() / 1000.0f);
}
} else {
name = StringPrintf("Untracked pid %d", pid);
}
auto status = siginfo.si_status;
if (WIFEXITED(status)) {
LOG(INFO) << name << " exited with status " << WEXITSTATUS(status) << wait_string;
} else if (WIFSIGNALED(status)) {
LOG(INFO) << name << " killed by signal " << WTERMSIG(status) << wait_string;
}
if (!svc) {
return true;
}
// 清除子进程相关的资源
svc->Reap();
if (svc->flags() & SVC_EXEC) {
exec_waiter_.reset();
}
if (svc->flags() & SVC_TEMPORARY) {
RemoveService(*svc);
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
4.5 while 循环中的触发处理
while (true) {
...
poll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
最终通过 epoll_wait 获取了具体的子进程终止事件,poll_event 返回具体的触发事件抽象,然后通过 ((void (*)()) ev.data.ptr)() 进行处理。
五. 设置其他一些系统属性
property_load_boot_defaults();
export_oem_lock_status();
set_usb_controller();
- 1
- 2
- 3
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("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
update_sys_usb_config();
}
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
六. 开启系统属性服务
void start_property_service() {
property_set("ro.property_service.version", "2");
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr, sehandle);
if (property_set_fd == -1) {
PLOG(ERROR) << "start_property_service socket creation failed";
exit(1);
}
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
之前的分析中 property_set 可以轻松设置系统属性,那为什么这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性,
Android 将属性的设置统一交由 init 进程管理,其他进程不能直接修改属性,而只能通知 init 进程来修改,而在这过程中,init 进程可以进行权限检测控制,决定是否允许修改。
首先创建一个 socket 并返回文件描述符,然后设置最大并发数为 8,其他进程可以通过这个 socket 通知 init 进程修改系统属性。
最后注册 epoll 事件,也就是当监听到 property_set_fd 改变时调用 handle_property_set_fd。
6.1 handle_property_set_fd
这个函数主要作用是建立 socket 连接,然后从 socket 中读取操作信息,根据不同的操作类型,调用 handle_property_set 做具体的操作
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;
}
struct ucred cr;
socklen_t cr_size = sizeof(cr); // 获取操作进程凭证
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
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;
}
// 根据操作类型信息,执行对应处理,两者区别一个是以 char 形式读取,一个以 String 形式读取
switch (cmd) {
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;
handle_property_set(socket, prop_value, prop_value, true);
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;
}
handle_property_set(socket, name, value, false);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
6.2 handle_property_set
这就是最终的处理函数,以”ctl.”开头的 key 就做一些 Service 的 Start、Stop、Restart 操作,其他的就是调用 property_set 进行属性设置,
不管是前者还是后者,都要进行 SELinux 安全性检查,只有该进程有操作权限才能执行相应操作
static void handle_property_set(SocketConnection& socket,
const std::string& name,
const std::string& value,
bool legacy_protocol) {
const char* cmd_name = legacy_protocol ? "PROP_MSG_SETPROP" : "PROP_MSG_SETPROP2";
if (!is_legal_property_name(name)) {
LOG(ERROR) << "sys_prop(" << cmd_name << "): illegal property name \"" << name << "\"";
socket.SendUint32(PROP_ERROR_INVALID_NAME);
return;
}
struct ucred cr = socket.cred();
char* source_ctx = nullptr;
getpeercon(socket.socket(), &source_ctx);
if (android::base::StartsWith(name, "ctl.")) {
if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
handle_control_message(name.c_str() + 4, value.c_str());
if (!legacy_protocol) {
socket.SendUint32(PROP_SUCCESS);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
<< " service ctl [" << value << "]"
<< " uid:" << cr.uid
<< " gid:" << cr.gid
<< " pid:" << cr.pid;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
}
}
} else {
if (check_mac_perms(name, source_ctx, &cr)) {
uint32_t result = property_set(name, value);
if (!legacy_protocol) {
socket.SendUint32(result);
}
} else {
LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
if (!legacy_protocol) {
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
}
}
}
freecon(source_ctx);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
操作流程示意:
1. 通过 is_legal_property_name 检测是否是合法的属性名
2. 通过 getpeercon 函数获取进程的安全上下文以及进程凭证,用于后面的 selinux 权限检测
3. 如果以 “ctl.” 打头的属性名表明是控制命令,使用 handle_control_message 进行处理,否则使用 property_set 进行常规属性设置。
4. 无论是控制命令还是常规属性设置操作之前,都要进行权限检测,根据前面 getpeercon 获取的进程安全上下文分别调用 check_control_mac_perms 和 check_mac_perms 判断进程有没有对应的权限进行操作。
七. init.rc 文件解析 & 处理
7.1 init.rc 配置文件简介
init.rc 是一个可配置的初始化文件,在 Android 中被用作程序的启动脚本,它是”run commands”运行命令的缩写。
通常定制厂商可以配置额外的初始化配置:init.%PRODUCT%.rc。在 init 的初始化过程中会解析该配置文件,完成定制化的配置过程。
文件结构
init.rc 的基本单位是 section(块),一个 section(块)可以包含多行。
section(块)有三种类型,一类称为 action(行为),一类称为 service(服务),另一类是 import(导入)即引入其他的 rc 文件,下面简单介绍下 action(行为) 和 service(服务)的组织方式。
action
on <trigger>
<command>
<command>
<command>
如:boot trigger 触发时的行为块
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
action 行为块以关键字 on 开始,on 后面紧跟 trigger(触发条件),当触发条件满足时,执行其他行一系列命令的集合。
service
service <name> <pathname> [ <argument> ]*
<option>
<option>
如:zygote 服务的启动配置
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
service 服务块以关键字 service 开始,后面紧跟 service 服务对应的进程可执行文件路径以及要传递的参数。
关键字 service 以下的行称为 option(选项),选项也有很多种。常见的如 class 表示服务所属的类别;class_start 可以同时启动一组服务。
7.2 解析过程分析
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
ActionManager& am = ActionManager::GetInstance();
ServiceManager& sm = ServiceManager::GetInstance();
Parser& parser = Parser::GetInstance();
// 创建 action 行为块 和 service 服务块对应的解析类 parser
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
7.2.1 ParseConfig
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
return ParseConfigDir(path);
}
return ParseConfigFile(path);
}
bool Parser::ParseConfigFile(const std::string& path) {
LOG(INFO) << "Parsing file " << path << "...";
android::base::Timer t;
std::string data;
std::string err;
// 将 path 文件中的内容,保存为字符串形式
if (!ReadFile(path, &data, &err)) {
LOG(ERROR) << err;
return false;
}
data.push_back('\n'); // TODO: fix parse_config.
// 解析获取的字符串
ParseData(path, data);
for (const auto& [section_name, section_parser] : section_parsers_) {
section_parser->EndFile();
}
LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
ParseConfig 函数传入需要解析的 rc 文件的路径,如果是文目录,则遍历该目录取出所有的 rc 文件并调用 ParseConfigFile 函数进行解析,如果是文件路径,则直接调用 ParseConfigFile 函数进行解析。
从上面的代码中可以看出,init 解析 rc 文件的过程中,首先调用 ReadFile 函数将 rc 文件的内容全部保存为字符串,存在 data 中,然后调用 ParseData 进行解析。ParseData 函数会根据关键字解析出服务块和行为块,以链表节点的形式注册到 service_list 与 action_list 中。
下面分析一下 ParseData 函数,根据关键字的不同会调用不同的 parser 去解析,action 行为块使用 ActionParser,而 service 服务块使用 ServiceParser 解析。
7.2.2 ParseData
void Parser::ParseData(const std::string& filename, const std::string& data) {
//TODO: Use a parser with const input and remove this copy
std::vector<char> data_copy(data.begin(), data.end());
data_copy.push_back('\0');
parse_state state;
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
// ①
SectionParser* section_parser = nullptr;
std::vector<std::string> args;
for (;;) {
// ②
switch (next_token(&state)) {
case T_EOF:
if (section_parser) {
section_parser->EndSection();
}
return;
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
//
if (section_parsers_.count(args[0])) {
if (section_parser) {
section_parser->EndSection();
}
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
}
}
args.clear();
break;
case T_TEXT:
// 解析到一个单词,将单词填充到 args vector 向量中
args.emplace_back(state.text);
break;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
标注说明:
① ParseData 会根据不同的 section 类别使用不同的 SectionParser 去解析,在解析 rc 文件之前,init 创建了三种不同类型的 SectionParser。
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
- 1
- 2
- 3
后面根据不同的关键字前缀会使用不同的 SectionParser 解析
if (section_parsers_.count(args[0])) {
...
// section_parsers_ 为 AddSectionParser 注入的 pars 类型
// args[0] 解析到的 section 的第一个单词,也就是 service、on、import 三者之一
section_parser = section_parsers_[args[0]].get();
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
根据 args[0] 关键字前缀决定了要使用的 SectionParser 接可以开始解析了。
② next_token 函数不具体分析,涉及到字符串而已,简单做一下说明。next_token 处理的数据以 parse_state 结构体指针返回,以行为单位分隔传递的字符串,成员变量代表的意义如下:
struct parse_state
{
char *ptr; // 要解析的字符串
char *text; // 解析到的字符串,可以理解为返回一行的数据
int line; // 解析到第行数
int nexttoken; // 解析状态,有 T_EOF、T_NEWLINE、T_TEXT 三种
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其中 T_EOF 表示字符串解析结束,T_NEWLINE 表示解析完一行的数据,T_TEXT 表示解析到一个单词,在代码中会填充到 args vector 向量中,用作后续的解析处理。
下面的代码是解析完某一行的处理代码:
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
// 根据 args[0] 第一个解析到的单词判断是否有对应的 sectionParser
if (section_parsers_.count(args[0])) {
// 即将开始一个新的 section,先调用 EndSection 结束上一次的 section
if (section_parser) {
section_parser->EndSection();
}
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
}
}
args.clear();
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
首先解析完某一行之后,args 中会填充这一行包含的各个单词,有两种情况,以下面的行为块为例
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
- 1
- 2
- 3
- 4
- 5
一种是 [on、boot] 两个单词,暂时表述为关键字行,也是一个 section 开始的标识,后续就要切换对应的 sectionParser 调用 ParseSection 函数开始解析过程。
还有一种是 [hostname、localhost] 两个单词,暂时描述为命令行,包含在上一个 section 过程中,也就可以调用上一个 sectionParser 的 ParseLineSection 函数进行解析。
所以总结一下,一个 section 开始时,首先切换相应的 sectionParser(actionParser & serviceParser),调用 ParseSection 函数处理关键字行,后续命令行使用 ParseLineSection 进行解析,解析完一个 section 之后记得调用 EndSection 结束。
下面分别分析 actionParser 和 serviceParse 的解析,其实 ParseSection 和 ParseLineSection 都是解析 args 参数填充一些链表(_action、_commands、_trigger等)记录所要配置或者执行触发的信息,在 init 程序的最后 while 循环中分别进行相应的配置操作。
7.2.3 ServiceParser 解析服务块
从前面的代码,我们知道,解析一个 section 块,首先需要调用 ParseSection、 函数,接着利用 ParseLineSection 处理子块,解析完所有数据后,调用 EndSection。
-> 上述函数的解析过程就是填充 service section 结构体
首先需要介绍 service.cpp 中 Service 类,Service 类是 init 中对一个具体服务的封装,同时包含 rc 文件中 section service 相关的信息。
首先看一下 ParseSection、ParseLineSection、ParseLineSection 这三个函数。
bool ServiceParser::ParseSection(std::vector<std::string>&& args, const std::string& filename,
int line, std::string* err) {
if (args.size() < 3) {
*err = "services must have a name and a program";
return false;
}
const std::string& name = args[1];
if (!IsValidName(name)) {
*err = StringPrintf("invalid service name '%s'", name.c_str());
return false;
}
Service* old_service = service_manager_->FindServiceByName(name);
if (old_service) {
*err = "ignored duplicate definition of service '" + name + "'";
return false;
}
std::vector<std::string> str_args(args.begin() + 2, args.end());
service_ = std::make_unique<Service>(name, str_args);
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
可以看到,ParseSection 切换 ServiceParser 进行服务块解析,首先判断服务名的合法性,然后通过先前解析 rc 文件传递下来的字符串参数构建了一个 Service 对象。
bool ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) {
return service_ ? service_->ParseLine(std::move(args), err) : false;
}
bool Service::ParseLine(const std::vector<std::string>& args, std::string* err) {
static const OptionParserMap parser_map;
auto parser = parser_map.FindFunction(args, err);
if (!parser) {
return false;
}
return (this->*parser)(args, err);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
ParseLineSection 用于解析除关键字行之外的其它选项行,选项行根据关键字(args[0])找到对应的处理函数进行解析,其他的参数(args[1]~args[n])传递给处理函数。
service section 选项行格式如下:
class main
onrestart restart audioserver
- 1
- 2
其中第一个是关键字 OptionParserMap 的 key,根据 key 找到对应的处理函数,后续的参数是传递给处理函数的。
OptionParserMap 包含 service section 选项行的函数处理信息,FindFunction 函数根据关键字(args[0])找到对应的处理函数。对应信息如下:
using FunctionInfo = std::tuple<std::size_t, std::size_t, Function>;
using Map = std::map<std::string, FunctionInfo>;
static const Map option_parsers = {
{"capabilities",
{1, kMax, &Service::ParseCapabilities}},
{"class", {1, kMax, &Service::ParseClass}},
{"console", {0, 1, &Service::ParseConsole}},
{"critical", {0, 0, &Service::ParseCritical}},
{"disabled", {0, 0, &Service::ParseDisabled}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}},
{"ioprio", {2, 2, &Service::ParseIoprio}},
{"priority", {1, 1, &Service::ParsePriority}},
{"keycodes", {1, kMax, &Service::ParseKeycodes}},
{"oneshot", {0, 0, &Service::ParseOneshot}},
{"onrestart", {1, kMax, &Service::ParseOnrestart}},
{"oom_score_adjust",
{1, 1, &Service::ParseOomScoreAdjust}},
{"memcg.swappiness",
{1, 1, &Service::ParseMemcgSwappiness}},
{"memcg.soft_limit_in_bytes",
{1, 1, &Service::ParseMemcgSoftLimitInBytes}},
{"memcg.limit_in_bytes",
{1, 1, &Service::ParseMemcgLimitInBytes}},
{"namespace", {1, 2, &Service::ParseNamespace}},
{"seclabel", {1, 1, &Service::ParseSeclabel}},
{"setenv", {2, 2, &Service::ParseSetenv}},
{"shutdown", {1, 1, &Service::ParseShutdown}},
{"socket", {3, 6, &Service::ParseSocket}},
{"file", {2, 2, &Service::ParseFile}},
{"user", {1, 1, &Service::ParseUser}},
{"writepid", {1, kMax, &Service::ParseWritepid}},
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
从左到右,参数分别代表:处理关键字、最小参数个数、最大参数个数、处理函数地址。
处理完所有行数之后,调用 EndSection 将创建并填充完成的 Sevice 对象加入到 services_ 链表中。
void ServiceParser::EndSection() {
if (service_) {
service_manager_->AddService(std::move(service_));
}
}
void ServiceManager::AddService(std::unique_ptr<Service> service) {
services_.emplace_back(std::move(service));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
7.2.4 ActionParser 解析行为块
从前面的代码,我们知道,解析一个 section 块,首先需要调用 ParseSection、 函数,接着利用 ParseLineSection 处理子块,解析完所有数据后,调用 EndSection。
-> 上述函数的解析过程就是填充 action section 结构体
首先需要介绍 action.cpp 中 Action 类,Action 类是 init 中对一个具体触发行为的封装,包含了以下几个重要信息:
class Action {
...
std::map<std::string, std::string> property_triggers_;
std::string event_trigger_;
std::vector<Command> commands_;
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
action section 可以理解为,当属性变化或系统进行到程序的某个时候(event_trigger_)时触发 commands_ 向量中的一系列命令信息。ActionParser 的解析过程实际上就是解析填充这些信息。
首先看一下 ParseSection、ParseLineSection、ParseLineSection 这三个函数。
bool ActionParser::ParseSection(std::vector<std::string>&& args, const std::string& filename,
int line, std::string* err) {
std::vector<std::string> triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
*err = "actions must have a trigger";
return false;
}
auto action = std::make_unique<Action>(false, filename, line);
if (!action->InitTriggers(triggers, err)) {
return false;
}
action_ = std::move(action);
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
可以看到,ParseSection 切换 ActionParser 进行行为块解析,首先创建 Action 对象,由于 ParseSection 是解析关键字行,所以后面的操作就是提取触发条件 event_trigger_ 或者 property_triggers_(属性变化时)。
triggers 向量中包含除去关键字 on 之外的其他触发条件信息,传递到 InitTriggers 进行处理。在 InitTriggers 函数中,根据 args[n] 参数中是否包含 “property:” 目标字符串, 判断是属性变化时触发还是某个时间条件触发(boot 或者 laterboot 等)。如果是属性变化时触发则填充 property_triggers_ 成员变量,否则填充 event_trigger_。
bool Action::InitTriggers(const std::vector<std::string>& args, std::string* err) {
const static std::string prop_str("property:");
for (std::size_t i = 0; i < args.size(); ++i) {
if (args[i].empty()) {
*err = "empty trigger is not valid";
return false;
}
if (i % 2) {
if (args[i] != "&&") {
*err = "&& is the only symbol allowed to concatenate actions";
return false;
} else {
continue;
}
}
if (!args[i].compare(0, prop_str.length(), prop_str)) {
// 属性变化时触发,在 ParsePropertyTrigger 函数中填充 property_triggers_
if (!ParsePropertyTrigger(args[i], err)) {
return false;
}
} else {
if (!event_trigger_.empty()) {
*err = "multiple event triggers are not allowed";
return false;
}
event_trigger_ = args[i];
}
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
解析完关键字行,就是调用 ParseLineSection 函数解析其他子块命令行,填充 _commands 向量。
bool ActionParser::ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) {
return action_ ? action_->AddCommand(std::move(args), line, err) : false;
}
bool Action::AddCommand(const std::vector<std::string>& args, int line, std::string* err) {
if (!function_map_) {
*err = "no function map available";
return false;
}
auto function = function_map_->FindFunction(args, err);
if (!function) {
return false;
}
AddCommand(function, args, line);
return true;
}
void Action::AddCommand(BuiltinFunction f, const std::vector<std::string>& args, int line) {
commands_.emplace_back(f, args, line);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
其中,类似于解析服务块,根据关键字 args[0] 通过函数 FindFunction 在 function_map_ 查找对应的处理函数。
function_map_ 中的 map 处理函数信息在 init 代码稍前的地方引入。
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
- 1
- 2
处理函数对应信息如下:
static const Map builtin_functions = {
{"bootchart", {1, 1, do_bootchart}},
{"chmod", {2, 2, do_chmod}},
{"chown", {2, 3, do_chown}},
{"class_reset", {1, 1, do_class_reset}},
{"class_restart", {1, 1, do_class_restart}},
{"class_start", {1, 1, do_class_start}},
{"class_stop", {1, 1, do_class_stop}},
{"copy", {2, 2, do_copy}},
{"domainname", {1, 1, do_domainname}},
{"enable", {1, 1, do_enable}},
{"exec", {1, kMax, do_exec}},
{"exec_start", {1, 1, do_exec_start}},
{"export", {2, 2, do_export}},
{"hostname", {1, 1, do_hostname}},
{"ifup", {1, 1, do_ifup}},
{"init_user0", {0, 0, do_init_user0}},
{"insmod", {1, kMax, do_insmod}},
{"installkey", {1, 1, do_installkey}},
{"load_persist_props", {0, 0, do_load_persist_props}},
{"load_system_props", {0, 0, do_load_system_props}},
{"loglevel", {1, 1, do_loglevel}},
{"mkdir", {1, 4, do_mkdir}},
{"mount_all", {1, kMax, do_mount_all}},
{"mount", {3, kMax, do_mount}},
{"umount", {1, 1, do_umount}},
{"restart", {1, 1, do_restart}},
{"restorecon", {1, kMax, do_restorecon}},
{"restorecon_recursive", {1, kMax, do_restorecon_recursive}},
{"rm", {1, 1, do_rm}},
{"rmdir", {1, 1, do_rmdir}},
{"setprop", {2, 2, do_setprop}},
{"setrlimit", {3, 3, do_setrlimit}},
{"start", {1, 1, do_start}},
{"stop", {1, 1, do_stop}},
{"swapon_all", {1, 1, do_swapon_all}},
{"symlink", {2, 2, do_symlink}},
{"sysclktz", {1, 1, do_sysclktz}},
{"trigger", {1, 1, do_trigger}},
{"verity_load_state", {0, 0, do_verity_load_state}},
{"verity_update_state", {0, 0, do_verity_update_state}},
{"wait", {1, 2, do_wait}},
{"wait_for_prop", {2, 2, do_wait_for_prop}},
{"write", {2, 2, do_write}},
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
从左到右,参数分别代表:处理关键字、最小参数个数、最大参数个数、处理函数地址。
处理完所有行数之后,调用 EndSection 将创建填充的 Action 对象加入到 actions_ 链表中。
void ActionParser::EndSection() {
if (action_ && action_->NumCommands() > 0) {
action_manager_->AddAction(std::move(action_));
}
}
void ActionManager::AddAction(std::unique_ptr<Action> action) {
actions_.emplace_back(std::move(action));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
7.2.5 Action 链表的最终触发处理过程
在前面 ActionParser 解析行为块的分析过程中触发条件被分为属性变化时触发以及到达某个特定时间条件出发。下面的分析以这两个触发条件为分类分别说明处理过程
到达某个特定时间条件
调用相应的 sectionParser 解析完 rc 文件之后,_action 链表也随之被填充完成,_action 链表中包含一些触发条件下(_trigger)的执行动作(_commands),那么这些触发条件是什么时候发生呢?
解析完成之后,init 会调用如下的代码:
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.boot.mode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
am.QueueEventTrigger 函数即表明到达了某个所需的某个时间条件。如 am.QueueEventTrigger(“early-init”) 表明 “early-init” 条件触发,对应的动作可以开始执行。
其实这个函数只是将时间点如:”late-init” 填充进 event_queue_ 运行队列,在后续的遍历循环中,会从运行队列中依次取出时间点,可以理解为系统运行到达了该时间点,随后遍历判断 actions 列表中 _event_trigger 触发条件是否满足, 如果满足则执行该 action 对象包含的命令 _commands。下面是具体过程。
while (true) {
// 决定timeout的时间,将影响 while 循环的间隔.
int epoll_timeout_ms = -1;
// 判断是否有事件需要处理
if (!(waiting_for_prop || sm.IsWaitingForExec())) {
// 依次执行每个 action 中携带 command 对应的执行函数
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || sm.IsWaitingForExec())) {
if (!shutting_down) restart_processes();
// If there's a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) {
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
//有 action 待处理,不等待
if (am.HasMoreCommands()) epoll_timeout_ms = 0;
}
// 前面分析中等待子进程终止信号的处理
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
可以看到,主要的处理过程就是 am.ExecuteOneCommand() 这个函数。
void ActionManager::ExecuteOneCommand() {
// 遍历 event_queue_ 执行队列,依次触发时间条件点
while (current_executing_actions_.empty() && !event_queue_.empty()) {
// 查询 actions_ 链表中 Action 对象是否有满足该触发条件
// 即:action._event_trigger = event_queue_(value)
for (const auto& action : actions_) {
if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
event_queue_.front())) {
// 如果满足条件,则将该 action 对象加入 current_executing_actions_ 执行对象列表中
current_executing_actions_.emplace(action.get());
}
}
event_queue_.pop();
}
if (current_executing_actions_.empty()) {
return;
}
auto action = current_executing_actions_.front();
if (current_command_ == 0) {
std::string trigger_name = action->BuildTriggersString();
LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
<< ":" << action->line() << ")";
}
// 依次执行该 action 的 _command 域中包含的命令
action->ExecuteOneCommand(current_command_);
// If this was the last command in the current action, then remove
// the action from the executing list.
// If this action was oneshot, then also remove it from actions_.
++current_command_;
if (current_command_ == action->NumCommands()) {
current_executing_actions_.pop();
current_command_ = 0;
if (action->oneshot()) {
auto eraser = [&action] (std::unique_ptr<Action>& a) {
return a.get() == action;
};
actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
从代码中可以看到,ExecuteOneCommand 函数判断 _action 链表中的 trigger 条件是否满足执行队列中时间点,依次取出匹配的 action 对象中的一个 command 命令并执行(一个action可能携带多个command)。
当一个 action 对象所有的 command 均执行完毕后,再执行下一个action。
当一个 trigger 触发时间点对应的 action 对象均执行完毕后,再执行下一个 trigger 对应 action。
当属性发生变化时
我们知道,其它进程可以直接进行属性的读操作,但是属性系统的写操作只能在 init 进程中进行,其它进程进行属性的写操作也需要通过 init 进程。那么属性变化 PropertySetImpl 是开源点,所以先看下这个函数。
static uint32_t PropertySetImpl(const std::string& name, const std::string& value) {
...
property_changed(name, value);
...
}
- 1
- 2
- 3
- 4
- 5
在这个函数中,每次设置属性操作的最后,都会调用 property_changed 函数来通知 init 进程属性进行了修改。
void property_changed(const std::string& name, const std::string& value) {
// powerctl 相关操作
if (name == "sys.powerctl") {
shutdown_command = value;
do_shutdown = true;
}
// 关键处理函数
if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
// 当前正在设置属性,进行一些同步化的操作
if (waiting_for_prop) {
if (wait_prop_name == name && wait_prop_value == value) {
LOG(INFO) << "Wait for property took " << *waiting_for_prop;
ResetWaitForProp();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
从代码中可以看到,property_triggers_enabled 是 <属性变化触发 action> 的使能点,开启之后每次属性发生变化都会调用 ActionManager.QueuePropertyChange(name, value) 函数
void ActionManager::QueuePropertyChange(const std::string& name, const std::string& value) {
event_queue_.emplace(std::make_pair(name, value));
}
- 1
- 2
- 3
QueuePropertyChange 函数同样填充了 event_queue_ 队列,将已改变属性的键值对作为参数。
运行队列中已经添加了该属性的变化触发条件,同样通过 am.ExecuteOneCommand() 函数遍历所有的 _actions 链表,执行相应的 commands。
八. 总结
到这里,init 进程就启动完毕了,在 while(true) 循环中不断处理子进程信号以及其他的配置或者控制操作。
总结一下 init 进程第二阶段执行,也就是用户态的执行主要工作内容有:
1. 创建进程会话密钥
2. 初始化属性服务
3. SELinux 第二阶段准备后续工作
4. 初始化子进程终止信号处理函数
5. init.rc 文件解析 & 处理