init进程是用户级的第一个进程(UserPlace)
在Linux中所有进程都是init的子进程,Linux中一切都是以文件的形式存在的,主要提供四大功能
处理Action
职责关注部分:
创建zygote进程
启动属性服务
性能分析(BootChart)
无线循环(启动其他的进程)
init进程的入口函数是main,它的代码如下所示:
[-->init.c]
int main(int argc, char **argv)
{
intdevice_fd = -1;
intproperty_set_fd = -1;
intsignal_recv_fd = -1;
intkeychord_fd = -1;
int fd_count;
ints[2];
intfd;
structsigaction act;
chartmp[PROP_VALUE_MAX];
structpollfd 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);
**......//创建一些文件夹,并挂载设备,这些是和Linux相关的,不拟做过多讨论。**
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0,NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
//重定向标准输入/输出/错误输出到/dev/_null_。
open_devnull_stdio();
/*
设置init的日志输出设备为/dev/__kmsg__,不过该文件打开后,会立即被unlink了,
这样,其他进程就无法打开这个文件读取日志信息了。
*/
log_init();
//上面涉及很多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析
****//解析init.rc配置文件****
parse_config_file("/init.rc");
......
//下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名,我的HTCG7手机为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。关于Uevent的知识,第9章中对
Vold进行分析时会做介绍。
*/
device_fd = device_init();
**//初始化和属性相关的资源
property_init();**
//初始化/dev/keychord设备,这和调试有关,本书不讨论它的用法。读者可以自行研究,
//内容比较简单。
keychord_fd = open_keychord();
......
/*
INIT_IMAGE_FILE定义为”/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机
画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件。
*/
if(load_565rle_image(INIT_IMAGE_FILE) ) {
/*
如果加载initlogo.rle文件失败(可能是没有这个文件),则会打开/dev/ty0设备,并
输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。
*/
......
}
}
if(qemu[0])
import_kernel_cmdline(1);
......
//调用property_set函数设置属性项,一个属性项包括属性名和属性值。
property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");
......//执行位于init阶段的动作
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
**//启动属性服务**
property_set_fd = start_property_service();
/*
调用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];
......
}
......
//执行配置文件中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();
......
//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;
//signal_recv_fd由socketpair创建,它的事件来自另外一个socket。
ufds[2].fd = signal_recv_fd;
ufds[2].events = POLLIN;
fd_count = 3;
if(keychord_fd > 0) {
//如果keychord设备初始化成功,则init也会关注来自这个设备的事件。
ufds[3].fd = keychord_fd;
ufds[3].events = POLLIN;
fd_count++;
}
......
#if BOOTCHART
......//与Boot char相关,不做讨论了。
/*
Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表,
以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。
*/
#endif
for(;;) {
**//从此init将进入一个无限循环。**
int nr, i, timeout = -1;
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
//在循环中执行动作
drain_action_queue();
restart_processes(); //重启那些已经死去的进程
......
#if BOOTCHART
...... // Boot Chart相关
#endif
//调用poll等待一些事情的发生
nr= poll(ufds, fd_count, timeout);
......
//ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。
if(ufds[2].revents == POLLIN) {
//有一个子进程去世,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事件。
}
return0;
}
1.1.1 解析配置文件init.rc
还有一个rc文件是xx.rc(xx是硬件名),这个文件只是系统的一个镜像文件,更改也没有用。如想改变则需要在boot.ing核镜像中修改。有一个函数parseconfig函数进行解析,其中有一个Section Zygote被放在一个Service中。
1.1.2 解析规则
使用的是Android Init Language 编译的脚本
分为 :
一个Section 的结构一般是:
on <trigger>
<command>
<command>
service <name> <pathname> [ <argument> ]*
#是注释 ,on init 和 onboot是Action类型。
解析rc文件的详细内容:
http://blog.csdn.net/yangwen123/article/details/9029959
1.1.3解析Service
init中使用一个结构体来保存Service Section 相关信息(使用Socket,action结构体) 函数ParseService 搭建一个servce的架子。
Parse_line_service 将根据配置文件的内容填完service结构体
zygote 解析后的结果:service List 将解析后的Service全部连在一起。
Socketinifo -> Socket链表
onrestart commands 指向一个 Commands 链表
1.1.4 init控制service
其中sygote 就是在其中一个Service中
在rc 文件中一个service
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
class_start default ->是一个COMMAND 位于boot(引导) section 范围内。
最后执行service_start函数,并在启动前要判断可执行文件,是否存在 /system/bin/applprocress
主要做了以下几个工作:
1.1.5 重启Zygote
onrestart是在zygote重启时用的,当内存不足时,Android系统会自动杀死一下进程来释放空间,所以当某些重要的服务被杀,同时该服务进程并未设置为oneshot,则必须重新启动该服务进程。
zygote死后父进程init 会通过Socket接收数据执行代码(Main函数中)->init.rc.main中工作如下:
static int wait_for_one_process(int block)
{ //block = 0 -->false
pid_t pid;
int status;
struct service *svc;
struct socketinfo *si;
time_t now;
struct listnode *node;
struct command *cmd;
/*当进程被终止时,将发送SIGCHLD信号,waitpid()函数用来回收进程所占用的资源,第一个参
数pid是指欲等待的子进程的识别码,设置为-1表示查看所有子进程是否发出SIGCHIL信号,第二
个参数status用于返回子进程的结束状态;第三个参数决定waitpid()函数是否应用阻塞处理方式。
waitpid()函数返回产生SIGCHID信号的进程pid */
while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
//正常情况下返回的死亡进程pid大于0,因此wait_for_one_process的返回值正常情况下为0
if (pid <= 0) return -1;
INFO("waitpid returned pid %d, status = %08x\n", pid, status);
//用于根据pid值在服务链表中查找对应的服务
svc = service_find_by_pid(pid);
if (!svc) {
ERROR("untracked pid %d exited\n", pid);
return 0;
}
NOTICE("process '%s', pid %d exited\n", svc->name, pid);
/* 检查服务是否设置了oneshot标志,SVC_ONESHOT表示进程仅运行一次,如果没有设置SVC_ONESHOT标志,
表示需要重启该服务进程,首先将该服务进程组下的所有子进程杀死 */
if (!(svc->flags & SVC_ONESHOT)) {
kill(-pid, SIGKILL);
NOTICE("process '%s' killing any children in process group\n", svc->name);
}
/* 删除该服务进程下的创建的所有socket */
for (si = svc->sockets; si; si = si->next) {
char tmp[128];
snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
unlink(tmp);
}
//设置服务的pid为0 ,并清除SVC_RUNNING标志
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
/* 如果设置了SVC_ONESHOT标志,表示服务只能运行一次,因此设置表示位SVC_DISABLED */
if (svc->flags & SVC_ONESHOT) {
svc->flags |= SVC_DISABLED;
}
/* 判断服务标志是否设置了SVC_DISABLED 或 SVC_RESET 对于设置了这两种标志的进程是不能重启的 */
if (svc->flags & (SVC_DISABLED | SVC_RESET) ) {
//设置进程运行状态属性值为stopped
notify_service_state(svc->name, "stopped");
return 0;
}
now = gettime();
//如果死亡的服务进程是系统关键进程,则直接重启手机
if (svc->flags & SVC_CRITICAL) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
ERROR("critical process '%s' exited %d times in %d minutes; "
"rebooting into recovery mode\n", svc->name,
CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
//手机重启
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
return 0;
}
} else {
svc->time_crashed = now;
svc->nr_crashed = 1;
}
}
//设置服务进程标志SVC_RESTARTING,在restart_processes()函数中会重启持有SVC_RESTARTING
svc->flags |= SVC_RESTARTING;
/* 运行该service下所有Execute all onrestart commands for this service. */
list_for_each(node, &svc->onrestart.commands) {
cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
//设置进程运行状态属性值为stopped
notify_service_state(svc->name, "restarting");
return 0;
}
系统应用程序将会存储一些属性进入属性表中。即时系统重启,注册表应用程序重启,也能初始化。
property_service (属性服务机制)(socket服务)
属性文件是一些位列于不同目录,系统依次读取的配置文件。
1.启动(Socket服务)
init.c文件中函数()与属性服务有关的代码
property_init();
property_set_fd=start_property_service();
1.2.1 属性服务的初始化
在前面分析main函数时涉及到一个property_init函数,该函数调用了init_property_area函数,该函数用于初始化属性内存区域,也就是system_property_area变量。
这个函数加载defaut.prop文件(属性文件)
虽然init进程创建但希望其他进程也能读到内存中的东西,于是做了以下两项工作:
1.2.2 属性服务器分析
(1)start_property_service函数,该函数在Property_service.c文件中,该文件与init.c文件中同一个目录。
函数如下:
void start_property_service(void)
{
int fd;
// 装载不同的属性文件
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_override_properties();
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
// 创建socket服务(属性服务)
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd < 0) return;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
// 开始服务监听
listen(fd, 8);
property_set_fd = fd;
}
在start_property_service 中执行了:load_persistent_properties();
// 创建socket服务(属性服务)。客户端只通过对此进行属性设置。
并定义两个宏(和系统预定属性文件路径有关)
将属性文件加载到属性空间中,四个储存属性的文件分别是:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
(2)处理设置请求(在init进程for循环中):
handle_property_set_fd进行处理。权限满足,调用property_set进行处理。
(3)客户端发送请求。
property_set 发送请求,由libcutils库进行提供。propertis.c文件中。