Android中的init进程源码分析

本文以详细的注释解释Android启动的过程中init进程main方法的执行过程

总的来说,可以将init的执行过程分为以下四个阶段:

  1. 初始化文件系统和日志系统,为之后的执行阶段做准备。这部分主要是Linux标准函数的调用。
  2. 解析init.rc和init.<hardware>.rc始化文件。
  3. 触发需要执行的Action和Service。
  4. init循环监听处理事件。init触发所有Action后,进人一个无限循环,执行在可执行队列中的命令,重启异常退出的Service,并循环处理来自property service(属性服务)、signal和keychord的事件。
int main(int argc, char** argv) {
    if (!strcmp(basename(argv[0]), "ueventd")) {
   		/**
		创建子进程,将创建设备节点文件:
		1.冷插拔:静态节点文件,类似键盘,鼠标等
		2.热插拔:动态节点文件,类似USB设备等
		*/
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
   		/**
		计算机系统和"看门狗"有两个引脚相连接, 正常运行时每隔一段时间就会通过其中一个引脚向"看门
		狗"发送信号,"看门狗"接收到信号后会将计时器清零并重新开始计时, 而一旦系统出现问题,进入死循
		环或任何阻塞状态,不能及时发送信号让"看门狗"的计时器清零,当计时结束时, "看门狗"就会通过另
		一个引脚向系统发送“复位信号”,让系统重启
		*/
        return watchdogd_main(argc, argv);
    }

    // Clear the umask.
    /**
	调用 umask(0) 的作用是将 umask 值设置为 0,意味着在创建新文件时没有任何访问权限被禁止。
	这将允许创建新文件时,文件的访问权限完全由文件创建时指定的权限掩码决定。
	*/
    umask(0);
	//添加环境变量,将一个键值对放到一个Char数组中,如果数组中有key就替换,没有就插入,跟Java中的Map差不多
    add_environment("PATH", _PATH_DEFPATH);
	//init进程会有两个阶段,判断运行是在哪个阶段
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);

    // 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是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中, 如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。 
		tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。 tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分
		区,性能仍然非常卓越。 由于tmpfs是驻留在RAM的,因此它的内容是不持久的。 断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
		*/
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        /**
        devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。 只要pty的主复合设备/dev/ptmx被打开,
        就会在/dev/pts下动态的创建一个新的pty设备文件。
        */
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        /**
		proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口, 通过它我们可以获得系统的信息,
		同时也能够在运行时修改特定的内核参数。
		*/
        mount("proc", "/proc", "proc", 0, NULL);
        /**
        sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。 它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的, 
        它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取
        */
        mount("sysfs", "/sys", "sysfs", 0, NULL);
		//selinuxfs也是虚拟文件系统,通常挂载在/sys/fs/selinux目录下,用来存放SELinux安全策略文件
    }

    // 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 设备上,不需要标准输入输出,从而关闭程序的输出,使其在后台运行。
    open_devnull_stdio();
    //初始化内核日志缓冲区,该缓冲区用于存储内核日志信息。
	//初始化内核日志设备,该设备用于向用户空间提供内核日志信息。
	//启动内核日志线程,该线程用于将内核日志信息写入到内核日志设备中。
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);

    NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");

    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        /**
        这是一个在 Linux 中常见的操作。这行代码的作用是创建一个名为 /dev/.booting 的文件,并以只写方式打开,同时设置文件权限为 0000,
        然后立即关闭文件描述符。具体来说,函数调用 open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000) 中的参数含义如下:
		/dev/.booting:创建的文件名为 /dev/.booting,位于 /dev 目录下。
		O_WRONLY:以只写方式打开文件。
		O_CREAT:如果文件不存在,则创建文件。
		O_CLOEXEC:在执行 exec() 系统调用时,会自动关闭文件描述符。
		0000:设置文件权限为 0000,即所有用户都没有任何权限。
		由于设置了文件权限为 0000,因此该文件无法被普通用户或 root 用户访问。通常,这种文件的作用是在启动过程中使用,
		用于表示系统当前正在启动,防止其他进程执行不必要的操作,或者用于通知其他进程执行相关操作。
		同时,由于设置了 O_CLOEXEC 标志,文件描述符会在进程执行 exec() 系统调用时自动关闭,以避免文件描述符泄漏。这是一个良好的编程实践,
		可以保护程序的安全性。
        */
        //在/dev目录下创建一个空文件.booting表示初始化正在进行
        //is_booting()函数会依靠空文件.booting来判断是否进程处于初始化中
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
		/**
		1.读取内核启动参数。系统属性可以从内核启动参数(kernel command line)中读取,因此 property_init() 函数会读取 /proc/cmdline 
		文件中的参数,并将其解析为系统属性。
		2.加载系统属性文件。系统属性还可以从 /system/build.prop 和 /default.prop 文件中加载,因此 property_init() 函数也会加载这些
		文件中的属性。
		3.注册系统属性服务。系统属性服务是一个 Android 组件,用于提供系统属性的查询和修改功能。因此,property_init() 函数还会注册系统属性
		服务,并将系统属性与服务关联起来。
		*/
		//创建一个共享区域来储存属性值
        property_init();

        // If arguments are passed both on the command line and in DT,
        // properties set in DT always have priority over the command-line ones.
        //读取设备树(DT)上的属性设置信息,查找系统属性,然后通过property_set设置系统属性
        process_kernel_dt();
        //解析kernel的cmdline文件提取以 androidboot.字符串打头的字符串,通过property_set设置该系统属性
        process_kernel_cmdline();

        // Propogate the kernel variables to internal variables
        // used by init as well as the current required properties.
        //额外设置一些属性,这个函数中定义了一个集合,集合中定义的属性都会从kernel中读取并记录下来
        export_kernel_boot_props();
    }

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    //安全策略初始化
    selinux_initialize(is_first_stage);

    // 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) {
	    /**
	    将 /init 文件的安全上下文恢复为默认值。如果恢复失败,则会调用 security_failure() 函数,用于处理安全失败的情况。
	    */
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        //重新执行 init 进程,并传递一个 --second-stage 参数。这样,init 进程就会进入第二阶段启动,并继续执行之后的初始化和启动操作。
        char* path = argv[0];
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
        if (execv(path, args) == -1) {
            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.
    /**
    恢复一些目录的安全上下文,包括 /dev、/dev/socket 和 /dev/__properties__ 目录。这些目录在安全策略加载之前就被创建了,因此需要手动恢
    复它们的上下文。
    */
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");
	
	/**
	创建 epoll 实例,并监控文件描述符上的事件。在 Android 系统启动过程中,epoll 实例通常用于监控系统属性(system property)和 socket
	 事件,以便在属性或套接字状态变化时及时通知应用程序或系统服务。
	*/
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
    //初始化子进程终止信号处理函数
	//在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出。
    signal_handler_init();
	//设置一些系统属性
    property_load_boot_defaults();
    //开启系统属性服务
    start_property_service();
	//解析rc文件
    init_parse_config_file("/init.rc");
	//将init.rc中配置的early-init 的Action放入可执行列队action_queue的队尾
    action_for_each_trigger("early-init", action_add_queue_tail);

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    //等待 coldboot 完成,确保系统已经启动并准备好执行后续的操作。
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    //分别初始化硬件随机数生成器、初始化按键事件和初始化控制台,确保系统能够正常使用这些设备和功能。
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    //将init.rc中配置的init 的Action放入可执行列队action_queue的队尾
    action_for_each_trigger("init", action_add_queue_tail);

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    char bootmode[PROP_VALUE_MAX];
    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("late-init", action_add_queue_tail);
    }

    // Run all property triggers based on current state of the properties.
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

    while (true) {
        if (!waiting_for_exec) {
            execute_one_command();//顺序执行所有action的command列表。
            restart_processes();//重启被标志为SVC_RESTARTING的service
        }

        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (!action_queue_empty() || cur_action) {
            timeout = 0;
        }
		/**
		bootchart 用于记录系统启动过程中的性能数据,包括不同进程的启动时间、CPU 占用率、内存使用情况等等。这些数据可以用于分析系统启动过程的
		性能瓶颈和优化方案。
		其参数是一个指向超时时间的指针。在函数内部,它会将当前时间点与传入的超时时间进行比较,以确定需要等待的时间。如果超时时间为负值,则表示
		需要立即记录当前 bootchart 信息。
		*/
        bootchart_sample(&timeout);

        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));//监听epoll事件,property service signal keychord等
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();//执行
        }
    }

    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

h397318057

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值