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

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

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

*                  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    if (!strcmp(basename(argv[0]), "ueventd")) {        return ueventd_main(argc, argv);    }    if (!strcmp(basename(argv[0]), "watchdogd")) {        return watchdogd_main(argc, argv);    }    // Clear the umask.    umask(0);    // 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。    add_environment("PATH", _PATH_DEFPATH);        bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);    // 判断是否是系统启动的第一阶段,只有启动参数中有--second-stage才为第二阶段
    // Get the basic filesystem setup we need put together in the initramdisk 
// on / and then we'll let the rc file figure out the rest.
if (is_first_stage) {
mount(
"tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); // 挂载tmpfs文件系统
mkdir("/dev/pts", 0755);
mkdir(
"/dev/socket", 0755);
mount(
"devpts", "/dev/pts", "devpts", 0, NULL); // 挂载devpts文件系统
#define MAKE_STR(x) __STRING(x)
mount(
"proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)); // 挂载proc文件系统
mount("sysfs", "/sys", "sysfs", 0, 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", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); /* 检测/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(1);    }}

  继续分析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就有了安全策略配置文件        if (selinux_android_load_policy() < 0) {            ERROR("failed to load policy: %s\n", strerror(errno));            security_failure();        }        bool kernel_enforcing = (security_getenforce() == 1);      // 内核中读取的信息        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            }        }        if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {            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) { if (restorecon("/init") == -1) { // selinux policy要求,重新设置init文件属性
            ERROR("restorecon failed: %s\n", strerror(errno));            security_failure();        }        char* path = argv[0];        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };     //设置参数--second-stage

 

    if (execv(path, args) == -1) {        // 执行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句柄    if (epoll_fd == -1) {        ERROR("epoll_create1 failed: %s\n", strerror(errno));        exit(1);    }

  接着往下分析:

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

    Noteinit是一个守护进程,为了防止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.    int s[2];    // 利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {        ERROR("socketpair failed: %s\n", strerror(errno));        exit(1);    }    signal_write_fd = s[0];    signal_read_fd = s[1];    // Write to signal_write_fd if we catch SIGCHLD.    struct sigaction act;    memset(&act, 0, sizeof(act));    // 信号处理器为SIGCHLD_handler,其被存在sigaction结构体中,负责处理SIGCHLD消息    act.sa_handler = SIGCHLD_handler;       // 信号处理器:SIGCHLD_handler    act.sa_flags = SA_NOCLDSTOP;            // 仅当进程终止时才接受SIGCHLD信号    // 调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中    sigaction(SIGCHLD, &act, 0);    // 相对于6.0的代码,进一步作了封装,用于终止出现问题的子进程    ServiceManager::GetInstance().ReapAnyOutstandingChildren();    register_epoll_handler(signal_read_fd, handle_signal);        // 定义在system/core/init/Init.cpp}

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

  注意到sigaction结构体的sa_flagsSA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLDinit需要忽略子进程在暂停时发出的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收到数据。*/    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {        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进行处理。    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {        ERROR("epoll_ctl failed: %s\n", strerror(errno));    }}

【小结】

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

  

  查看handle_signal函数:

static void handle_signal() {      // --> 位于system/core/init/signal_handler.cpp中    // Clear outstanding requests.    char buf[32];    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(-1, &status, WNOHANG));    if (pid == 0) {        return false;    } else if (pid == -1) {        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"); // 获取启动模式 if (strncmp(bootmode.c_str(), "ffbm", 4) == 0){ property_set("ro.logdumpd","0"); }else{ property_set("ro.logdumpd","1"); } 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的解析过程

 

转载于:https://www.cnblogs.com/pepsimaxin/p/6702945.html

更多相关文章

  1. C语言函数的递归(上)
  2. android 4.1源码下载方法
  3. [Android开发]android 跨进程通信之content provider
  4. 用adb抓取log
  5. 查看基于Android(安卓)系统单个进程内存、CPU使用情况的几种方法
  6. Android(安卓)Universal Image Loader 源码分析(转载)
  7. android 终于搞定了判断ScrollView是否滚动到底部!
  8. Android中的android.provider.Settings.System系统属性设定
  9. android 上层应用如何监听 sdcard的插拔事件

随机推荐

  1. 替代CardView实现圆角图的第三方控件
  2. Android(安卓)studio设置背景图片
  3. Android(安卓)Launcher3安装应用后,控制应
  4. Android入门:增删改查通讯录
  5. Android中文联系人排序及检索补丁的原理(0
  6. LeakCanary分析,如何判定的内存泄漏说明
  7. 【原创】【Android(安卓)Camera】—— 关
  8. Ubuntu下安装Android(安卓)Studio全过程(2
  9. Ubuntu 16.04 64bit 编译 Android(安卓)4
  10. 【Android(安卓)Developers Training】 2