android的init过程分析
前言Android系统是运作在linux kernal上的,因此它的启动过程也遵循linux的启动过程,当linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,它的生命周期贯穿整个linux 内核运行的始终,linux中所有其他的进程的共同始祖均为init进程。当然为了启动并运行整个android系统,google实现了自己的init进程,下面主要分析init进程都做了些什么?
1.首先,init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间,当程序表的空间达到上限时,则系统就不能再启动新的进程了,那么就会引起很严重的系统问题。
在linux当中,父程序是通过捕捉SIGCHLD信号来得知子进程结束的情况的;由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位的含义是就是要求系统在子进程暂停时不发送SIGCHLD信号。具体的代码如下所示:
struct sigaction act;
………………
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
act.sa_mask = 0;
act.sa_restorer = NULL;
sigaction(SIGCHLD, &act, 0);
2.创建文件系统目录并挂载相关的文件系统
/* 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", 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);
2.1 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响.
2.2 在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs 4类文件系统
2.2.1tmpfs文件系统
tmpfs是一种虚拟内存文件系统,因此它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有内容均为临时性的内容,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。
tmpfs有些像虚拟磁盘(ramdisk),但不是一回事。说其像虚拟磁盘,是因为它
可以使用你的RAM,但它也可以使用你的交换分区。传统的虚拟磁盘是一个块设
备,而且需要一个mkfs之类的命令格式化它才能使用。tmpfs是一个独立的文件系
统,不是块设备,只要挂接,立即就可以使用。
tmpfs的大下是不确定的,它最初只有很小的空间,但随着文件的复制和创建,
它的大小就会不断变化,换句话说,它会根据你的实际需要而改变大小;tmpfs的速
度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越;
由于tmpfs是驻留在RAM的,因此它的内容是不持久的,断电后,tmpfs的内容就消失
了,这也是被称作tmpfs的根本原因。
关于tmpfs文件系统请参考linux内核文档:
kernel/Documentation/filesystems/tmpfs.txt
2.2.2devpts文件系统
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要
pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文
件。
2.2.3 proc文件系统
proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
在proc文件系统中,你可以修改内核的参数,是不是很强大?怎么修改呢?你只需要echo一个新的值到对应的文件中即可,但是如果在修改过程中发生错误的话,那么你将别无选择,只能重启设备。
关于tmpfs文件系统请参考linux内核文档:
kernel/Documentation/filesystems/proc.txt
2.2.4 sysfs文件系统
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系
统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系
统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
3.屏蔽标准的输入输出,即标准的输入输出定向到NULL设备。
这一步是通过调用函数open_devnull_stdio实现的,下面我们研究一下open_devnull_stdio的函数实现
void open_devnull_stdio(void)
{
int fd;
static const char *name = "/dev/__null__";
//创建一个字符专用文件(character special file) /dev/__null__
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
//获取/dev/__null__的文件描述符,并输出该文件
fd = open(name, O_RDWR);
unlink(name);
//将与进程相关的标准输入(0),标准输出(1),标准错误输出(2),均定向到NULL设备
if (fd >= 0) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) {
close(fd);
}
return;
}
}
exit(1);
}
这里解释一下
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
过程:
首先说明以下dup2的作用,这个函数主要是复制一个函数的描述符,一般用于重定向进程的stdin,stdout,stderr。它的原型如下:
int dup2(int oldfd, int newfd);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
这三次调用一次将依次代表stdin,stdout,stderr的描述符0,1,2,重定向到dev/null,通过这种方式达到屏蔽标准输入输出的作用。
4. 初始化内核log系统
这个过程对应的源码为:
log_init();
这个函数详细实现为
void log_init(void)
{
static const char *name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
log_fd = open(name, O_WRONLY);
//当进程在进行exec系统调用时,要确保log_fd是关闭的(通过FD_CLOEXEC标志位来设置).
fcntl(log_fd, F_SETFD, FD_CLOEXEC);
unlink(name);
}
}
有上述实现看出内
补充:移动开发 , Android ,