当前位置:网站首页>Framework学习之旅:init 进程启动过程
Framework学习之旅:init 进程启动过程
2022-07-27 02:31:00 【流金岁月5789651】
概述
init 进程是Android系统中用户空间的第一个进程,进程号为1,是Android系统启动流程中一个关键的步骤,它有很多重要的职责,比如创建Zygote(孵化器)和属性服务等。它的生命周期贯穿整个Linux内核运行的始终。Android中所有其它的进程共同的鼻祖均为init进程。
init进程启动流程分析
Android Q(10.0)的init入口函数由原先的init.cpp调整到了main.cpp。接下来就从main函数开始:
system/core/init/main.cpp
// 第一个参数argc表示参数个数,第二个参数是参数列表
int main(int argc, char** argv) {
//当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1表示true,也就执行ueventd_main
//ueventd主要是负责设备节点的创建、权限设定等一些列工作
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//当传入的参数个数大于1时,执行下面的几个操作
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”,启动Selinux安全策略
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
//参数为“second_stage”,启动init进程第二阶段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 默认启动init进程第一阶段
return FirstStageMain(argc, argv);
}
main() 方法执行顺序如下:
- ueventd_main init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件。
- FirstStageMain 启动第一阶段
- SetupSelinux 加载selinux规则,并设置并设置selinux日志,完成SELinux相关工作
- SecondStageMain 启动第二阶段
接下来,继续查看ueventd_main() 函数
platform/system/core/init/ueventd.cpp
int ueventd_main(int argc, char** argv) {
//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
umask(000);
//初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程 还没有起来,
//采用kernel的log系统,打开的设备节点/dev/kmsg,那么可通过cat/dev/kmsg来获取内核log。
android::base::InitLogging(argv, &android::base::KernelLogger);
//注册selinux相关的用于打印log的回调函数
SelinuxSetupKernelLogging();
SelabelInitialize();
//解析xml,根据不同SOC厂商获取不同的hardware rc文件
auto ueventd_configuration = ParseConfig({
"/ueventd.rc", "/vendor/ueventd.rc","/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
//冷启动
if (access(COLDBOOT_DONE, F_OK) != 0) {
ColdBoot cold_boot(uevent_listener, uevent_handlers);
cold_boot.Run();
}
for (auto& uevent_handler : uevent_handlers) {
uevent_handler->ColdbootDone();
}
//忽略子进程终止信号
signal(SIGCHLD, SIG_IGN);
//在最后一次调用waitpid()和为上面的sigchld设置SIG_IGN之间退出的获取和挂起的子级
while (waitpid(-1, nullptr, WNOHANG) > 0) {
}
//监听来自驱动的uevent,进行“热插拔”处理
uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
for (auto& uevent_handler : uevent_handlers) {
//热启动,创建设备
uevent_handler->HandleUevent(uevent);
}
return ListenerAction::kContinue;
});
return 0;
}
Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。
ueventd通过两种方式创建设备节点文件:
- “冷插拔”(Cold Plug)
即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
- 应“热插拔”(Hot Plug)
即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点
文件。
init 进程启动第一阶段
init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。
platform\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();
}
//清空文件权限
umask(0);
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
//在RAM内存上获取基本的文件系统,剩余的被rc文件所用
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
// 非特权应用不能使用Andrlid cmdline
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)));
//这对于日志包装器是必需的,它在ueventd运行之前被调用
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666,makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1,3)));
//在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,只需要在第二阶段通过rc文件解析来加载。
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC |MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=1000"));
//创建可供读写的vendor目录
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,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
// 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC |MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));
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
//把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
SetStdioToDevNull(argv);
//在/dev目录下挂载好 tmpfs 以及 kmsg
//这样就可以初始化 /kernel Log 系统,供用户打印log
InitKernelLogging(argv);
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat("/"), not freeingramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev !=new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
SetInitAvbVersionInRecovery();
static constexpr uint32_t kNanosecondsPerMillisecond =1e6;
uint64_t start_ms = start_time.time_since_epoch().count()/kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(),1);
//启动init进程,传入参数selinux_steup
// 执行命令: /system/bin/init selinux_setup
const char* path = "/system/bin/init";
const char* args[] = {
path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
PLOG(FATAL) << "execv("" << path << "") failed";
return 1;
}
第一阶段完成以下内容:
- 创建文件系统目录并挂载相关的文件系统
- 屏蔽标准的输入输出/初始化内核log系统
加载SELinux规则
SELinux是Linux的一个扩张强制访问控制安全模块。在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。
初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段。
platform\system\core\init\selinux.cpp
// 初始化selinux
int SetupSelinux(char** argv) {
//初始化Kernel日志
InitKernelLogging(argv);
// Debug版本init crash时重启引导加载程序
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
//注册回调,用来设置需要写入kmsg的selinux日志
SelinuxSetupKernelLogging();
//加载SELinux规则
SelinuxInitialize();
// 在内核域中,希望转换到init域。在其xattrs中存储selabel的文件系统(如ext4)不需要显式restorecon,但其他文件系统需要。尤其是对于ramdisk,如对于a/b设备的恢复映像,这是必需要做的一步其实就是当前在内核域中,在加载Seliux后,需要重新执行init切换到C空间的用户态。
if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
PLOG(FATAL) << "restorecon failed of /system/bin/initfailed";
}
//准备启动init进程,传入参数second_stage
const char* path = "/system/bin/init";
const char* args[] = {
path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
// 执行 /system/bin/init second_stage, 进入第二阶段
PLOG(FATAL) << "execv("" << path << "") failed";
return 1;
}
// 加载SELinux规则
void SelinuxInitialize() {
LOG(INFO) << "Loading SELinux policy";
if (!LoadPolicy()) {
LOG(FATAL) << "Unable to load SELinux policy";
}
//获取当前Kernel的工作模式
bool kernel_enforcing = (security_getenforce() == 1);
//获取工作模式的配置
bool is_enforcing = IsEnforcing();
//如果当前的工作模式与配置的不同,就将当前的工作模式改掉
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(FATAL) << "security_setenforce
(" << (is_enforcing ? "true" : "false") << ") failed";
}
}
if (auto result = WriteFile("/sys/fs/selinux/checkreqprot", "0"); !result) {
LOG(FATAL) << "Unable to write to/sys/fs/selinux/checkreqprot: " << result.error();
}
}
// 加载SELinux规则:这里区分了两种情况,这两种情况只是区分从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,
// 第二个是从 /sepolicy读取,他们最终都是调用selinux_android_load_policy_from_fd()方法
bool LoadPolicy() {
return IsSplitPolicyDevice() ? LoadSplitPolicy():LoadMonolithicPolicy();
}
SELinux有两种工作模式:
- permissive
所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志,一般eng模式用。
- enforcing
所有操作都会进行权限检查。一般user和user-debug模式用,不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce文件,0表示permissive, 1表示enforcing。
init进程启动第二阶段
init进程启动第二阶段需要重点分析 SecondStageMain()方法。
int SecondStageMain(int argc, char* argv) {
// 01. 创建进程会话密钥并初始化属性系统
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
//创建 /dev/.booting 文件,就是个标记,表示booting进行中
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 初始化属性系统,并从指定文件读取属性
property_init();
//02. 进行SELinux第二阶段并恢复一些文件安全上下文 SelinuxRestoreContext();
// 03. 新建epoll并初始化子进程终止信号处理函数
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
InstallSignalFdHandler(&epoll);
//04. 设置其他系统属性并开启系统属性服务
StartPropertyService(&epoll);
// 05 解析init.rc等文件,建立rc文件的action 、 service,启动其他进程
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
}
小结:

