安卓底层开发学习经验第十四期
这一期我们来看一下init进程是如何守护我们的服务
我们init进程在守护阶段做的工作有三个,第一个是启动我们的服务,执行我们脚本的命令,第二个是接受shell或系统中的消息,来设置我们系统的prop,第三个就是守护我们的系统服务,如果服务退出,那么就会根据服务的设置状态重启推出的服务
下面我们来看一下init进程是如何处理消息与守护服务的
这就是我们init进程与其他进程进行交互的一个大概示意图,首先我们init进程会创建两个套接字,一个是PROP_SERVICE_NAME,它主要用于监听其他进程所发送的消息,这个消息主要是设置我们的环境变量,第二个套接字是设置信号操作函数,主要用于监听子进程退出的函数,它主要用于我们的子进程服务和init进程进行通讯,当我们把这两个套接字创建完之后呢,我们就在for循环中监听这两个scoket,我们监听分为两种,一种是设置我们的prop,另一个是监听我们子进程是否退出,在setprop这个事件中,我们分为两种情况,第一种是在shell下,通过setprop操作,或者start,stop,class这些操作来向我们的PROP_SERVICE_NAME来发送消息,第二个是在代码中通过setprop这个函数向我们的init进程发送消息,当我们的进程通过PROP_SERVICE_NAME收到消息之后就会做消息的解析,然后根据我们消息设置的格式去设置我们的环境变量,或者重启我们的服务,停止我们的服务,或者启动我们的服务,这个是prop处理消息的一个过程。
下面我们再来看一下我们的子进程也就是我们的服务异常退出的一个过程,当我们的服务退出之后,我们的子进程就会截获到我们子进程所获得的信号,向我们的进程创建的scoket发送消息,我们的init进程截获到这个消息之后,就会根据退出服务的一个pid,找到我们service列表中所对应的服务,把这个服务重新启动,这个是我们init进程守护我们服务的一个过程。
下面我们来看一下我们的setprop的一个具体操作过程
首先我们会在init进程中创建一个scoket,这个scoket就是PROP_SERVICE_NAME,然后我们在shell下,通过start、stop、restart、class向我们scoket发送消息,我们也可以使用我们的setprop向我们的scoket发送消息,而且我们也可以在我们的应用中调用我们的函数向scoket发送消息,当我们的init进程通过scoket收到消息之后,就会调用handle_property_set_fd()这个函数来处理消息,处理方式分为两种,第一是prop环境变量的设置,第二个就是我们服务的一个处理。
下面我们来看一下代码的具体实现过程,首先打开init.c,找到我们的property_service_init_action函数,他是以action的方式添加到我们的queue_builtin_action中的
他在这里主要是调用的是start_property_service()
他首先会创建一个PROP_SERVICE_NAME的scoket,他是一个本地scoket,创建完之后他就把这个scoket赋给了一个全局变量property_set_fd,这时我们的init进程就可以通过监听这个scoket来收其他进程所发送的消息。那我们来看一下这个消息的具体处理过程
在这里会通过Poll对我们的scoket进行监听,如果在这里收到一个handle_property_set_fd的消息,他首先会把这个消息接收下来,接收下来之后会做一个处理,如果说我们这个消息是PROP_MSG_SERPROP,他就会分为两种方式进行处理,第一种是处理我们的handle_control_message,我们来看一下他都做了哪些事情
它主要处理三种消息,分别是start、stop、restart,start后面要跟我们service的一个名字,在msg_start中其实传进来的其实就是我们服务的名称,然后首先找到我们服务的节点,然后通过我们的service_start来把这个服务给启动,另外两种也是相同的处理方式
最后我们来看一下init进程是如何守护我们的服务的
在init进程中有一个signal_init_action的函数,它主要是设置我们信号的一个处理函数,这个信号也就是我们的SIGCHLD,然后我们子进程退出的时候就会触发这个设置的函数,这个函数是我们的sigchld_handler(),同时也会在这里创建两个scoket,分别是signal_fd、signal_recv_fd,当我们创建完之后,我们的init进程就会在这里监听我们的signal_recv_fd,当init进程去处理action_queue创建服务的时候,我们这个服务的子进程会继承所有父进程的所有资源,包括我们信号量的设置,以及我们创建的两个scoket,这样我们通过两个scoket就可以使我们的子进程和父进程进行通讯,当我们的子进程异常退出的时候,我们会处理SIGCHLD这个信号,而这个信号量就会触发我们的sigchld_handler函数,这个函数所做的事情就是通过signal_fd向我们的父进程signal_recv_fd发送消息,发送完消息,我们的init进程就会知道有一个子进程退出了,这时候我们就可以通过wait_for_one_process来处理我们退出的服务,并重启服务。
下面我们来看一下代码的具体实现,在我们init.rc中我们会创建一个signal_init_action的一个操作节点,我们会把这个节点添加到action_queue中,下面我们来看一下signal_init_action的一个具体实现
他首先调用的是一个signal_init的一个函数我们来看一下这个函数
这个函数首先会调用我们的sigaction这个函数,来设置SA_NOCLDSTOP这个信号的处理方式,当我们这个进程捕获到这个信号时,就会调用我们的sigchld_handler来处理这个信号,第二个就是他会调用socketpair这个函数,来创建一对未命名的相互连接的套接字,通过这个创建我们可以得到两个文件描述符,一个是S0、一个是S1,我们分别将他们赋值到signal_fd和signal_recv_fd中,这样我们就有了一对可以通讯的套接字,如果我们创建了一个子进程,那么这个子进程也会继承这对套接字,这时我们就可以通过这对套接字来进行父子进程之间的通讯。Sigchld_handler他的实现就是向我们的signal_fd中写一个消息,写完之后我们的父进程就会收到这个消息,并对这个消息进行处理。
我们再来看一下init进程如何处理子进程的消息,如果说我们无使用Poll这个函数等到了子进程发送的消息,我们就会调用handle_signal这个函数,来处理我们的消息
读到之后我们就会在这里等wait_for_one_process
首先我们会调用waitpid来回收我们的子进程,如果说没有子进程退出那我们就直接返回,如果说有的话,那么我们就会根据进程的pid查找到我们service列表中所对应的service节点,然后将service的一些资源清理掉,清理完之后我们会在最后调用notify_service_state把这个服务重新启动,这个就完成了一个子进程异常退出到重启的一个过程。