3.3 init进程的执行过程
init进程是用户空间的第一个进程,进程号为1。Android世界中,很多重要的工作都是从它开始的。
init进程相关源码位于/system/core/init目录下,从其编译文件Android.mk中可以看到这部分源代码最终被编译为可执行文件init,代码如下:
LOCAL_MODULE:=init//编译后的模块名,即可执行文件的名字
include$(BUILD_EXECUTABLE)//编译成可执行文件
分析可执行文件,首先需要在源码中找到main函数入口,它位于/system/core/init/init.c中,代码如下:
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;
/如果参数中传入ueventd,执行ueventd_main/
if(!strcmp(basename(argv[0]),"ueventd"))
return ueventd_main(argc, argv);
/清除系统默认权限,保证新创建目录的访问权限由mkdir设置,传入0相当于chmod 777/
umask(0);
/*创建基本文件系统目录并挂载相关的文件系统。Android分别挂载
*了tmpfs、devpts、proc、sysfs这四类文件系统。这几种文件
系统的参考文档位于/kernel/common/Documentation/filesystems目录下/
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);
/*indicate that booting is in progress to background
fw loaders, etc/
close(open("/dev/.booting",O_WRONLY|O_CREAT,0000));
/屏蔽标准输入输出,并初始化内核Log系统,日志信息将写入/dev/kmsg/
open_devnull_stdio();//不需要标准输入输出,重定向到null设备
klog_init();
/*调用init_property_area初始化属性系统所需的ashmem
(Android Shared Memory),对应于/dev/properties/
property_init();
/从/proc/cpuinfo中获取硬件信息/
get_hardware_name(hardware,&revision);
/解析init.rc初始化文件/
init_parse_config_file("/init.rc");
/*调用import_kernel_cmdline从/proc/cmdline读取内核启动参
数,然后调用export_kernel_boot_props在属性系统设置启动属性/
process_kernel_cmdline();
//安全相关的一些策略,参见SELINUX
ifdef HAVE_SELINUX
selinux_load_policy();
endif
//是否充电模式
is_charger=!strcmp(bootmode,"charger");
/非充电模式下,调用load_properties_from_file加载default.prop中设置的默认属性/
if(!is_charger)
property_load_boot_defaults();
/解析init.rc初始化文件/
init_parse_config_file("/init.rc");
/*触发init.rc中配置的early-init Action,并通过
*action_add_queue_tail函数将该Action放入可执行队列action_queue
队尾。其中early-init既是Action的名字,也是Action对应的触发器/
action_for_each_trigger("early-init",action_add_queue_tail);
/*触发内置Action,这里的内置是build in的意思。内置
**Action并没有在init.rc或者init.<hardware>.rc中配置。
queue_builtin_action函数的第一个参数是个函数指针,
*第二个参数是该Action的触发器,也是这个Action的名字。
函数指针的作用是把该触发器指定的Action放入可执行队列队尾/
queue_builtin_action(wait_for_coldboot_done_action,"wait_for_coldboot_done");
queue_builtin_action(keychord_init_action,"keychord_init");
//该Action执行时,会显示开机"A N D R O I D"文字
queue_builtin_action(console_init_action,"console_init");
/触发init Action/
action_for_each_trigger("init",action_add_queue_tail);
/充电模式下不加载以下文件系统,所以不需要把以下Action加入可执行队列/
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);
}
/触发内置Action/
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_propety_triggers");
/*如果定义了BOOTCHART宏,触发boot chart初始化的Action。boot chart是分析系
统启动过程的工具,它可以生成直观的启动图,用于理解启动过程或者分析系统启动速度/
if BOOTCHART
queue_builtin_action(bootchart_init_action,"bootchart_init");
endif
/init经过上面的初始化和触发Action的过程,进入一个无限循环,执行Command,处理事件/
for(;){
int nr, i,timeout=-1;
/执行当前Action的一个Command/
execute_one_command();
/*重新启动异常退出的Service。通过遍历service_list列表,找到flags设置为
需要重启的Service,调用restart_service_if_needed函数启动它/
restart_processes();
/监听来自属性服务property service的事件。属性服务是init提供的重要功能/
if(!property_set_fd_init&&get_property_set_fd()>0){
/get_property_set_fd得到property service的fd,是一个Socket/
ufds[fd_count].fd=get_property_set_fd();
/有POLLIN事件发生时,revents就会被置为POLLIN/
ufds[fd_count].events=POLLIN;//有数据可读的事件
ufds[fd_count].revents=0;
fd_count++;
property_set_fd_init=1;
}
/*监控signal。主要用于接收子进程异常退出后内核抛出的SIGCHLD信号,
*然后决定回收子进程资源或者重启子进程,防止子进程成为僵尸进程。
get_signal_fd(signal_recv_fd)位于sigchld_handler.c/
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;
}
/监听来自keychord设备的事件。keychord是组合按键设备/
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;
}
/如果有正要处理的Action,则设置timeout=0,即poll不阻塞/
if(!action_queue_empty()||cur_action)
timeout=0;
if BOOTCHART
//省略BOOTCHART部分
endif
/将ufds传入poll函数,监控事件的发生/
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();//处理keychord事件
else if(ufds[i].fd==get_signal_fd())
handle_signal();//处理signal事件
}
}
}
return 0;
}
注意 pollfd是Linux中定义的结构体,用于存放需要监控事件的文件描述符,其定义如下:
struct pollfd{
int fd;//需要监控的文件描述符
short events;//监控fd上的事件,由调用方设置
short revents;//fd上发生过的事件,由返回方设置
};
poll的函数原型如下:
int poll(struct pollfd fds[],nfds_t nfds, int timeout);
nfds:用于标记fds[]中结构体元素的总数。
timeout:用于标记poll函数调用的阻塞事件,单位是毫秒。如果timeout=0,则poll不阻塞,直接返回。poll返回fds中revents不为0的fd个数;如果超时没有任何事件发生,返回0;失败时,返回-1。
通过对init.c文件中main函数的分析,可以将init的执行过程分为以下四个阶段:
1)初始化文件系统和日志系统,为之后的执行阶段做准备。这部分主要是Linux标准函数的调用。
2)解析init.rc和init.<hardware>.rc初始化文件。
3)触发需要执行的Action和Service。
4)init循环监听处理事件。init触发所有Action后,进入一个无限循环,执行在可执行队列中的命令,重启异常退出的Service,并循环处理来自property service(属性服务)、signal和keychord的事件。
第一阶段很容易理解,都是基本的Linux函数调用。接下来,将针对其余三个阶段详细讲解init的运行过程。