init进程源于Linux,是Linux内核启动后运行的第一个进程。其他所有系统运行所需的进程都由init进程来创建。
在系统启动完成后,init进程会作为守护进程监视其他进程。
对于终结或者进入僵死状态的进程,init会强制释放该进程所占用的所有系统资源。
Android系统以Linux内核为基础,不可避免地集成了init进程的很多特点。
概述
与Linux类似,init是Android系统启动后由内核运行的第一个进程,启动过程如下图所示:
Linux系统启动时依次执行start_kernel()函数、init_post()函数和run_init_process()函数,之后开始运行init进程。Android的init进程主要提供4个功能:
- 分析启动init.rc脚本文件
- 为APP访问设备驱动生成设备节点文件
- 子进程的创建和终止
- 提供property service属性服务,进行Android系统的属性管理
static noinline int init_post(void)
{
if(execute_command){
run_init_process(execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel"
"See Linux Documentation/init.txt for guidance.");
}
init_post()函数通过if判断语句调用run_init_process()函数获取execute_command中注册的进程文件路径。
后续的run_init_process()函数分别在各个子目录下查找init文件,并启动init进程。
init进程
本小节参考链接:https://www.cnblogs.com/nokiaguy/archive/2013/04/14/3020774.html
增加了一点代码注释
init进程与其他进程一样,由一个main()函数作为入口。
int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;
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);
// 下面的代码开始建立各种用户空间的目录,如/dev、/proc、/sys等
// 这些目录由init进程生成,当系统终止时被撤销
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
/* 检测/dev/.booting文件是否可读写和创建*/
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
open_devnull_stdio();
// 设置init的日志输出设备文件为/dev/_kmsg_
klog_init();
// 初始化属性
property_init();
// 通过读取/proc/cpuinfo获得硬件的hardware名称
get_hardware_name(hardware, &revision);
// 处理内核命令行
process_kernel_cmdline();
… …
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
INFO("reading config file\n");
// 分析/init.rc文件的内容
init_parse_config_file("/init.rc");
... ...// 执行初始化文件中的动作
action_for_each_trigger("init", action_add_queue_tail);
// 在charger模式下略过mount文件系统的工作
if (!is_charger) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", 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");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
// 进入无限循环,建立init的子进程(init是所有进程的父进程)
for(;;) {
int nr, i, timeout = -1;
// 执行命令(子进程对应的命令)
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 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是一个性能统计工具,用于搜集硬件和系统的信息,并将其写入磁盘,以便其
// 他程序使用
#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
// 等待下一个命令的提交
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
根目录有一个init.rc文件。该文件是只读的,即使有了root权限,可以修改该文件也没有。因为我们在根目录看到的文件只是内存文件的镜像。也就是说,android启动后,会将init.rc文件装载到内存。而修改init.rc文件的内容实际上只是修改内存中的init.rc文件的内容。一旦重启android,init.rc文件的内容又会恢复到最初的装载。想彻底修改init.rc文件内容的唯一方式是修改Android的ROM中的内核镜像(boot.img)。其实boot.img名曰内核镜像,不过该文件除了包含完整的Linux内核文件(zImage)外,还包括另外一个镜像文件(ramdisk.img)。ramdisk.img就包含了init.rc文件和init命令。所以只有修改ramdisk.img文件中的init.rc文件,并且重新打包boot.img文件,并刷机,才能彻底修改init.rc文件。如果读者有Android源代码,编译后,就会看到out目录中的相关子目录会生成一个root目录,该目录实际上就是ramdisk.img解压后的内容。会看到有init命令和init.rc文件。
脚本文件init.rc和init.{hardware}.rc
init.rc在系统运行过程中用于通用的环境变量设置,以及与进程相关的定义。
init.{hardware}.rc文件用于定义Android在不同硬件平台下的特定进程和环境设置等。
这两个文件的处理方式和语法是相同的。
init.rc文件可以分为两部分
- on开头的动作列表 action list。用于创建所需目录,以及为特定文件指定权限
- service开头的服务列表,用来记录初始化程序所需要启动的一些程序。 service用来记录init进程启动。由init进程启动的子进程——daemon进程启动
# on init的环境变量设置部分主要设置运行根文件系统命令的目录,以及程序编译时需要的库目录
on init
sysclktz 0
loglevel 3
# setup the global environment
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
export LD_LIBRARY_PATH /vendor/lib/:/system/lib
... ...
# 在根文件系统主要挂载/system与/data两个目录
on fs
# mount mtd partitions
# mount /system rw first to give the filesystem a chance to save a checkpoint
mount yaffs2 mtd@system /system
mount yaffs2 mtd@system /system ro remount
mount yaffs2 mtd@userdata /data nosuid nodev
mount yaffs2 mtd@cache /cache nosuid nodev
on post-fs-data
# We chown/chmod /data again so because mount is run as root + defaults
chown system system /data
chown 0771 data
## Daemon processes to be run by init
##
# 第一个字符串console是服务的名称
# 第二个字符串表示服务的路径
# 第二行是服务的内容,主要包含运行权限、条件及重启等相关选项
service console /system/bin/sh
class core
console
disabled
user shell
group log
on property:ro.debuggable=1
start console
# adb is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd
class core
socket adbd stream 660 system system
disabled
seclable u:r:adbd:s0
# adbd on at boot in emulator
on property:ro.kernel.qemu=1
start adbd
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
service vold /system/bin/vold
class core
socket vold stream 0660 root mount
ioprio be 2