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

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

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

*                  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 分析)的更多相关文章

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

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

  2. Android启动篇 — init原理(一)

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

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

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

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

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

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

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

  6. Android Studio第一次启动失败的解决办法

    Android Studio Android 开发环境 由于GFW的问题,安装后第一次启动会在显示Fetching android sdk component information对话框后,提示错误 ...

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

    前言 init进程,它是一个由内核启动的用户级进程,当Linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,确切的说,它是Linux系统中用户控件的第一个进程,所以它的进程号是1 ...

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

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

  9. Tigase8.0 源代码分析:一、启动篇

    Tigase8.0 引用了IoC(控制反转)和DI(依赖注入) 等技术手段,来对对象的创建和控制.不懂的百度下就知道了,Spring完美的实现IOC ,贴一段解释: 通俗地说:控制反转IoC(Inve ...

随机推荐

  1. svn Server sent unexpected return value (403 Forbidden) in response to CHECKOUT

    今天,提交資料到公司svn服務器,但是一直提示 Server sent unexpected return value (403 Forbidden) in response to CHECKOUT ...

  2. Stack和Vector源码分析

    Stack和Vector源码分析 Stack和Vector源码分析stack源码分析1.Stack是什么2.Stack的结构图3.Stack继承关系4.Stack的主要方法5.Stack源码Vecto ...

  3. C程序员眼里的Python

    注释 Phython的注释和C语言非常不同,第一种 #开头的注释,类似于C的//开头,而"""对 包围注释,类似于C的/* */,以及xml类的<!--    -- ...

  4. LeeCode数组第15题三数之和

    题目:三数之和 内容: 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中 ...

  5. ArcCore重构-目标文件结构化

    基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5   基本问题 3. 编译系统中所有代码文件通过搜索路径(VPATH)中搜索,存在名称污染问题,需加入路径信息:   ...

  6. POST与PUT

    POST和PUT都是HTTP中客户端向服务器发送请求的方法 POST : 向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件).数据被包含在请求本文中.这个请求可能会创建新的 资源或修改 ...

  7. .NET之EntityFramework框架运用

    1.创建EF模型库 创建类库-->添加新建项-->选择ADO.NET实体数据模型-->选择 来自数据库的EF选择器-->配置数据库链接以及相应的数据库-->看底部(将ap ...

  8. Fiddler证书安装(查看HTTPS)

    现在很多带有比较重要信息的接口都使用了安全性更高的HTTPS,而Fiddler默认是抓取HTTP类型的接口,要想查看HTTPS类型接口就需要安装fiddler证书.   fiddler安装教程可参考: ...

  9. IBM x3850 RAID5数据恢复过程

    [raid数据恢复故障描述]    需要进行数据恢复的是北京一家公司的IBM X3850服务器,服务器挂载了5块73G SAS硬盘组成raid5磁盘阵列,4号盘为热备盘(Hot-Spare),由于未知 ...

  10. Git的思想和基本工作原理2

    那么,简单地说,Git 究竟是怎样的一个系统呢?请注意,接下来的内容非常重要,若是理解了 Git 的思想和基本工作原理,用起来就会知其所以然,游刃有余. 在开始学习 Git 的时候,请不要尝试把各种概 ...