Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)
======================================================== ========================================================
= 【原创文章】:参考部分博客内容,学习之余进行了大量的筛减细化分析 = = 【特殊申明】:避讳抄袭侵权之嫌疑,特此说明,欢迎转载! =
======================================================== ========================================================
*************************************************************************** ***************************************************************************
* Android源码下载:https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ * * 源码编译可参考【牛肉面大神之作】:http://blog.csdn.net/cjpx00008/article/details/60474883 *
*************************************************************************** ***************************************************************************
【开篇说明】
在【Android启示录】中,提到了主要的分析对象和分享内容,抛开Android内核级的知识点,学习Android第一步便是“init”,作为天字第一号进程,代码羞涩难懂,但是也极其重要,熟悉init的原理对后面Zygote -- SystemServer -- 核心服务等一些列源码的研究是有很大作用的,所以既然说研究Android源码,就先拿init “庖丁解牛”!
【正文开始】
Init进程,它是一个由内核启动的用户级进程,当Linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,确切的说,它是Linux系统中用户控件的第一个进程,所以它的进程号是1。它的生命周期贯穿整个linux 内核运行的始终, linux中所有其它的进程的共同始祖均为init进程,可以通过“adb shell ps | grep init”查看进程号。
Android init进程的入口文件在system/core/init/init.cpp中,由于init是命令行程序,所以分析init.cpp首先应从main函数开始:
int main(int argc, char** argv) { // 入口函数main ]), "ueventd")) { return ueventd_main(argc, argv); } ]), "watchdogd")) { return watchdogd_main(argc, argv); } // Clear the umask. umask(); // 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。 add_environment("PATH", _PATH_DEFPATH); ) || (strcmp(argv[], ); // 判断是否是系统启动的第一阶段,只有启动参数中有--second-stage才为第二阶段
, NULL); , , NULL); // 挂载sysfs文件系统 }
以上代码主要做的工作就是:【创建文件系统目录并挂载相关的文件系统】
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ // We must have some place other than / to create the device nodes for // kmsg and null, otherwise we won't be able to remount / read-only // later on. Now that tmpfs is mounted on /dev, we can actually talk // to the outside world. open_devnull_stdio(); // 重定向标准输入输出到/dev/_null_ --> 定义在system/core/init/Util.cpp中 // init进程通过klog_init函数,提供输出log信息的设备 --> 定义在system/core/libcutils/Klog.c中 klog_init(); // 对klog进行初始化 klog_set_level(KLOG_NOTICE_LEVEL); // NOTICE level
继续分析源码,接下来要做的就是初始化属性域:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage"); if (!is_first_stage) { // 引入SELinux机制后,通过is_first_stage区分init运行状态 // Indicate that booting is in progress to background fw loaders, etc. close(open()); /* 检测/dev/.booting文件是否可读写、创建等*/
property_init(); // 初始化属性域 --> 定义于system/core/init/Property_service.cpp // 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(); }
看一下property_init方法:位于system/core/init/Property_service.cpp中
void property_init() { if (__system_property_area_init()) { // 调用此函数初始化属性域 ERROR("Failed to initialize property area\n"); exit(); } }
继续分析main函数:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */ // Set up SELinux, including loading the SELinux policy if we're in the kernel domain. selinux_initialize(is_first_stage); // 调用selinux_initialize启动SELinux
详细看一下selinux_initialize()函数:
static void selinux_initialize(bool in_kernel_domain) { // 区分内核态和用户态 Timer t; //使用Timer计时,计算selinux初始化耗时 selinux_callback cb; cb.func_log = selinux_klog_callback; // 用于打印Log的回调函数 selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; // 用于检查权限的回调函数 selinux_set_callback(SELINUX_CB_AUDIT, cb); if (in_kernel_domain) { // 内核态处理流程,第一阶段in_kernel_domain为true INFO("Loading SELinux policy...\n"); // 该行log打印不出,INFO级别 // 用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件 ) { ERROR("failed to load policy: %s\n", strerror(errno)); security_failure(); } ); // 内核中读取的信息 bool is_enforcing = selinux_is_enforcing(); // 命令行中得到的信息 if (kernel_enforcing != is_enforcing) { // 用于设置selinux的工作模式。selinux有两种工作模式: // 1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志 // 2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式 if (security_setenforce(is_enforcing)) { //设置selinux的模式,是开还是关 ERROR("security_setenforce(%s) failed: %s\n", is_enforcing ? "true" : "false", strerror(errno)); security_failure(); // 将重启进入recovery mode } } ) { security_failure(); } NOTICE("(Initializing SELinux %s took %.2fs.)\n", is_enforcing ? "enforcing" : "non-enforcing", t.duration()); //输出selinux的模式,与初始化耗时
} else { selinux_init_all_handles(); //如果启动第二阶段,调用该函数 } }
回到main函数中继续分析:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */ /* 05. 重新设置属性 */ // If we're in the kernel domain, re-exec init to transition to the init domain now // that the SELinux policy has been loaded. if (is_first_stage) { ) { // 按selinux policy要求,重新设置init文件属性
ERROR("restorecon failed: %s\n", strerror(errno)); security_failure(); } ]; char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; //设置参数--second-stage
) { // 执行init进程,重新进入main函数 ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno)); security_failure(); } } // These directories were necessarily created before initial policy load // and therefore need their security context restored to the proper value. // This must happen before /dev is populated by ueventd. NOTICE("Running restorecon...\n"); restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon("/property_contexts"); restorecon_recursive("/sys"); epoll_fd = epoll_create1(EPOLL_CLOEXEC); // 调用epoll_create1创建epoll句柄 ) { ERROR("epoll_create1 failed: %s\n", strerror(errno)); exit(); }
接着往下分析:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */· /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ signal_handler_init(); // 装载子进程信号处理器
Note:init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
细化signal_handler_init()函数:
void signal_handler_init() { // 函数定位于:system/core/init/Singal_handler.cpp // 在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况 // Create a signalling mechanism for SIGCHLD. ]; // 利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端 , s) == -) { ERROR("socketpair failed: %s\n", strerror(errno)); exit(); } signal_write_fd = s[]; signal_read_fd = s[]; // Write to signal_write_fd if we catch SIGCHLD. struct sigaction act; memset(&act, , sizeof(act)); // 信号处理器为SIGCHLD_handler,其被存在sigaction结构体中,负责处理SIGCHLD消息 act.sa_handler = SIGCHLD_handler; // 信号处理器:SIGCHLD_handler act.sa_flags = SA_NOCLDSTOP; // 仅当进程终止时才接受SIGCHLD信号 // 调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中 sigaction(SIGCHLD, &act, ); // 相对于6.0的代码,进一步作了封装,用于终止出现问题的子进程 ServiceManager::GetInstance().ReapAnyOutstandingChildren(); register_epoll_handler(signal_read_fd, handle_signal); // 定义在system/core/init/Init.cpp }
Linux进程通过互相发送接收消息来实现进程间的通信,这些消息被称为“信号”。每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。
注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
观察SIGCHLD_handler具体工作:
static void SIGCHLD_handler(int) { /* init进程是所有进程的父进程,当其子进程终止产生SIGCHLD信号时,SIGCHLD_handler对signal_write_fd执行写操作,由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。*/ )) == -) { ERROR("write(signal_write_fd) failed: %s\n", strerror(errno)); } }
在装在信号监听器的最后,有如下函数:register_epoll_handler(signal_read_fd, handle_signal);
void register_epoll_handler(int fd, void (*fn)()) { // 回到init.cpp中 epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = reinterpret_cast<void*>(fn); // epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理 // 当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。 ) { ERROR("epoll_ctl failed: %s\n", strerror(errno)); } }
【小结】
当init进程调用signal_handler_init后,一旦收到子进程终止带来的SIGCHLD消息后,将利用信号处理者SIGCHLD_handler向signal_write_fd写入信息; epoll句柄监听到signal_read_fd收消息后,将调用handle_signal进行处理。
查看handle_signal函数:
static void handle_signal() { // --> 位于system/core/init/signal_handler.cpp中 // Clear outstanding requests. ]; read(signal_read_fd, buf, sizeof(buf)); ServiceManager::GetInstance().ReapAnyOutstandingChildren(); }
从代码中可以看出,handle_signal只是清空signal_read_fd中的数据,然后调用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。
继续分析:
// 定义于system/core/init/service.cpp中,是一个单例对象。 ServiceManager::ServiceManager() { // 默认private属性 } ServiceManager& ServiceManager::GetInstance() { static ServiceManager instance; return instance; } void ServiceManager::ReapAnyOutstandingChildren() { while (ReapOneProcess()) { // 实际调用了ReapOneProcess函数 } }
接下来看下ReapOneProcess这个函数:
bool ServiceManager::ReapOneProcess() { int status; //用waitpid函数获取状态发生变化的子进程pid //waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了 pid_t pid = TEMP_FAILURE_RETRY(waitpid(-, &status, WNOHANG)); ) { return false; } ) { ERROR("waitpid failed: %s\n", strerror(errno)); return false; } // 利用FindServiceByPid函数,找到pid对应的服务。 // FindServiceByPid主要通过轮询解析init.rc生成的service_list,找到pid与参数一直的svc Service* svc = FindServiceByPid(pid); std::string name; if (svc) { name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name().c_str(), pid); } else { name = android::base::StringPrintf("Untracked pid %d", pid); } if (WIFEXITED(status)) { NOTICE("%s exited with status %d\n", name.c_str(), WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { NOTICE("%s killed by signal %d\n", name.c_str(), WTERMSIG(status)); // 输出服务结束原因 } else if (WIFSTOPPED(status)) { NOTICE("%s stopped by signal %d\n", name.c_str(), WSTOPSIG(status)); } else { NOTICE("%s state changed", name.c_str()); } if (!svc) { return true; } if (svc->Reap()) { // 结束服务,相对于6.0作了进一步的封装,重启一些子进程,不做具体分析 waiting_for_exec = false; RemoveService(*svc); // 移除服务对应的信息 } return true; }
继续分析main()函数:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */· /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ /* 08. 启动匹配属性的服务端*/ property_load_boot_defaults(); // 进程调用property_load_boot_defaults进行默认属性配置相关的工作 export_oem_lock_status(); std::string bootmode = property_get("ro.bootmode"); // 获取启动模式 ) == ){ property_set("); }else{ property_set("); } start_property_service(); // 启动属性服务
看下property_load_boot_defaults()函数:位于system/core/init/Property_service.cpp中
// property_load_boot_defaults实际上就是调用load_properties_from_file解析配置文件 /* 09. 设置默认系统属性 */ // 然后根据解析的结果,设置系统属性 void property_load_boot_defaults() { load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL); }
接着继续分析main:
int main(int argc, char** argv) { /* 01. 创建文件系统目录并挂载相关的文件系统 */ /* 02. 屏蔽标准的输入输出/初始化内核log系统 */ /* 03. 初始化属性域 */ /* 04. 完成SELinux相关工作 */· /* 05. 重新设置属性 */ /* 06. 创建epoll句柄 */ /* 07. 装载子进程信号处理器 */ /* 08. 设置默认系统属性 */ /* 09. 启动配置属性的服务端 */ /* 10. 匹配命令和函数之间的对应关系 */
const BuiltinFunctionMap function_map; // system/core/init/builtins.cpp Action::set_function_map(&function_map); // 在Action中保存function_map对象,记录了命令与函数之间的对应关系
【结尾】
由于init涉及的知识点是相当多,代码之间的逻辑也是极其复杂,我在看别人的博客过程中,最反感一篇博客要看很久,往往因为琐事而放弃坚持(确切的说,随手把网页关掉了),所以我就分章节分析,尽量少源码多讲解。
接下来,在Android启动篇 — init原理(二)中将详细分析init.rc的解析过程。
Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)的更多相关文章
- Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)
======================================================== ================================== ...
- Android启动篇 — init原理(一)
======================================================== ================================== ...
- Android启动篇 — init原理(二)
======================================================== ================================== ...
- Android启动脚本init.rc(2)
在Android中使用启动脚本init.rc,可以在系统的初始化中进行简单的操作. init.rc启动脚本路径:system/core/rootdir/init.rc 内容: Commands:命令 ...
- init进程 && 解析Android启动脚本init.rc && 修改它使不启动android && init.rc中启动一个sh文件
Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译 ...
- Android Studio第一次启动失败的解决办法
Android Studio Android 开发环境 由于GFW的问题,安装后第一次启动会在显示Fetching android sdk component information对话框后,提示错误 ...
- Android 8.1 源码_启动篇(一) -- 深入研究 init(转 Android 9.0 分析)
前言 init进程,它是一个由内核启动的用户级进程,当Linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,确切的说,它是Linux系统中用户控件的第一个进程,所以它的进程号是1 ...
- Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)
前言 在Android中,zygote是整个系统创建新进程的核心进程.zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态.在之后的运作中,当其他 ...
- Tigase8.0 源代码分析:一、启动篇
Tigase8.0 引用了IoC(控制反转)和DI(依赖注入) 等技术手段,来对对象的创建和控制.不懂的百度下就知道了,Spring完美的实现IOC ,贴一段解释: 通俗地说:控制反转IoC(Inve ...
随机推荐
- VirtualBox 自动挂载共享文件夹
在文件 /etc/rc.local 中(用root用户)追加如下命令 mount -t vboxsf sharing /mnt/share 重启后就大功告成了,网上大部分说的修改etc下面的fstab ...
- Linux下高并发socket最大连接数
http://soft.chinabyte.com/os/285/12349285.shtml (转载时原文内容做个修改) 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是 ...
- unity做游戏常用功能实现(一)多方向同时输入也能让物体正常移动
-------小基原创,转载请给我一个面子 网上有很多讲输入控制如何移动,但是多数都是讲单一按下,对于同时按下2个或2个以上按键并没有说明怎么解决,这里小基研究了一下方便大家 (如果你直接写input ...
- 分享一个开源的网盘下载工具BaiduPCS-Go
大家在使用网盘的时候,一定忍受不了限速下载的速度.今天给大家分享一个开源的网盘下载项目BaiduPCS-Go.Go语言编写,仿 Linux shell 文件处理命令的百度网盘命令行客户端.多平台支持, ...
- 使用opencv调用24*24点阵字库和8*16ASCII字库在图片显示文字数字
课程实验:编程读汉字点阵字库,把自己的名字和学号叠加到图片的右下位置. 主要步骤分为三部分 第一部分:读取图片(文件读取) 第二部分:读取文字并从字库中提取相应的编码(字库的存储原理) 第三部分:将相 ...
- 集成支付宝,报警告warning: (arm64) /Users/tommy/Desktop/Project/ios-msdk-git/AlipaySDK4Standard/AlipaySDK/Library/UTDI
集成支付宝的时候遇到的问题,找到了解决办法,还说明了原因,非常好,觉得应该记下来,反正以我的记性下次一定是会忘光光哒~ 1) Go to Build Settings -> Build Opt ...
- Spring Boot实战笔记(一)-- Spring简介
一.Spring 概述 Spring框架是一个轻量级的企业级开发的一站式解决方案.所谓的解决方案就是可以基于Spring解决所有的Java EE开发的所有问题. Spring框架主要提供了Ioc(In ...
- Linux文本处理命令 -- awk
简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再 ...
- EXCEL解析之终极方法WorkbookFactory
Selenium做自动化测试当然不能避免和Excel打交道. 由于Excel版本的关系,文件扩展名分xls和xlsx, 以往的经验都是使用HSSFWorkbook和XSSFWorkbook来分别处理. ...
- tkinter中spinbox递增和递减控件(十)
spinbox递增和递减控件 import tkinter wuya = tkinter.Tk() wuya.title("wuya") wuya.geometry("3 ...