========================================================          ========================================================

=              【原创文章】:参考部分博客内容,学习之余进行了大量的筛减细化分析                        =          =                          【特殊申明】:避讳抄袭侵权之嫌疑,特此说明,欢迎转载!                           =   
========================================================          ========================================================

***************************************************************************         ***************************************************************************

*                  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函数开始:

  1. int main(int argc, char** argv) { // 入口函数main
  2. ]), "ueventd")) {
  3. return ueventd_main(argc, argv);
  4. }
  5.  
  6. ]), "watchdogd")) {
  7. return watchdogd_main(argc, argv);
  8. }
  9.  
  10. // Clear the umask.
  11. umask(); // 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。
  12. add_environment("PATH", _PATH_DEFPATH);
  13.  
  14. ) || (strcmp(argv[], ); // 判断是否是系统启动的第一阶段,只有启动参数中有--second-stage才为第二阶段
  1. , NULL); , , NULL); // 挂载sysfs文件系统
    }

  以上代码主要做的工作就是:【创建文件系统目录并挂载相关的文件系统】

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. // We must have some place other than / to create the device nodes for
  5. // kmsg and null, otherwise we won't be able to remount / read-only
  6. // later on. Now that tmpfs is mounted on /dev, we can actually talk
  7. // to the outside world.
  8. open_devnull_stdio(); // 重定向标准输入输出到/dev/_null_ --> 定义在system/core/init/Util.cpp中
  9. // init进程通过klog_init函数,提供输出log信息的设备 --> 定义在system/core/libcutils/Klog.c中
  10. klog_init(); // 对klog进行初始化
  11. klog_set_level(KLOG_NOTICE_LEVEL); // NOTICE level

  继续分析源码,接下来要做的就是初始化属性域:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
    /* 03. 初始化属性域 */
  4. NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
  5. if (!is_first_stage) { // 引入SELinux机制后,通过is_first_stage区分init运行状态
  6. // Indicate that booting is in progress to background fw loaders, etc.
  7. close(open()); /* 检测/dev/.booting文件是否可读写、创建等*/
  1. property_init(); // 初始化属性域 --> 定义于system/core/init/Property_service.cpp
  2.  
  3. // If arguments are passed both on the command line and in DT,
  4. // properties set in DT always have priority over the command-line ones.
  5. process_kernel_dt();
  6. process_kernel_cmdline(); // 处理内核命令行
  7. // Propagate the kernel variables to internal variables
  8. // used by init as well as the current required properties.
  9. export_kernel_boot_props();
  10. }

  看一下property_init方法:位于system/core/init/Property_service.cpp中

  1. void property_init() {
  2. if (__system_property_area_init()) { // 调用此函数初始化属性域
  3. ERROR("Failed to initialize property area\n");
  4. exit();
  5. }
  6. }

  继续分析main函数:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. /* 03. 初始化属性域 */
    /* 04. 完成SELinux相关工作 */
  5. // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
  6. selinux_initialize(is_first_stage); // 调用selinux_initialize启动SELinux

  详细看一下selinux_initialize()函数:

  1. static void selinux_initialize(bool in_kernel_domain) { // 区分内核态和用户态
  2. Timer t; //使用Timer计时,计算selinux初始化耗时
  3.  
  4. selinux_callback cb;
  5. cb.func_log = selinux_klog_callback; // 用于打印Log的回调函数
  6. selinux_set_callback(SELINUX_CB_LOG, cb);
  7. cb.func_audit = audit_callback; // 用于检查权限的回调函数
  8. selinux_set_callback(SELINUX_CB_AUDIT, cb);
  9.  
  10. if (in_kernel_domain) { // 内核态处理流程,第一阶段in_kernel_domain为true
  11. INFO("Loading SELinux policy...\n"); // 该行log打印不出,INFO级别
  12. // 用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件
  13. ) {
  14. ERROR("failed to load policy: %s\n", strerror(errno));
  15. security_failure();
  16. }
  17.  
  18. ); // 内核中读取的信息
  19. bool is_enforcing = selinux_is_enforcing(); // 命令行中得到的信息
  20. if (kernel_enforcing != is_enforcing) {
  21. // 用于设置selinux的工作模式。selinux有两种工作模式:
  22. // 1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
  23. // 2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式
  24. if (security_setenforce(is_enforcing)) { //设置selinux的模式,是开还是关
  25. ERROR("security_setenforce(%s) failed: %s\n",
  26. is_enforcing ? "true" : "false", strerror(errno));
  27. security_failure(); // 将重启进入recovery mode
  28. }
  29. }
  30.  
  31. ) {
  32. security_failure();
  33. }
  34.  
  35. NOTICE("(Initializing SELinux %s took %.2fs.)\n",
  36. is_enforcing ? "enforcing" : "non-enforcing", t.duration()); //输出selinux的模式,与初始化耗时
  1. } else {
    selinux_init_all_handles(); //如果启动第二阶段,调用该函数
    }
    }

  回到main函数中继续分析:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. /* 03. 初始化属性域 */
  5. /* 04. 完成SELinux相关工作 */
    /* 05. 重新设置属性 */
  6. // If we're in the kernel domain, re-exec init to transition to the init domain now
  7. // that the SELinux policy has been loaded.
  8. if (is_first_stage) {
  9. ) { // 按selinux policy要求,重新设置init文件属性
  1. ERROR("restorecon failed: %s\n", strerror(errno));
  2. security_failure();
  3. }
  4. ];
  5. char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; //设置参数--second-stage
  1. ) { // 执行init进程,重新进入main函数
  2. ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
  3. security_failure();
  4. }
  5. }
  6.  
  7. // These directories were necessarily created before initial policy load
  8. // and therefore need their security context restored to the proper value.
  9. // This must happen before /dev is populated by ueventd.
  10. NOTICE("Running restorecon...\n");
  11. restorecon("/dev");
  12. restorecon("/dev/socket");
  13. restorecon("/dev/__properties__");
  14. restorecon("/property_contexts");
  15.  
  16. restorecon_recursive("/sys");
  17.  
  18. epoll_fd = epoll_create1(EPOLL_CLOEXEC); // 调用epoll_create1创建epoll句柄
  19. ) {
  20. ERROR("epoll_create1 failed: %s\n", strerror(errno));
  21. exit();
  22. }

  接着往下分析:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. /* 03. 初始化属性域 */
  5. /* 04. 完成SELinux相关工作 */·
  6. /* 05. 重新设置属性 */
  7. /* 06. 创建epoll句柄 */
    /* 07. 装载子进程信号处理器 */
  8. signal_handler_init(); // 装载子进程信号处理器

    Note:init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。

  细化signal_handler_init()函数:

  1. void signal_handler_init() { // 函数定位于:system/core/init/Singal_handler.cpp
  2. // 在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况
  3. // Create a signalling mechanism for SIGCHLD.
  4. ];
  5. // 利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端
  6. , s) == -) {
  7. ERROR("socketpair failed: %s\n", strerror(errno));
  8. exit();
  9. }
  10.  
  11. signal_write_fd = s[];
  12. signal_read_fd = s[];
  13.  
  14. // Write to signal_write_fd if we catch SIGCHLD.
  15. struct sigaction act;
  16. memset(&act, , sizeof(act));
  17. // 信号处理器为SIGCHLD_handler,其被存在sigaction结构体中,负责处理SIGCHLD消息
  18. act.sa_handler = SIGCHLD_handler; // 信号处理器:SIGCHLD_handler
  19. act.sa_flags = SA_NOCLDSTOP; // 仅当进程终止时才接受SIGCHLD信号
  20. // 调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中
  21. sigaction(SIGCHLD, &act, );
  22. // 相对于6.0的代码,进一步作了封装,用于终止出现问题的子进程
  23. ServiceManager::GetInstance().ReapAnyOutstandingChildren();
  24.  
  25. register_epoll_handler(signal_read_fd, handle_signal); // 定义在system/core/init/Init.cpp
  26. }

  Linux进程通过互相发送接收消息来实现进程间的通信,这些消息被称为“信号”。每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。

  注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。

  观察SIGCHLD_handler具体工作:

  1. static void SIGCHLD_handler(int) {
  2. /* init进程是所有进程的父进程,当其子进程终止产生SIGCHLD信号时,SIGCHLD_handler对signal_write_fd执行写操作,由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。*/
  3. )) == -) {
  4. ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
  5. }
  6. }

  在装在信号监听器的最后,有如下函数:register_epoll_handler(signal_read_fd, handle_signal);

  1. void register_epoll_handler(int fd, void (*fn)()) { // 回到init.cpp中
  2. epoll_event ev;
  3. ev.events = EPOLLIN;
  4. ev.data.ptr = reinterpret_cast<void*>(fn);
  5. // epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理
  6. // 当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。
  7. ) {
  8. ERROR("epoll_ctl failed: %s\n", strerror(errno));
  9. }
  10. }

