Android-深入理解init

1. init

1.1 init介绍

  init是一个进程,它是Linux系统中用户空间的第一个进程,因为Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1,本节关注几个重要的职责:
  1.init进程负责创建系统中的几个关键进程,尤其是zygote(后面会讲)
  2.Android系统有很多属性,于是init就提供了一个property service(属性服务)来管理他们,那么这个属性服务是怎么工作的呢?

1.2 学习路线

  1.init如何创建zygote
  2.init的属性服务是如何工作

1.3 init分析

init进程的入口函数main,代码如下:

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 *tmpdec;
	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("/dec/socker", 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();

	//解析init.rc配置文件
	parse_config_file("/init.cr");

	//.....
	//下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名
	get_hardware_name();
	snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
	//解析这个和机器相关的配置文件 这里对应的文件为init.bravo.rc
	parse_config_file(tmp);

	/*
	 * 解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句
	 * 代码将执行那些处于early-init阶段的Action. init将动作执行的时间划
	 * 分为四个阶段,所以就有了先后之分。那些动作属于哪个阶段由配置文件
	 * 决定,后面会介绍配置文件的相关知识。
	 *
	 * */

	action_for_each_trigger("early-init", action_add_queue_tail);
	drain_action_queue();

	/* 创建利用Uevent与Linux内核交互的socket,关于Uevent的知识,第九章
	 * 中对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();

	/*
	 * 调用sockerpair函数创建两个已经connect好的socker,socketpair是Linux
	 * 的系统调用,man socketpair可以查到
	 * */
	if(sockerpair(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事件
	}

	return 0;
}

根据本章学习内容,可以把init的工作流程分为以下四点:
  1.解析两个配置文件,我们将分析其中对init.rc文件的解析
  2.执行各个阶段的动作,创建zygote的工作就是其中的某个阶段完成的
  3.调用property_init初始化属性相关的资源,并且通过property_start_service启动属性服务
  4.init进入一个无限循环,并且等待一些事情的发生,重点关注init如何处理来自socket和来自属性服务器的相关事情。

1.3.1 解析配置文件

  在init中会解析两个配置文件,其中一个是系统配置文件init.rc,另一个是与硬件平台相关的配置文件,以HTC G7手机为例,这个配置文件为init.bravo.rc,其中bravo是硬件平台的名称,对于这两个配置文件进行解析,调用的是同一个parse_config_file函数。

int parse_config_file(const char* fn)
{
	char* data;
	data = read_file(fn, 0);//读取配置文件的内容,这个文件时init.rc
	if(!data) return -1;
	parse_config(fn, data);//调用parse_config做到真正的解析
	return 0;
}

  读取完文件的内容后,将调用parse_config进行解析,这个函数的代码如下:
在这里插入图片描述
在这里插入图片描述
  上面就是parse_config函数,这个函数首先会找到配置文件的一个section,然后针对不同的section使用不同的解析函数去解析,section和init.rc文件的组织结构有关。

1.关键字定义
keyword.h的定义
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
keywords.h做了两件事:
  1.第一次包含keywords.h时,声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class, K_mkdir等关键字
  2.第二次包含keywords.h后,得到了一个keyword_info结构体数组,这个keyword_info结构体数组以前面定义的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称,处理函数,处理函数的参数个数,以及属性。
  目前,关键字信息中最重要的就是symbol和flags了,什么样的关键字被认为是section呢,根据keywords.h的定义,当symbol为service的时候表示section:

KEYWORD (on, SECTION, 0, 0)
KEYWORD (service, SECTION, 0, 0)

2.解析init.rc
在这里插入图片描述
分析可知:
  1.一个section的内容从这个标识section的关键字开始,到下一个标识section的地方结束
  2.init.rc中出现了名为boot和init的section,这里的boot和init就是前面介绍的4个动作执行阶段中的boot和init,也就是说,在boot阶段执行的动作都是由boot这个section定义的。

1.3.2 解析service

解析service的入口函数是parse_new_section,代码如下:
在这里插入图片描述
  其中,解析service时,用到了parse_service和parse_line_service这两个函数,先看init怎么组织service的

1.service结构体
  init中使用了一个叫service的结构体来保存与service section相关的信息,代码如下:

在这里插入图片描述
service中使用的这个action结构体
在这里插入图片描述
  parse_line_service 将根据配置文件的内容填充service结构体,那么,zygote解析完后会得到什么呢?

在这里插入图片描述
图中可知:
  1.service_list链表将解析后的service全部链接到一起,并且是一个双向链表,前向节点用prev表示,后向节点用next表示
  2.socketinfo也是一个双向链表,因为zygote只有一个socket,所以画了一个虚框socker作为链表的示范
  3.onrestart通过commands指向了一个commands链表,zygote有三个commands

1.3.3 init控制service

1.启动zygote
init.rc中有这样一句话

#class_start是一个COMMAND,对应的函数为do_class_start,很重要,切记
class_start default

  class_start标识一个COMMAND,对应的处理函数为do_class_start,它位于bootsection的范围内,当init进程执行到下面几句话时,do_class_start就会被执行了。

//将boot section节的command加入到执行队列
action_for_each_trigger("boot", action_add_queue_tail);
//执行队列里的命令,class是一个COMMAND,所以它对应的do_class_start会被执行。
drain_action_queue();

2.重启zygote

  onrestart是在zygote重启时使用的,zygote死后,父进程init的动作:

static void sigchld_handler(int s)
{
	//当子进程退出时,init的这个信号处理函数会被调用
	write(signal_fd, &s, 1);//往signal_fd中写数据
}

  signal_fd就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据,那么另一个socket就一定能接受到,这样就会导致init从poll函数中返回

1.3.4 属性服务

  Windows平台上面有一个叫做注册表的东西,注册表可以存储一些类型key/value的键值对,一般而言,系统或某些引用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能够根据之前在注册表中设置的属性进行相应的初始化工作,Android平台也提供了一个类似的机制,称为属性服务,应用程序可以通过这个属性机制,查询或设置属性,读者可以用adb shell登录到真机或模拟器上,然后用getprop命令查看当前系统中有哪些属性。

  其中与init.c和属性服务有关的代码有下面两行:

proprety_init();
property_set_fd = start_property_service();

1.属性服务初始化
  创建存储空间,proprety_init函数代码如下:

void property_init(void)
{
	init_property_area();//初始化属性存储区域
	//加载default.prop文件
	load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}

  在properyty_init函数中,先调用init_property_area函数,创建一块用于存储属性的存储区域,然后加载default.prop文件中的内容,再看init_property_area是如何工作的,代码如下:
在这里插入图片描述
在这里插入图片描述
  最后的赋值语句,_system_property_area_是bionic libc库中输出的一个变量,为啥要给他赋值?
  因为虽然属性区域是由init进程创建的,但Android系统希望其他进程也能读取这块内存里的东西,为了做到这一点,他便做了以下两项工作:
  1.把属性区域创建在共享内存上,而共享内存是可以跨进程的,这一点,已经在上面的代码中可以看见了,init_workspace函数内部将创建这个共享内存。
  2.如何让别人知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic lib库被加载时,将自动调用这个_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作。

客户端进程获取存储空间

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  上面代码的很多地方都和共享内存有关,后面讲,通过这种方式,客户端进程可以直接读取属性空间,但没有权限设置属性,客户端进程要如何设置属性呢?

2.属性服务器的分析
启动属性服务器
  init进程会启动一个属性服务器,而客户端之只能通过与属性服务器交互来设置属性,先来看属性服务器的内容,它由start_property_service函数启动。
在这里插入图片描述
  属性服务创建了一个用来接受请求的socket,可这个请求在哪里被处理呢,事实上,在init中的for循环处已经进行了相关处理了。

处理设置属性请求
  接受请求的地方在init进程中,代码如下:

if (ufds[1].revents == POLLIN)
	handle_property_set_fd(property_set_fd);

  当属性服务器收到了客户端请求时,init会调用handle_property_set_fd进行处理,代码如下:
在这里插入图片描述
在这里插入图片描述
  当客户端的权限满足要求时,init就调用property_set进行相关处理,代码如下:
在这里插入图片描述
在这里插入图片描述
客户端发送请求
  客户端通过property_set发送请求,property_set由libcutils库提供的。
在这里插入图片描述
在这里插入图片描述

1.4 总结

  介绍了init进程如何解析zygote,以及属性服务器的工作原理,init同时还涉及很多Linux系统相关的知识。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天津 唐秙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值