init 进程流程分析 一
- 1.1引入init进程
- 1.2 init 进程入口函数
- 参考博客
- 预告
- 下一章 FirstStageMain()分析
1.1引入init进程
1.启动电源以及系统启动:
上电引导芯片从预定义的地放(固化在ROM)开始执行,加载引导程序Bootloader 到RAM中,然后执行。
注释:1、仅读存储器 (Read-Only Memory,ROM) 2、随机存取存储器 (Random Access Memory,RAM)
2.引导程序Bootloader:
负责将系统拉起来并运行
3.linux内核启动
当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。此外,还启动了Kernel的swapper进程(pid = 0)和kthreadd进程(pid = 2)。下面分别介绍下它们:
1.swapper进程:又称为idle进程,系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理,加载Binder Driver、Display、Camera Driver等相关工作。
2.kthreadd进程:Linux系统的内核进程,是所有内核进程的鼻祖,会创建内核工作线程kworkder,软中断线程ksoftirqd,thermal等内核守护进程。
kernel_init 主要工作是完成一些 init 的初始化操作,然后去系统根目录下依次找 ramdisk_execute_command 和 execute_command 设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了.Android 系统一般会在根目录下放一个 init 的可执行文件,也就是说 Linux 系统的 init 进程在内核初始化完成后,就直接通过 run_init_process 函数执行 init 这个文件,该可执行文件的源代码在 system/core/init/main.cpp 中。
在内核完成系统设置后,它首先在系统文件中寻找 init.rc 文件,井启动 init 进程。
1.2 init 进程入口函数
init进程是Android启动过程中在Linux系统中用户空间的第一个进程。
init启动入口是在/system/core/init/init.cpp文件的SecondStageMain方法中,但调用init的SecondStageMain方法是在/system/core/init/main.cpp中的main方法进行的,所以先从/system/core/init/main.cpp的main方法开始。
因此我们先分析第一阶段。
路径:/system/core/init/main.cpp
/*
* 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
* 2.main函数有四个参数入口,
*一是参数中有ueventd,进入ueventd_main
*二是参数中有subcontext,进入InitLogging 和SubcontextMain
*三是参数中有selinux_setup,进入SetupSelinux
*四是参数中有second_stage,进入SecondStageMain
*3.main的执行顺序如下:
* (1)ueventd_main init进程创建子进程ueventd,
* 并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件
* (2)FirstStageMain 启动第一阶段
* (3)SetupSelinux 加载selinux规则,并设置selinux日志,完成SELinux相关工作
* (4)SecondStageMain 启动第二阶段
*/
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#endif
// __has_feature 大致的意思是通过给定的值,判断编译器是否支持该特性
// address_sanitizer 检测内存访问错误的工具
//条件编译 判断 内存检测工具 当内存检测工具为TRUE,则走下面的回调函数。
setpriority(PRIO_PROCESS, 0, -20);
// int setpriority(int which, int who, int prio)方法可以用来设置进程、进程组和用户的进程执行优先权,故which有三个参数可选:PRIO_PROCESS,PRIO_PGRP,PRIO_USER
// 参数PRIO_PROCESS表示对进程设置优先级,0表示进程识别码,-20表示最高优先级(优先级在-20~20之间,越小越优先,默认为0,必须拥有root权限才能设置为低于0的数值)
// Boost prio which will be restored later
if (!strcmp(basename(argv[0]), "ueventd")) {
// 1.创建设备节点,权限设定等,当argv[0]的内容为ueventd时,strcmp()函数的值为0,再使用!strcmp进行取反,则0变为1,1就是true。
//strcmp比较字符串函数
return ueventd_main(argc, argv);
//重点2:ueventd_main:创建设备节点,权限设定等
}
// 当传入的参数个数大于1时,执行下面的几个操作:
if (argc > 1) {
// 参数为“subcontext”时,初始化日志系统
if (!strcmp(argv[1], "subcontext")) {
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
return SubcontextMain(argc, argv, &function_map);
}
// 3. 参数为“selinux_setup”,启动Selinux安全策略
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
//重点3. SetupSelinux:启动Selinux安全策略
}
// 4. 参数为“second_stage”,启动init进程第二阶段:解析init.rc文件,提供属性服务,创建epoll与处理子进程的终止等
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
//重点4. SecondStageMain:解析init.rc文件,提供属性服务,创建epoll与处理子进程的终止等
}
}
// 2. 默认启动init进程第一阶段:挂载相关文件系统等
return FirstStageMain(argc, argv);
//重点 1. FirstStageMain:挂载相关文件系统等
}
注释:由kernel_init()中run_init_process(ramdisk_execute_command)启动init进程。(ramdisk_execute_command值为/init)
注释:这个可执行的init就是man.cpp编译出来的,第一次启动的时候是没有参数的,所以会走'重点1'也就是init第一阶段
在 Android 系统启动时,进程启动的顺序如下:
1、内核启动后,执行 init 进程。
2、init 进程调用 FirstStageMain() 函数,该函数主要是设置一些系统属性(例如设置安卓运行时的文件夹位置、开启 adb 调试等)。
3、FirstStageMain() 函数调用 SecondStageMain() 函数。
4、SecondStageMain() 函数主要是启动 ueventd 进程。
5、ueventd 进程启动后,会执行 ueventd_main() 函数。
6、ueventd_main() 函数主要是监控系统中硬件设备的插拔事件并向系统发送事件消息。
7、SecondStageMain() 函数会继续执行,调用 SetupSelinux() 函数,该函数主要是启动 selinux 安全模块,进行安全策略的加载和管理。
8、SetupSelinux() 函数执行完成后,init 进程就进入了等待状态,等待系统其他进程的启动和处理。
因此,进程的启动顺序是 FirstStageMain() -> SecondStageMain() -> ueventd_main() -> SetupSelinux()。
参考博客
kernel init
参考其第二部分
Address Sanitizer
因此,Google 开发了一款专门用于检测内存访问错误的工具:AddressSanitizer(简称 ASan),它可以自动检测程序运行时(runtime)发生的许多内存访问错误。
setpriority
函数说明:setpriority()可用来设置进程、进程组和用户的进程执行优先权。参数which 有三种数值, 参数who 则依which 值有不同定义。
basename
basename命令用于获取路径中的文件名或路径名,还可以对末尾字符进行删除。
ueventd
Android 中的 ueventd 是一个守护进程,它通过netlink scoket监听内核生成的uevent消息,当ueventd收到这样的消息时,它通过采取适当的行动来处理它,通常是在/dev中创建设备节点,设置文件权限,设置selinux标签等。ueventd还可以处理内核请求的固件加载、创建块设备符号链接以及创建字符设备。