第二阶段主要内容:
1.创建进程会话密钥并初始化属性系统
2.进行SELinux第二阶段并恢复一些文件安全上下文
3.新建epoll并初始化子进程终止信号处理函数。
4.启动匹配属性的服务端, 详细查看第六节-属性服务
5.解析init.rc等文件,建立rc文件的action 、service,启动其他进程
信号处理
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie
process),需要init在子进程在结束时获取子进程的结束码。通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
InstallSignalFdHandler
在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出。我们来看看init进程如何处理这个信号。
首先,新建一个sigaction结构体,sa_handler是信号处理函数,指向内核指定的函数指针SIG_DFL和Android9.0及之前的版本不同,这里不再通过socket的读写句柄进行接收信号,改成了内核的信号处理函数SIG_DFL。
然后,sigaction(SIGCHLD,&act,nullptr)这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理。
最后,RegisterHandler的作用就是signal_read_fd(之前的s[1])收到信号,触发handle_signal。
终上所述,InstallSignalFdHandler函数的作用就是,接收到SIGCHLD信号时触发HandleSignalFd进行信号处理。

platform/system/core/init.cpp
// 初始化子进程终止信号处理过程
static void InstallSignalFdHandler(Epoll* epoll) {
// SA_NOCLDSTOP使init进程只有在其子进程终止时才会受到SIGCHLD信号
const struct sigaction act {
.sa_handler = SIG_DFL, .sa_flags
= SA_NOCLDSTOP };
sigaction(SIGCHLD, &act, nullptr);
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (!IsRebootCapable()) {
// 如果init不具有 CAP_SYS_BOOT的能力,则它此时正值容器中运行
// 在这种场景下,接收SIGTERM 将会导致系统关闭
sigaddset(&mask, SIGTERM);
}
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
PLOG(FATAL) << "failed to block signals";
}
// 注册处理程序以解除对子进程中的信号的阻止
const int result = pthread_atfork(nullptr, nullptr,&UnblockSignals);
if (result != 0) {
LOG(FATAL) << "Failed to register a fork handler: " <<strerror(result);
}
//创建信号句柄
signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
if (signal_fd == -1) {
PLOG(FATAL) << "failed to create signalfd";
}
//信号注册,当signal_fd收到信号时,触发HandleSignalFd
// 分析1、分析2
if (auto result = epoll->RegisterHandler(signal_fd,HandleSignalFd); !result) {
LOG(FATAL) << result.error();
}
}
分析1:RegisterHandler
/platform/system/core/epoll.cpp
// 监控SIGCHLD信号,调用 ReapAnyOutstandingChildren来 终止出现问题的子进程
Result Epoll::RegisterHandler(int fd, std::function<void()>
handler, uint32_t events) {
if (!events) {
return Error() << "Must specify events";
}
auto [it, inserted] = epoll_handlers.emplace(fd,std::move(handler));
if (!inserted) {
return Error() << "Cannot specify two epoll handlers for agiven FD";
}
epoll_event ev;
ev.events = events;
ev.data.ptr = reinterpret_cast<void*>(&it->second);
// 将fd的可读事件加入到epoll_fd的监听队列中
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
Result result = ErrnoError() << "epoll_ctl failed to add fd";
epoll_handlers.erase(fd);
return result;
}
return {
};
}
分析2:HandleSignalFd
// 监控SIGCHLD信号,调用 ReapAnyOutstandingChildren来终止出现问题的子进程
static void HandleSignalFd() {
signalfd_siginfo siginfo;
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd,&siginfo, sizeof(siginfo)));
if (bytes_read != sizeof(siginfo)) {
PLOG(ERROR) << "Failed to read siginfo from signal_fd";
return;
}
//监控SIGCHLD信号
switch (siginfo.ssi_signo) {
case SIGCHLD:
ReapAnyOutstandingChildren();// 分析3
break;
case SIGTERM:
HandleSigtermSignal(siginfo);
break;
default:
PLOG(ERROR) << "signal_fd: received unexpected signal" << siginfo.ssi_signo;
break;
}
}
分析3:ReapOneProcess
ReapOneProcess是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程。
/platform/system/core/sigchld_handle.cpp
void ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
static bool 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;
// 当我们知道当前有一个僵尸pid,我们使用scopeguard来清楚该 pid
auto reaper = make_scope_guard([pid] {
TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
std::string name;
std::string wait_string;
Service* service = nullptr;
if (SubcontextChildReap(pid)) {
name = "Subcontext";
} else {
service = ServiceList::GetInstance().FindService(pid, &Service::pid);
if (service) {
name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
if (service->flags() & SVC_EXEC) {
auto exec_duration = boot_clock::now() - service->time_started();
auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds >(exec_duration).count();
wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
}else if (service->flags() & SVC_ONESHOT) {
auto exec_duration = boot_clock::now() - service->time_started();
auto exec_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds >(exec_duration) .count();
wait_string = StringPrintf(" oneshot service took %f seconds in background",exec_duration_ms / 1000.0f);
}
}else {
name = StringPrintf("Untracked pid %d", pid);
}
}
if (siginfo.si_code == CLD_EXITED) {
LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
}else {
LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
}
//没有找到service,说明已经结束了,退出
if (!service) return true;
//清除子进程相关的资源
service->Reap(siginfo);
if (service->flags() & SVC_TEMPORARY) {
//移除该service
ServiceList::GetInstance().RemoveService(*service);
}
return true;
}
小结
信号处理主要工作:
1.初始化信号signal句柄
2.循环处理子进程
3.注册epoll句柄
4.处理子进程终止
属性服务
Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看具体的流程是什么。
property_init
初始化属性系统,并从指定文件读取属性,并进行SELinux注册,进行属性权限控制
清除缓存。这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为/dev/properties然后就是调用CreateSerializedPropertyInfo加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存。
platform/system/core/property_service.cpp
void property_init() {
//设置SELinux回调,进行权限控制
selinux_callback cb;
cb.func_audit = PropertyAuditCallback; selinux_set_callback(SELINUX_CB_AUDIT, cb);
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
StartPropertyService
启动属性服务:首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性,最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd。
platform/system/core/init.cpp
void StartPropertyService(Epoll* epoll) {
property_set("ro.property_service.version", "2");
//建立socket连接
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, {
})) {
property_set_fd = *result;
} else {
PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
// 最大监听8个并发
listen(property_set_fd, 8);
// 注册property_set_fd,当收到句柄改变时,通过handle_property_set_fd来处理
if (auto result = epoll- >RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
PLOG(FATAL) << result.error();
}
}
handle_property_set_fd
建立socket连接,然后从socket中读取操作信息,根据不同的操作类型,调用HandlePropertySet做具体的操作。HandlePropertySet是最终的处理函数,以"ctl"开头的key就做一些Service的Start,Stop,Restart操作,其他的就是调用property_set进行属性设置,不管是前者还是后者,都要进行SELinux安全性检查,只有该进程有操作权限才能执行相应操作。
platform/system/core/property_service.cpp
static void handle_property_set_fd() {
// 等待客户端连接
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
ucred cr;
socklen_t cr_size = sizeof(cr);
// 获取连接到此socket的进程的凭据
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
// 建立socket连接
SocketConnection socket(s, cr);
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
// 读取socket中的操作信息
if (!socket.RecvUint32(&cmd, &timeout_ms)) {
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;
std::string source_context;
if (!socket.GetSourceContext(&source_context)) {
PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed";
return;
}
const auto& cr = socket.cred();
std::string error;
uint32_t result = HandlePropertySet(prop_name, prop_value, source_context, cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
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;
}
std::string source_context;
if (!socket.GetSourceContext(&source_context)) {
PLOG(ERROR) << "Unable to set property '" << name << "': getpeercon() failed";
socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
return;
}
const auto& cr = socket.cred();
std::string error;
uint32_t result = HandlePropertySet(name, value, source_context, cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
}
socket.SendUint32(result);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
第三阶段init.rc
当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。其他进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/initsecond_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。
在这个基础上Android推出了一个init.rc的机制,即类似通过读取配置文件的方式,来启动不同的进程。接下来我们就来看看init.rc是如何工作的。
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。
init.rc主要包含五种类型语句:Action、Command、Service、Option、Import。
Action
Action表示了一组命令(commands)组成。Action包括一个触发器,决定了何时运行这个动作。
通过触发器trigger,即以on开头的语句来决定执行相应的service的时机,具体有如下时机:
- on early-init
在初始化早期阶段触发
- on init
在初始化阶段触发
- on late-init
在初始化晚期阶段触发
- on boot/charger
当系统启动/充电时触发
- on property:=
当属性值满足条件时触发
Command
command是action的命令列表中的命令,或者是service中的选项onrestart的参数命令,命令将在所属事件发生时被一个个地执行。
常用的命令:
- class_start <service_class_name>
启动属于同一个class的所有服务
- class_stop <service_class_name>
停止指定类的服务
- start <service_name>
启动指定的服务,若已启动则跳过
- stop <service_name>
停止正在运行的服务
- setprop
设置属性值
- mkdir
创建指定目录
- symlink <sym_link>
创建连接到的<sym_link>符号链接
- write
向文件path中写入字符串
- exec
fork并执行,会阻塞init进程直到程序完毕
- exprot
设定环境变量
- loglevel
设置log级别
- hostname
设置主机名
- import
导入一个额外的init配置文件
Service
服务Service,以service开头,由init进程启动。一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。
Options
Options是Service的可选项,与service配合使用
可选项命令:
- disabled
不随class自动启动,只有根据service名才启动
- oneshot
service退出后不再重启
- user/group
设置执行服务的用户/用户组,默认都是root
- class
设置所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default
- onrestart
当服务重启时执行相应命令
- socket
创建名为/dev/socket/的socket
- critical
在规定时间内该service不断重启,则系统会重启并进入恢复模式
- default
意味着disabled=false,oneshot=false,critical=false
import
用来导入其他的rc文件
init.rc 解析过程
1. LoadBootScripts
如果没有特殊配置ro.boot.init_rc,则解析./init.rc把/system/etc/init、/product/etc/init、/product_services/etc/init、/odm/etc/init、/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件。
platform\system\core\init\init.cpp
static void LoadBootScripts(ActionManager& action_manager,ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")){
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/product_services/etc/init")){
late_import_paths.emplace_back("/product_services/et c/init");
}
if (!parser.ParseConfig("/odm/etc/init")){
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")){
late_import_paths.emplace_back("/vendor/etc/init");
}
}else{
parser.ParseConfig(bootscript);
}
}
Android7.0后,init.rc进行了拆分,每个服务都有自己的rc文件,他们基本上都被加载到/system/etc/init,/vendor/etc/init,/odm/etc/init等目录,等init.rc解析完成后,会来解析这些目录中的rc文件,用来执行相关的动作。
platform\system\core\init\init.cpp
// 创建Parser解析对象,例如service、on、import对象
Parser CreateParser(ActionManager& action_manager,ServiceList&service_list) {
Parser parser;
parser.AddSectionParser( "service", std::make_unique<ServiceParser> (&service_list, subcontexts, std::nullopt));
parser.AddSectionParser("on",std::make_unique<ActionParser>(&action_manager, subcontexts));
parser.AddSectionParser("import",std::make_unique<ImportParser>(&parser));
return parser;
}
2.Zygote启动
从Android 5.0的版本开始,Android支持64位的编译,因此zygote本身也支持32位和64位。通过属性ro.zygote来控制不同版本的zygote进程启动。
在init.rc的import段如下代码:
import /init.environ.rc
import /init.usb.rc
import /init.${
ro.hardware}.rc
import /vendor/etc/init/hw/init.${
ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${
ro.zygote}.rc
从上面的import /init.${ro.zygote}.rc,可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件。
init.rc位于/system/core/rootdir下,在这个路径下还包括四个关于zygote的rc文件:分别是init.zygote32.rc、init.zygote32_64.rc、init.zygote64.rc、init.zygote64_32.rc,由硬件决定调用哪个文件。
这里拿64位处理器为例,init.zygote64.rc的代码如下所示:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
# class是一个option,指定zygote服务的类型为main
class main
priority -20
user root
group root readproc reserved_disk
# socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart write /sys/android_power/request_state wake
# onrestart是一个option,说明在zygote重启时需要执行的command
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
service zygote :init.zygote64.rc 中定义了一个zygote服务, init进程就是通过这个service名称来创建zygote进程。
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
zygote这个服务,通过执行进行/system/bin/app_process64 并
传入4个参数进行运行:
- -Xzygote
该参数将作为虚拟机启动时所需的参数
- /system/bin
/system/bin 代表虚拟机程序所在目录
- –zygote
指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
- –start-system-server
告诉Zygote进程启动systemServer进程
init 进程启动总结
- 创建和挂载启动所需的文件目录
- 初始化和启动属性服务
- 解析init.rc配置文件并启动启动Zygote服务
边栏推荐
- Contour detection based on OpenCV (2)
- Is Jiufang intelligent investment a regular company? Talk about Jiufang intelligent investment
- Characteristics and experimental suggestions of abbkine abfluor 488 cell apoptosis detection kit
- Source code analysis of openfeign
- 回归测试:意义、挑战、最佳实践和工具
- 04. Detailed steps for installing the simulated browser chromedriver in Google browser
- Debug mode in pycharm for detailed debugging
- MySQL has a nonexistent error
- Number of square arrays (day 81)
- 一文读懂 | 数据中台如何支撑企业数字化经营
猜你喜欢

Use websocket to realize a web version of chat room (fishing is more hidden)

NLP hotspots from ACL 2022 onsite experience

Learning and understanding of four special data types of redis
![[Android synopsis] kotlin multithreaded programming (I)](/img/04/4349bacbd401868d73a3b05d018b66.png)
[Android synopsis] kotlin multithreaded programming (I)

Characteristics and determination scheme of Worthington pectinase

代码回滚,你真的理解吗?

次轮Okaleido Tiger即将登录Binance NFT,引发社区热议

Source code analysis of openfeign

SkyWalking分布式系统应用程序性能监控工具-中

Plato Farm有望通过Elephant Swap,进一步向外拓展生态
随机推荐
0726~简历梳理面试总结
Maximum continuous subsequence (day 77)
分析一下CSDN大佬写的CAS,可重入锁, 非公平锁
Characteristics and experimental suggestions of abbkine abfluor 488 cell apoptosis detection kit
Machine learning [Matplotlib]
第五届强网杯全国网络安全挑战赛 题目复现(有题目附件,详解)
明汯投资裘慧明:长期优异超额的背后考验的是团队的投研能力和策略的完整性
Use websocket to realize a web version of chat room (fishing is more hidden)
Chapter 5 decision tree and random forest practice
Okaleido tiger is about to log in to binance NFT in the second round, which has aroused heated discussion in the community
Learning and understanding of four special data types of redis
About the solution of using hyperbeach to appear /bin/sh: 1: packr2: not found
Data analysis and disassembly method of banyan tree in Bairong
复盘:DFS与BFS的主要区别,在思想上的区别,代码实现上的区别
ZJCTF_ login
Day 27 of leetcode
The application and significance of digital twins are the main role and conceptual value of electric power.
Message queue learning -- Concepts
Review in the sixth week
04. Detailed steps for installing the simulated browser chromedriver in Google browser