Android init process source code analysis

This article explains the execution process of the main method of the init process during Android startup with detailed notes

In general, the execution process of init can be divided into the following four stages:

  1. Initialize the file system and log system in preparation for the subsequent execution phase. This part is mainly the call of Linux standard functions.
  2. Parse init.rc and init.<hardware>.rc initialization files.
  3. Trigger the Action and Service that need to be executed.
  4. The init loop monitors and processes events. After init triggers all Actions, it enters an infinite loop, executes the commands in the executable queue, restarts the Service that exits abnormally, and cyclically processes events from property service (property service), signal and 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;
}

Supongo que te gusta

Origin blog.csdn.net/h397318057/article/details/130147327
Recomendado
Clasificación