您的位置:首页 > 移动开发 > Android开发

深入理解init_1----init分析(基于Android 2.2,源码来自Google)

2017-02-28 16:56 746 查看

深入理解init_1—-init分析

1、概述

Init是一个进程,确切的说,它是Linux系统中用户空间的第一个进程。由于android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1.作为天字第一号进程,init被赋予了很多及其重要的工作职责,下面就关注其中两个比较重要的职责:

1) Init 进程负责创建系统中几个关键进程,尤其是下一章要介绍的zygote,它更是java世界的开创者。那么init进程如何创建zygote的呢?

2) Android系统有很多属性,于是init就提供了一个property service (属性服务)来管理他们。那么这个属性服务怎样工作的呢?

下面我们就这两个问题来分析init:

(1) Init 如何创建 zygote?

(2) Init的属性服务是如何工作的。



init.c 主要函数功能概略图


2、init分析

2.1、文件位置

System/core/init.c

2.2部分代码分析

int main(int argc, char **argv)
{
int device_fd = -1;
int property_set_fd = -1;
int signal_recv_fd = -1;
int keychord_fd = -1;
int fd_count;
int s[2];
int fd;
struct sigaction act;
char tmp[PROP_VALUE_MAX];
struct pollfd 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);

/* 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.
*/
//创建一些文件夹,并挂载设备,这些是与Linux相关的
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);

mount("tmpfs", "/dev", "tmpfs", 0, "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);

/* 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.
*/
//重定向标准输入/输出/错误输出到 /dev/_null_
open_devnull_stdio();
/*
设置init的日志输出设备为 /dev/_kmsg_,不过这个文件被打开后,会立即被UNLINK了,这样,其他进程就没有办法打开这个文件读取日志信息。
*/

log_init();

//解析init.rc 配置信息
INFO("reading config file\n");
parse_config_file("/init.rc");

/* pull the kernel commandline and ramdisk properties file in */
qemu_init();
import_kernel_cmdline(0);

//下面这个函数通过读取/proc/cpuinfo 得到机器的Hardware名,笔者手机是HTC G7,手机为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
INFO("device init\n");
device_fd = device_init();
//初始化和属性相关的资源
property_init();

// only listen for keychords if ro.debuggable is true
//初始化 /dev/keychord设备
keychord_fd = open_keychord();

if (console[0]) {
snprintf(tmp, sizeof(tmp), "/dev/%s", console);
console_name = strdup(tmp);
}
fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);
/*INIT_IMAGE_FILE 定义为“/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件,如果加载 initlogo.rle文件失败(可能是没有这个文件),则会打开 /dev/ty0设备,并会输出“ANDROID” 字样作为开机动画,在模拟器上看到的开机动画就是它*/
if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"  // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"             A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}

if (qemu[0])
import_kernel_cmdline(1);

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 函数设置属性项,一个属性项包括属性名和属性值。
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);

/* execute all the boot actions to get us started */
//执行位于init阶段的过程
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();

/* read any property files on system or data and
* fire up the property service.  This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
//启动属性服务
property_set_fd = start_property_service();

/* create a signalling mechanism for the sigchld handler */
//调用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];
fcntl(s[0], F_SETFD, FD_CLOEXEC);
fcntl(s[0], F_SETFL, O_NONBLOCK);
fcntl(s[1], F_SETFD, FD_CLOEXEC);
fcntl(s[1], F_SETFL, O_NONBLOCK);
}

/* make sure we actually have all the pieces we need */
if ((device_fd < 0) ||
(property_set_fd < 0) ||
(signal_recv_fd < 0)) {
ERROR("init startup failure\n");
return 1;
}

/* execute all the boot actions to get us started */
//执行文件中的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();

/* run all property triggers based on current state of the properties */
queue_all_property_triggers();
drain_action_queue();

/* enable property triggers */
property_triggers_enabled = 1;
//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;
ufds[2].fd = signal_recv_fd;    //signal_recv_fd由socketpair创建,它的时间来自于另一个socket
ufds[2].events = POLLIN;
fd_count = 3;

if (keychord_fd > 0) {
ufds[3].fd = keychord_fd;       //如果keychord设备初始化成功,则init也会关注来自这个设备的事件
ufds[3].events = POLLIN;
fd_count++;
} else {
ufds[3].events = 0;
ufds[3].revents = 0;
}

#if BOOTCHART
// Boot chart 是一个小工具,它能对系统的性能进行分析,并且生成系统启动过程的图表,以提供一些有价值的信息,而这些信息的作用是帮助提升系统的启动速度。
bootchart_count = bootchart_init();
if (bootchart_count < 0) {
ERROR("bootcharting init failure\n");
} else if (bootchart_count > 0) {
NOTICE("bootcharting started (period=%d ms)\n", bootchart_count*BOOTCHART_POLLING_MS);
} else {
NOTICE("bootcharting ignored\n");
}
#endif
//从此init将进入一个无限循环。
for(;;) {
int nr, i, timeout = -1;

for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
//在循环中执行动作
drain_action_queue();
restart_processes();  //重启那些已经死去的进程

if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

#if BOOTCHART
//和  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

//调用PULL等待一些事情的发生
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
//ufds[2] 保存的是signal_recv_fd,用于接收来自socket的消息。
if (ufds[2].revents == POLLIN) {
/* we got a SIGCHLD - reap and restart as needed */
/*有一个子进程去世,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事件
}

return 0;
}


从上面的代码可以得到,init的工作任务很重,上面的代码省略了不少,可结果还是很长,从上面分析的结果来看,可将init的工作流程精简为以下四点:

1) 解析两个配置文件,我们将分析其中一个init.rc 文件。

2) 执行各个阶段的动作,创建zygote就是在其中某个阶段完成的。

3) 调用property_init ,初始化属性相关的资源,并且通过property_start_service启动属性服务。

4) Init进入一个无限循环,并且等待一些事情的发生。重点可关注init如何处理来自socket和来自属性服务的相关事情。

文档参考:

整理抄录自 — 《深入理解Android卷1》,邓凡平著。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android