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的运行过程。