本节开始依次分析init进程源代码中main()函数内的代码。受限于篇幅,咱们没法将全部源代码一一列出讲解,这里分析主要流程和思路,但愿读者可以参考init进程的实际代码,一块儿研究学习。linux
init进程分析init.rc启动脚本文件,并根据相关文件中包含的内容,执行相应的功能。另外,init进程提供属性服务,保存系统运行所需的环境变量。此外,其还负责监视子进程的运行,处理子进程的终止和重启。当应用程序访问设备驱动时,还会生成设备节点文件。接下来咱们参考main()函数逐一分析代码。网络
init进程的运行分红两阶段,第一阶段只完成最主要的初始化工做,如目录生成和挂载、日志初始化和设置、SELinux初始化等,以后第二阶段才完成余下的初始化过程,如属性的初始化、属性服务启动、init.rc文件分析和相关服务的启动等。以下代码所示,当第一阶段完成后,init进程调用execv[1]函数并带上“--second-stag”标志切换到第二阶段。数据结构
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0); ... if (is_first_stage) { ... 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(); } }
编译Android系统源代码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,由init进程启动后,在运行过程当中建立和挂载的,以下代码所示。当系统终止时,这类目录就会消失。socket
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");[2] mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL);[3] #define MAKE_STR(x) __STRING(x) mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));[4] mount("sysfs", "/sys", "sysfs", 0, NULL);[5]
init进程建立系统运行所需的目录,造成下图所示的层次目录结构。在图中,[]内表示挂载在相应目录下的文件系统。ide
下列代码用于生成log设备并设置日志输出级别,以便输出init进程的运行信息。函数
open_devnull_stdio();[6] klog_init(); klog_set_level(KLOG_NOTICE_LEVEL);
init进程经过执行前面的代码生成/dev目录,包含系统中使用的设备,然后调用open_devnull_stdio()函数,建立运行日志输出设备。open_devnull_stdio()函数会优先使用/sys/fs/selinux/null设备节点文件,若是该节点不可用,则在/dev目录下生成__null__设备节点文件,最后再将标准输入、标准输出和标准错误输出所有重定向到__null__设备中,以下图所示。学习
由上图可见,open_devnull_stdio()函数将标准输入输出全都重定向到__null__设备中,为了查看进程输出的日志,init进程调用klog_init()[7]函数提供输出日志信息的设备,以下面的代码所示。日志
void klog_init(void) { if (klog_fd >= 0) return; /* Already initialized */ klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); if (klog_fd >= 0) { return; } static const char* name = "/dev/__kmsg__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { klog_fd = open(name, O_WRONLY | O_CLOEXEC); unlink(name); } }
init进程经过调用klog_init()函数初始化日志输出设备节点。klog_init()函数优先尝试打开“/dev/kmsg”节点文件,若是尝试失败,则生成“/dev/__kmsg__”设备节点文件。在底层该设备调用内核信息输出函数printk(),init进程最终是经过该函数来输出日志信息。如下代码所示是可用于输出信息的宏定义。code
#define ERROR(x...) init_klog_write(KLOG_ERROR_LEVEL, x) #define WARNING(x...) init_klog_write(KLOG_WARNING_LEVEL, x) #define NOTICE(x...) init_klog_write(KLOG_NOTICE_LEVEL, x) #define INFO(x...) init_klog_write(KLOG_INFO_LEVEL, x) #define DEBUG(x...) init_klog_write(KLOG_DEBUG_LEVEL, x) #define VERBOSE(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
这一小节咱们先讲解完init进程对目录生成和挂载、日志初始化和设置,接下来的一小节咱们将继续讲解SELinux的初始化过程。进程
[1] execv会中止执行当前的进程,而且以progname应用进程替换被中止执行的进程,进程ID保持不变。
[2] tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统彻底驻留在RAM中,读写速度远快于内存或硬盘系统。/dev目录保存着硬件设备访问所需的设备驱动程序。在Android中,将相关目录用做tmpfs,能够大幅提高设备访问的速度。
[3] devpts是一种虚拟终端文件系统,用来支持外部网络连接虚拟终端。
[4] proc是一种虚拟文件系统,只存在于内存中,而不占用外存空间。借助此文件系统,应用程序能够与内核内部数据结构进行交互。
[5] sysfs是一种特殊的文件系统,在Linux Kernel 2.6中引入,用于将系统中的设备组织成层次结构,统一proc、devfs、devpts这些文件系统,并将内核数据结构信息输出到用户空间。
[6] open_devnull_stdio()函数在system/core/init/util.cpp中定义。
[7] klog_init()函数在system/core/libcutils/klog.cpp中定义。