【小结】

  当init进程调用signal_handler_init后,一旦收到子进程终止带来的SIGCHLD消息后,将利用信号处理者SIGCHLD_handler向signal_write_fd写入信息; epoll句柄监听到signal_read_fd收消息后,将调用handle_signal进行处理。

  

  查看handle_signal函数:

  1. static void handle_signal() { // --> 位于system/core/init/signal_handler.cpp中
  2. // Clear outstanding requests.
  3. ];
  4. read(signal_read_fd, buf, sizeof(buf));
  5.  
  6. ServiceManager::GetInstance().ReapAnyOutstandingChildren();
  7. }

  从代码中可以看出,handle_signal只是清空signal_read_fd中的数据,然后调用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。

  继续分析:

  1. // 定义于system/core/init/service.cpp中,是一个单例对象。
  2. ServiceManager::ServiceManager() { // 默认private属性
  3. }
  4.  
  5. ServiceManager& ServiceManager::GetInstance() {
  6. static ServiceManager instance;
  7. return instance;
  8. }
  9. void ServiceManager::ReapAnyOutstandingChildren() {
  10. while (ReapOneProcess()) { // 实际调用了ReapOneProcess函数
  11. }
  12. }

  接下来看下ReapOneProcess这个函数:

  1. bool ServiceManager::ReapOneProcess() {
  2. int status;
  3. //用waitpid函数获取状态发生变化的子进程pid
  4. //waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
  5. pid_t pid = TEMP_FAILURE_RETRY(waitpid(-, &status, WNOHANG));
  6. ) {
  7. return false;
  8. } ) {
  9. ERROR("waitpid failed: %s\n", strerror(errno));
  10. return false;
  11. }
  12. // 利用FindServiceByPid函数,找到pid对应的服务。
  13. // FindServiceByPid主要通过轮询解析init.rc生成的service_list,找到pid与参数一直的svc
  14. Service* svc = FindServiceByPid(pid);
  15.  
  16. std::string name;
  17. if (svc) {
  18. name = android::base::StringPrintf("Service '%s' (pid %d)",
  19. svc->name().c_str(), pid);
  20. } else {
  21. name = android::base::StringPrintf("Untracked pid %d", pid);
  22. }
  23.  
  24. if (WIFEXITED(status)) {
  25. NOTICE("%s exited with status %d\n", name.c_str(), WEXITSTATUS(status));
  26. } else if (WIFSIGNALED(status)) {
  27. NOTICE("%s killed by signal %d\n", name.c_str(), WTERMSIG(status)); // 输出服务结束原因
  28. } else if (WIFSTOPPED(status)) {
  29. NOTICE("%s stopped by signal %d\n", name.c_str(), WSTOPSIG(status));
  30. } else {
  31. NOTICE("%s state changed", name.c_str());
  32. }
  33.  
  34. if (!svc) {
  35. return true;
  36. }
  37.  
  38. if (svc->Reap()) { // 结束服务,相对于6.0作了进一步的封装,重启一些子进程,不做具体分析
  39. waiting_for_exec = false;
  40. RemoveService(*svc); // 移除服务对应的信息
  41. }
  42.  
  43. return true;
  44. }

  继续分析main()函数:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. /* 03. 初始化属性域 */
  5. /* 04. 完成SELinux相关工作 */·
  6. /* 05. 重新设置属性 */
  7. /* 06. 创建epoll句柄 */
  8. /* 07. 装载子进程信号处理器 */
    /* 08. 启动匹配属性的服务端*/
  9. property_load_boot_defaults(); // 进程调用property_load_boot_defaults进行默认属性配置相关的工作
  10. export_oem_lock_status();
  11.  
  12. std::string bootmode = property_get("ro.bootmode"); // 获取启动模式
  13. ) == ){
  14. property_set(");
  15. }else{
  16. property_set(");
  17. }
  18. start_property_service(); // 启动属性服务

  看下property_load_boot_defaults()函数:位于system/core/init/Property_service.cpp中

  1. // property_load_boot_defaults实际上就是调用load_properties_from_file解析配置文件 /* 09. 设置默认系统属性 */
  2. // 然后根据解析的结果,设置系统属性
  3. void property_load_boot_defaults() {
  4. load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
  5. }

  接着继续分析main:

  1. int main(int argc, char** argv) {
  2. /* 01. 创建文件系统目录并挂载相关的文件系统 */
  3. /* 02. 屏蔽标准的输入输出/初始化内核log系统 */
  4. /* 03. 初始化属性域 */
  5. /* 04. 完成SELinux相关工作 */·
  6. /* 05. 重新设置属性 */
  7. /* 06. 创建epoll句柄 */
  8. /* 07. 装载子进程信号处理器 */
  9. /* 08. 设置默认系统属性 */
  10. /* 09. 启动配置属性的服务端 */
    /* 10. 匹配命令和函数之间的对应关系 */
  1. const BuiltinFunctionMap function_map; // system/core/init/builtins.cpp
  2. Action::set_function_map(&function_map); // 在Action中保存function_map对象,记录了命令与函数之间的对应关系

【结尾】

  由于init涉及的知识点是相当多,代码之间的逻辑也是极其复杂,我在看别人的博客过程中,最反感一篇博客要看很久,往往因为琐事而放弃坚持(确切的说,随手把网页关掉了),所以我就分章节分析,尽量少源码多讲解。

  接下来,在Android启动篇 — init原理(二)中将详细分析init.rc的解析过程

Android启动篇 — init原理(一)的更多相关文章

  1. Android启动篇 — init原理(二)

    ========================================================          ================================== ...

  2. Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  3. Android 7.0 启动篇 — init原理(一)(转 Android 9.0 分析)

    ========================================================          ================================== ...

  4. init进程 && 解析Android启动脚本init.rc && 修改它使不启动android && init.rc中启动一个sh文件

    Android启动后,系统执行的第一个进程是一个名称为init 的可执行程序.提供了以下的功能:设备管理.解析启动脚本.执行基本的功能.启动各种服务.代码的路径:system/core/init,编译 ...

  5. Android启动脚本init.rc(2)

    在Android中使用启动脚本init.rc,可以在系统的初始化中进行简单的操作. init.rc启动脚本路径:system/core/rootdir/init.rc 内容: Commands:命令 ...

  6. Android启动脚本init.rc说明文档readme.txt翻译

    Android Init Language--------------------- Android初始化语言--------------------- The Android Init Langua ...

  7. Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)

    前言 在Android中,zygote是整个系统创建新进程的核心进程.zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态.在之后的运作中,当其他 ...

  8. 【转】Android总结篇系列:Activity启动模式(lauchMode)

    [转]Android总结篇系列:Activity启动模式(lauchMode) 本来想针对Activity中的启动模式写篇文章的,后来网上发现有人已经总结的相当好了,在此直接引用过来,并加上自己的一些 ...

  9. Android插件化技术——原理篇

    <Android插件化技术——原理篇>     转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...

随机推荐

  1. Android布局管理详解(1)—— LinearLayout 线性布局

    Android的布局方式共有6种,分别是LinearLayout(线性布局).TableLayout(表格布局).FrameLayout(帧布局).RelativeLayout(相对布局).GridL ...

  2. PHP命名空间的概念与使用

    命名空间在其它编程语言中其名称不尽相同,但其核心慨念都是自定义一个存储空间.避免类名重复系统无法判断该执行哪一个类或是哪一个函数. 举例说明下.我先创建test这个文件夹在其当前目录下再创建一个ind ...

  3. 1121: [POI2008]激光发射器SZK

    1121: [POI2008]激光发射器SZK Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 574  Solved: 475[Submit][Sta ...

  4. MD5加密 32位

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; ...

  5. ajax 提交表单(文件)

    废话不多说,自己看代码 引入的jquery <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js&quo ...

  6. SEO-长尾词与分词技术

        长尾关键词与分词技术 长尾关键词:网站非目标关键词,能给网站带来流量的关键词. 例如:主关键词是成都网站建设 那么,跟成都网站建设相关的词,就叫做长尾关键词. 比如:成都网站建设哪里好?成都网 ...

  7. 关于极光推送在手机系统低于iOS10的手机上闪退的问题。

    最近项目中用到了极光推送,升级到了最新的SDK 2.1.9版本,发现只能在iOS10 上运行,其他测试的时候真机闪退.贴上一个可能的原因:

  8. c/c++重定向输入输出

    #define Local #include <iostream> #include <cstdio> //#include <stdio.h> using nam ...

  9. Android之万能播放器解码框架Vitamio的介绍及使用

    一.简介 Vitamio能够流畅播放720P甚至1080P高清MKV,FLV,MP4,MOV,TS,RMVB等常见格式的视频,还可以在Android 与 iOS 上跨平台支持 MMS, RTSP, R ...

  10. [转]利用excel进行线性规划求解

                           利用线性回归方法求解生产计划 方法一: 1.建立数学模型: 设变量:设生产拉盖式书桌x台,普通式书桌y台,可得最大利润 ‚确定目标函数及约束条件 目标函 ...