作为第一个内核启动的进程,init进程初始了化安卓运行环境。执行了文件夹建立,文件系统挂载,全局property初始化,建立监听 socket,根据init.rc文件启动service,执行action,等一系列操作,下面来具体看看在Android 4.0.3中的源代码具体实现:
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;
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
/* clear the umask */
umask(0);
/* Get the basic filesystem setup we need put
* together in the initramdisk on / and then we'll
* let the rc file figure out the rest.
*/
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));
/* We must have some place other than / to create the
* device nodes for kmsg and null, otherwise we won't
* be able to remount / read-only later on.
* Now that tmpfs is mounted on /dev, we can actually
* talk to the outside world.
*/
open_devnull_stdio();
klog_init();
INFO("reading config file\n");
init_parse_config_file("/init.rc");
/* pull the kernel commandline and ramdisk properties file in */
import_kernel_cmdline(0, import_kernel_nv);
/* don't expose the raw commandline to nonpriv processes */
chmod("/proc/cmdline", 0440);
get_hardware_name(hardware, &revision);
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {
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 (!strcmp(bootmode, "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");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
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;
#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;
}
从main函数中可以看到init程序具体做了一下几件事:
1.创建相应的文件目录,如:各种mkdir操作
2.挂载相应的文件系统,如:各种mount操作
3.初始化日志系统,打开/dev/null作为输出设备等系统初始化操作
4.解析init.rc文件操作,如:init_parse_config_file("/init.rc");
5.配置安卓系统环境变量操作,如:import_kernel_cmdline(0, import_kernel_nv);
6.添加各种action操作,如:action_for_each_trigger,queue_builtin_action;
7.循环执行action:execute_one_command;
8.启动或重启各种service:restart_processes;
9.poll相应的文件描述符,包括property service、signal和keychord对应的文件描述符。
下面来具体分析各项工作的实现细节:
1-3:这三个步骤较为简单,主要是一些linux系统调用,不具体阐述。
4:调用init_parse_config_file函数读取/init.rc中的配置信息,初始化action和service等,该函数的实现如下:
init_parse_config_file:
int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
可以看到函数定义了一个char*指针,然后调用
read_file函数将文件内容载入内存中并返回给data,并调用
parse_config函数进行解析。
read_file函数的逻辑较为简单,实现代码如下:
read_file:
void *read_file(const char *fn, unsigned *_sz)
{
char *data;
int sz;
int fd;
data = 0;
fd = open(fn, O_RDONLY);
if(fd < 0) return 0;
sz = lseek(fd, 0, SEEK_END);
if(sz < 0) goto oops;
if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
data = (char*) malloc(sz + 2);
if(data == 0) goto oops;
if(read(fd, data, sz) != sz) goto oops;
close(fd);
data[sz] = '\n';
data[sz+1] = 0;
if(_sz) *_sz = sz;
return data;
oops:
close(fd);
if(data != 0) free(data);
return 0;
}
函数首先通过open系统调用打开文件,然后调用lseek将文件定位到文件末端,返回的值表示文件的长度存放在变量sz中,然后在调用lseek重新定位到文件头部。接下来调用malloc申请大小为文件长度加上2的内存空间,并通过read将文件内容全部读入到内存,并把剩余两个字节分别置为换行和0,方便下面的文件内容解析操作,最后返回data。
将文件加载之后,通过parse_config对文件内容进行解析,这个解析过程有点意思,具体细节可以参考parse_config。简单地说,parse_config函数对init.rc文件中的service标签建立对应的service结构体,插入service_list;对on标签建立相应的action,设置action对应的commands以及将action插入action_list;对import标签则调用init_parse_config_file递归加载对应的文件内容,你可以理解为一条import是一个新的文件,在init.rc中将其替换为相应的文件内容进行解析。
另外在第5个操作之后,还会根据全局变量hardware再次载入一个rc文件并解析。
5:这个步骤主要调用的是import_kernel_cmdline(0, import_kernel_nv),这是第一次调用该方法,读取/proc/cmdline文件,然后根据空格将文件内容分割,每一部分通过调用import_nernel_nv函数,传入的参数是一个name=value类型的字符串,根据字符串将全局变量qemu、console、bootmode、serialno、baseband、carrier、bootloader、hardware设置成文件中相应的值。
static void import_kernel_nv(char *name, int in_qemu)
{
char *value = strchr(name, '=');
if (value == 0) return;
*value++ = 0;
if (*name == 0) return;
if (!in_qemu)
{
/* on a real device, white-list the kernel options */
if (!strcmp(name,"qemu")) {
strlcpy(qemu, value, sizeof(qemu));
} else if (!strcmp(name,"androidboot.console")) {
strlcpy(console, value, sizeof(console));
} else if (!strcmp(name,"androidboot.mode")) {
strlcpy(bootmode, value, sizeof(bootmode));
} else if (!strcmp(name,"androidboot.serialno")) {
strlcpy(serialno, value, sizeof(serialno));
} else if (!strcmp(name,"androidboot.baseband")) {
strlcpy(baseband, value, sizeof(baseband));
} else if (!strcmp(name,"androidboot.carrier")) {
strlcpy(carrier, value, sizeof(carrier));
} else if (!strcmp(name,"androidboot.bootloader")) {
strlcpy(bootloader, value, sizeof(bootloader));
} else if (!strcmp(name,"androidboot.hardware")) {
strlcpy(hardware, value, sizeof(hardware));
}
} else {
/* in the emulator, export any kernel option with the
* ro.kernel. prefix */
char buff[32];
int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
if (len < (int)sizeof(buff)) {
property_set( buff, value );
}
}
}
可以理解为从cmdline文件中设置系统参数。/proc/cmdline文件中的内容是一个name=value的形式,例如我手机上的文件如下:
6:这里主要有两个函数与action操作有关:
A:action_for_each_trigger(const char *name, void (*func)(struct action *act));
该函数会遍历action_list(现在action_list中的action都是根据.rc文件中添加的),对于每个名称等于“name”的action,调用action_add_queue_tail将其添加到action queue。注意到这里有两个action存储结构,action_list存储的是系统中所有的action,是静态的,而如果这些action需要被执行,必须添加到action queue中去,系统只执行位于队列中的action。
B:queue_builtin_action(int (*func)(int nargs, char **args), char *name);
该函数会根据参数name创建一个action结构,并根据函数指针func创建一个command结构,将其添加到action的command list中去,最后再把这个action添加到action list中去。
可以看到,除了rc文件中的action外,安卓额外还添加了一些重要的action:
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {
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 (!strcmp(bootmode, "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);
}
从各个action的名字也可以看出其相应的功能,由于安卓的property机制较为重要,这里只介绍property相对应的action,对于console和keychord的实现过程其实也是类似的。
property有点类似于windows中的注册表,保存系统的一些环境变量信息,通过property_get和property_set函数进行取值和设值。在实现中为了考虑效率,get是直接从共享内存中读取数据,而init进程中的set函数也是直接操作共享内存进行设值,但是当property service启动之后(其实就init程序本身,在property area初始化完成之后init便作为一个property service身份运行着),其他的程序调用set函数是通过socket发送消息给property service,然后由property service来直接修改共享内存中的service。
property_init_action,这个action会初始化property area,实际上调用的是property_init。
property_init首先调用init_property_area初始化property机制需要用到的共享内存区域,最后调用的是init_workspace函数,init_workspace函数首先打开或创建一个文件/dev/__properties__,并调用truncate函数将其大小调整为32KB,然后调用mmap把这个文件映射到内存空间,把这个内存地址赋值给全局变量pa_workplace中的data,也赋值给__system_property_area__,达到共享内存的效果。这个command被执行之后,property area就被初始化了,全局变量property_area_inited置为1。
property_init然后会调用load_properties_from_file,从文件中初始化一些property。依次调用的是load_properties和property_set,这里的property_set函数不同于后期其他程序调用的property_set,它是定义在property_service.c文件中,因为在初始化property时还没有创建相应的服务,所以只能直接对共享内存区域进行操作,后期的set函数是bionic库下的set函数,通过socket将消息发送给property service间接设定property。
set_init_properties_action,该action也是设定一些property,主要是根据文件/proc/cmdline文件内容来设置,实现代码如下:
static int set_init_properties_action(int nargs, char **args)
{
char tmp[PROP_VALUE_MAX];
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2");
else
property_set("ro.factorytest", "0");
property_set("ro.serialno", serialno[0] ? serialno : "");
property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
property_set("ro.baseband", baseband[0] ? baseband : "unknown");
property_set("ro.carrier", carrier[0] ? carrier : "unknown");
property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");
property_set("ro.hardware", hardware);
snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
property_set("ro.revision", tmp);
return 0;
}
再次回到set_init_propeties_action函数中,再继续设置一些property。
最后还有一个property_service_init_action,它调用的是start_property_service,虽然名字是启动property service,但本质上还是从多个文件中再次载入一些property,然后创建一个套接字并监听,专门用来处理property的set请求,并把这个socket对应的文件描述符赋值给全局变量property_set_fd,方便在init程序中进行poll操作。
总结来说,在init中和property相关的主要有三个:property_init_action,set_init_properties_action,property_service_init_action。三个action的工作分别为:初始化property共享内存区域;设置一些初始property;启动property监听套接字。这三个action执行完后,整个Android的property机制就运行起来了。
到目前为止,Android好像只是把action和service插入到了队列或者链表中,并未真正运行它们,相应的套接字也还未真正工作,所以init程序最后把7、8、9三个工作都放到了一个无限循环中去执行。
for(;;) {
int nr, i, timeout = -1;
/* 判断cur_action和cur_command是否为空等,cur_action为空的话从action_queue
* 队列中取出头部,然后尝试获取cur_action中一个新的command,通过调用cmd的
* func执行相应的函数。即每次循环都会执行一个cmd */
execute_one_command();
/* 实际上调用的是service_for_each_flags,对于service_list中的每一个service,
* 如果设置了SVC_RESTARTING标志位,执行restart_service_if_needed,不同于
* action,service在每次循环都会尝试restart所有的service */
restart_processes();
restart_service_if_needed函数的具体分析可以看 安卓启动服务。
接下来循环体中的三个for循环如下所示:
/* property_set_fd_init在第一次循环中还是0,start_property_service会将property_set_fd置为1,所以符合判断条件 */
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd(); /* 将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;
}
在循环体的最后会调用poll系统调用,根据不同的时间执行不同的操作
/* 循环遍历poll的socket数组,激活相应的操作 */
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();
}
}
handle_property_set_fd函数是对property service的文件描述符的处理函数,实现如下:
void handle_property_set_fd()
{
......
/* 后续程序调用property_set时是通过socket进行操作的,首先会和property service建立连接,所以需要先调用accept接收连接请求 */
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
......
switch(msg.cmd) {
case PROP_MSG_SETPROP:
/* 如果是一个set请求,调用property_set对property对应的共享内存进行设值 */
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if(memcmp(msg.name,"ctl.",4) == 0) {
......
} else {
if (check_perms(msg.name, cr.uid, cr.gid)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
close(s);
}
break;
default:
close(s);
break;
}
}
至此,整个init程序的执行流程分析完毕!