3.2 init分析
最后更新于:2022-04-02 05:48:38
init进程的入口函数是main,它的代码如下所示:
**init.c**
~~~
int main(int argc, char **argv)
{
intdevice_fd = -1;
intproperty_set_fd = -1;
intsignal_recv_fd = -1;
intkeychord_fd = -1;
int fd_count;
ints[2];
intfd;
structsigaction act;
chartmp[PROP_VALUE_MAX];
structpollfd ufds[4];
char*tmpdev;
char*debuggable;
//设置子进程退出的信号处理函数,该函数为sigchld_handler。
act.sa_handler = sigchld_handler;
act.sa_flags= SA_NOCLDSTOP;
act.sa_mask = 0;
act.sa_restorer = NULL;
sigaction(SIGCHLD, &act, 0);
......//创建一些文件夹,并挂载设备,这些是和Linux相关的,不拟做过多讨论。
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0,NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
//重定向标准输入/输出/错误输出到/dev/_null_。
open_devnull_stdio();
/*
设置init的日志输出设备为/dev/__kmsg__,不过该文件打开后,会立即被unlink了,
这样,其他进程就无法打开这个文件读取日志信息了。
*/
log_init();
//上面涉及很多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析
//解析init.rc配置文件
parse_config_file("/init.rc");
......
//下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名,我的HTCG7手机为bravo。
get_hardware_name();
snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);
//解析这个和机器相关的配置文件,我的G7手机对应文件为init.bravo.rc。
parse_config_file(tmp);
/*
解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句代码将执行那些处于
early-init阶段的Action。init将动作执行的时间划分为四个阶段:early-init、init、
early-boot、boot。由于有些动作必须在其他动作完成后才能执行,所以就有了先后之分。哪些
动作属于哪个阶段由配置文件决定。后面会介绍配置文件的相关知识。
*/
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();
/*
创建利用Uevent和Linux内核交互的socket。关于Uevent的知识,第9章中对
Vold进行分析时会做介绍。
*/
device_fd = device_init();
//初始化和属性相关的资源
property_init();
//初始化/dev/keychord设备,这和调试有关,本书不讨论它的用法。读者可以自行研究,
//内容比较简单。
keychord_fd = open_keychord();
......
/*
INIT_IMAGE_FILE定义为”/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机
画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件。
*/
if(load_565rle_image(INIT_IMAGE_FILE) ) {
/*
如果加载initlogo.rle文件失败(可能是没有这个文件),则会打开/dev/ty0设备,并
输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。
*/
......
}
}
if(qemu[0])
import_kernel_cmdline(1);
......
//调用property_set函数设置属性项,一个属性项包括属性名和属性值。
property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");
......//执行位于init阶段的动作
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
//启动属性服务
property_set_fd = start_property_service();
/*
调用socketpair函数创建两个已经connect好的socket。socketpair是Linux的系统调用,
不熟悉的读者可以利用man socketpair查询相关信息。后面就会知道它们的用处了。
*/
if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
signal_fd = s[0];
signal_recv_fd = s[1];
......
}
......
//执行配置文件中early-boot和boot阶段的动作。
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
......
//init关注来自四个方面的事情。
ufds[0].fd= device_fd;//device_fd用于监听来自内核的Uevent事件
ufds[0].events = POLLIN;
ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性服务器的事件
ufds[1].events= POLLIN;
//signal_recv_fd由socketpair创建,它的事件来自另外一个socket。
ufds[2].fd = signal_recv_fd;
ufds[2].events = POLLIN;
fd_count = 3;
if(keychord_fd > 0) {
//如果keychord设备初始化成功,则init也会关注来自这个设备的事件。
ufds[3].fd = keychord_fd;
ufds[3].events = POLLIN;
fd_count++;
}
......
#if BOOTCHART
......//与Boot char相关,不做讨论了。
/*
Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表,
以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。
*/
#endif
for(;;) {
//从此init将进入一个无限循环。
int nr, i, timeout = -1;
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
//在循环中执行动作
drain_action_queue();
restart_processes(); //重启那些已经死去的进程
......
#if BOOTCHART
...... // Boot Chart相关
#endif
//调用poll等待一些事情的发生
nr= poll(ufds, fd_count, timeout);
......
//ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。
if(ufds[2].revents == POLLIN) {
//有一个子进程去世,init要处理这个事情
read(signal_recv_fd, tmp, sizeof(tmp));
while (!wait_for_one_process(0))
;
continue;
}
if(ufds[0].revents == POLLIN)
handle_device_fd(device_fd);//处理Uevent事件
if(ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd);//处理属性服务的事件。
if(ufds[3].revents == POLLIN)
handle_keychord(keychord_fd);//处理keychord事件。
}
return0;
}
~~~
从上面的代码中可知,init的工作任务还是很重的。上面的代码虽已省略了不少行,可结果还是很长,不过从本章要分析的两个知识点来看,可将init的工作流程精简为以下四点:
- 解析两个配置文件,其中,将分析对init.rc文件的解析。
- 执行各个阶段的动作,创建Zygote的工作就是在其中的某个阶段完成的。
- 调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务。
- init进入一个无限循环,并且等待一些事情的发生。重点关注init如何处理来自socket和来自属性服务器相关的事情。
>[info] **提示**:精简工作流程,是以后分析代码时常用的方法。读者在分析代码的过程中,也可使用这种方法。
';