init与zygote的启动流程

1.init进程启动过程

init进程是Android系统中用户空间的第一个进程,进程号就是1,手机开机后,并不会直接进入init进程,在它的前面还有几步流程

  1. 启动电源,引导芯片代码从预定的地方(固化在ROM)开始,加载引导程序BootLoaderRAM,然后执行
  2. 引导程序BootLoader在Android操作系统程序开始之前的一个小程序,把系统OS拉起来并运行
  3. 启动Linux内核,寻找init.rc文件,启动init进程

上面的话有点多,而且有点难以理解,我对它的看法就是

1.启动电源

2.加载引导程序

3.启动Linux内核

4.启动init进程

1.1init进程的入口函数

刚才我们说过,在启动完linux内核之后就会寻找init.rc,然后启动init进程

我们要是找到Android的系统源码的话,我们就能发现,init目录下的所有文件就组成了init进程的整体,它的main.cpp文件就是它的入口函数

代码就不列了,我们主要知道init的这个入口函数里面到底做了哪些事情?

大致可以分为5部

1.1.1创建和启动所需要的文件目录

init的main()函数里面主要挂载了tmpfs,devpts,proc,sysfs和selinuxfs5种文件系统,这五种文件系统只有在系统运行的时候才会存在

1.1.2对属性进行初始化与启动属性服务

在main()中

property_init();

用这段代码进行初始化

后面

start_property_service();

这段进行启动属性服务

1.1.3子进程信号处理函数

main函数有一段这个代码

signal_handler_init();

不看解释我以为又初始化某个属性,结果它的作用是如果init的子进程(zygote进程)异常退出的话,init进程可能会不知道zygote进程退出了,在系统表里面还为这个进程保留了一定的信息(进程号,退出状态,运行时间等等),导致进程表的资源被浪费。为了防止zygote进程成为这样的僵尸进程,为此用这段代码,用来接收zygote进程终止的SIGCHLD信号

1.1.4.重启死去的进程

restart_process();

1.1.5.解析init.rc配置文件

parser.ParseConfig("/init.rc");

我们重点看一下如何解析init.rc配置文件的

1.2解析init.rc配置文件

init.rc这个配置文件极为重要,我们在上面不也说过了嘛,它由5种类型的语句构成的分别是

Action,Command,Service,Option,Import

其中我们上面说的zygote进程,主要是由Service类型语句构成的,Service语句用来通知init进程创建一个名为zygote的进程

1.3解析Service语句

一般情况下我们用ServiceParser来解析Service语句,

这个挺好记的,Service是服务,Parser为解析,服务+解析,所以它就是用来解析Service语句的

ServiceParser解析Service语句通常会用到两个函数

  1. ParseSection
  2. ParseLineSection

看这两个函数长得其实也差不多,但是前者是用来解析Service中的rc文件,比如init.zygote64.rc,然后就可以定义Zygote进程的属性、启动服务、设置文件权限、挂载文件系统等

但是这个并不等于就可以直接启动zygote进程,ParseSection这个函数只是Zygote进程启动的前置工作之一,它确保了Zygote进程在启动后能够正确地读取配置文件中定义的属性和服务,并执行相应的操作。但解析init.zygote64.rc文件并不代表Zygote进程已经完全启动。

后者,ParseLineSection这个函数用来解析子项,这句话的意思是,ParseLineSection是用来解析init.zygote64.rc文件中的子项的

这句话怎么理解,它和ParseSection解析的init_zygote64.rc解析的范围有什么不一样的

ParseSection函数用来解析init.zygote64.rc文件中的段落,而ParseLineSection函数用来解析init.zygote64.rc文件中的子项。在解析init.zygote64.rc文件时,Zygote进程会先使用ParseSection函数解析整个文件,然后使用ParseLineSection函数解析每个子项

在ServiceParser中我们会通过

service = std::make_unique<Service>(name,str_args);

来创建Service对象,这个Service对象就是Android中四大组件的那个Service

ServiceParser中调用完ParseSectionParseLineSection后才会调用EndSection

EndSection中会调用ServiceManagerAddService函数,这个AddService会调用

service_.emplace_back(std::move(service));

这个就是把内容填充到service后将service对象加入vector类型的service链表中

1.4init启动Zygote

在刚才我们已经将Service放入到Vector类型service链表中

既然放入到了链表中,那么就说明service对象很多,所以我们现在的任务就是从service链表中选出那个zygote,因为zygoteclassNamemain,所以我们从链表中找到classNamemain的那个就可以了

因为zygote对应的init.zygote64.rc并没有设置disabled选项,所以我们得通过调用start方法,才能启动zygote

1.4.1start方法

我们刚才说了因为没有设置disabled选项所以我们必须得调用start方法才能启动zygote,所以我们来看来看看start的方法是怎么实现的

1.首先它会判断servicce是否是否启动,如果启动了的话,那就不管了直接返回一个false,如果没有启动,那么就进行下面的操作

2.它判断子进程有没有启动,如果没有启动那么就通过fork()来创建一个子进程(这个在fork()在操作系统里面经常遇见),并返回pid的值,如果pid的值为0,那么就通过execve函数的调用,启动service的子进程

3.在启动子进程的同时会进入该Service的main()函数,如果该servicezygote则会调用runtimestart方法,这样就成功启动了zygote

当时我有两个地方不太懂,

第一个是为什么要创建一个子进程,第二个是pid==0这意味着什么?

结合网上查到的资料与我自己的理解

对于为什么要创建一个子进程,理解是当应用程序进程启动时,Zygote 进程会派生出一个新的应用程序进程,并将应用程序的类和资源加载到该进程的 Dalvik 虚拟机中。起到提高启动速度和效率与降低资源浪费的作用

而pid0则是因为在Android系统中,进程ID0表示虚拟进程,它不占用系统资源,而是用来管理系统的一些特殊任务。这里的Zygote进程与后面的Binder驱动程序就是和这个有关

对了在嵌入式里面也有pid,但那是一个算法和这个没有关系

1.5属性服务

这里的属性服务指的是即使系统或软件重启,还是可以根据之前注册表中的记录,进行相应的初始化工作

这个和之前讲的Activity中的一个特别相似

在讲Activity中,如果我们不使用ViewModel的话,在把手机从竖屏到横屏的时候,它会执行onDestroy(),但是假如我们那个Activity中有EditText,EditText里面有TextView,为什么TextView不会丢失

我们把它的生命周期打印下来

 D/TAG: onCreate
 D/TAG: onStart
 D/TAG: onResume
 D/TAG: onPause
 D/TAG: onStop
 D/TAG: onSaveInstanceState
 D/TAG: onDestroy
 D/TAG: onCreate
 D/TAG: onStart
 D/TAG: onRestoreInstanceState
 D/TAG: onResume

我们注意一下,这里有一个onSaveInstanceStateonRestoreInstanceState,就是因为有这个,在旋转的时候通过onSaveInstanceState把我们的EditText中的数据保存下来又通过onRestoreInstanceState将保存的数据弄到EditText中

这个的作用和属性服务很像

我们在init进程的入口函数的代码中也讲过,init的其中一个功能就是对属性进行初始化与启动属性服务

property_init();

start_property_service();

其中property_init()在《进阶解密》中并没有讲多少只是提了一嘴可以初始化属性内存区域

主要讲的是start_property_service

我们就主要看看start_property_service主要干了些什么

1.5.1start_property_service

简单说一下就是4个点

  1. 创建一个非阻塞的socket
  2. 调用listen方法对property_set_fd进行监听,使socket成为一个属性服务
  3. property_set_fd放入epoll进行监听
  4. property_set_fd有数据到来的时候,init进程会调用handler_property_set_fd进行处理

首先socket我们都很清楚,它是IPC的一个方式,我用Java写聊天室就是通过socket写的

主要是第二点和第三点到底是什么意思,listen和epoll感觉都是对property_set_fd进行监听,那么这两个监听又有什么不同呢?

我搜了一下发现,listen主要监听的是property_set_fd的属性

而epoll监听的是多个套接字,比如监听IPC

chatgpt给出的解释是

listen_socket() 函数用于创建 Unix 套接字并监听属性变化的通知,而 epoll_wait() 函数用于监听套接字状态的变化,并作出相应的响应。

那么就把它理解成listen比epoll多一个创建套接字的过程,epoll又比listen多一个响应的过程

在handler的那块我写过,当MessageQueue没有Message传递给Looper的时候,在epoll会调用epoll.await来阻塞主线程,这一块联系到一起了

1.5.2服务传理客户端请求

我们上面说了当property_set_fd有数据到来的时候,init进程会调用handler_property_fd进行处理

系统属性分为2种类型,一种是普通属性,一种是控制属性。控制属性的一个例子就是开机动画

既然属性都分为这两种了,那么处理属性的也分为两种

一种是处理普通属性,一种是处理控制属性一般以 ctl.开头的就是控制属性

因为控制的英文单词是control

如果是普通属性则会对普通属性进行修改,首先判断属性合不合法,然后判断属性存不存在,不存在就添加,存在就更新

1.6总结init进程

init进程主要就是干了以下三点

  1. 创建和挂载启动所需要的文件目录
  2. 初始化和启动属性
  3. 解析init.rc配置文件并启动zygote进程

2.Zygote启动流程

2.1启动Zygote进程

2.1.1runtime.start()的前戏

在刚才init的启动流程中我们还记得,Zygote的启动是在init解析init.rc文件后,解析service,然后在service链表中找到那个className为main的service,调用它的start方法,start方法在内部会先判断它是否启动这个service,没有启动的话判断是否有service子进程在没有的情况下先通过fork创建子进程然后找到pid==0的那个进程启动service的子进程然后进入service的main函数,调用runtime.start()来启动Zygote进程

但是runtime是怎么调用的我们并不是多熟悉,所以了解Zygote的第一步,就是先了解一下runtime在调用之前它的那个函数都经历过什么?

我们先找到frameworks/base/cmds.app_process/app_main.cpp中找到代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oRtqeBm-1686922381178)(../../assets/QQ图片20230616192925.jpg)]

这段代码可以理解做了3件事。

第一件事是判断main函数在不在zygote进程中,如果在的话把标志位设置为true

第二件事是如果不在zygote进程中,那么就再判断是否运行在SystemServer进程中,是的话把它的相关标志位标志为true

第三件事是如果zygote进程中的标志为true,那么就调用runtimestart

然后我们看看runtime.start()代码里面的东西

2.1.2runtime.start()

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
	.....
//const char* kernelHack = getenv("LD_ASSUME_KERNEL");
//ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) { //1 启动虚拟机
    return;
}
onVmCreated(env); //2 创建虚拟机

/*
 * Register android functions.
 */
if (startReg(env) < 0) { //3 注册Java方法
    ALOGE("Unable to register all android natives\n");
    return;
}

/*
 * We want to call main() with a String array with arguments in it.
 * At present we have two arguments, the class name and an option string.
 * Create an array to hold them.
 */
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;

stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);//4 获得类名
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);

for (size_t i = 0; i < options.size(); ++i) {
    jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
    assert(optionsStr != NULL);
    env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}

/*
 * Start VM.  This thread becomes the main thread of the VM, and will
 * not return until the VM exits.
 */
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName); //5 找到类名对应的类--ZygoteInit
if (startClass == NULL) {
    ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
    /* keep going */
} else {
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
        "([Ljava/lang/String;)V");//6 找到对应类的main方法
    if (startMeth == NULL) {
        ALOGE("JavaVM unable to find main() in '%s'\n", className);
        /* keep going */
    } else {
        env->CallStaticVoidMethod(startClass, startMeth, strArray);//7 调用找到的main方法,从这里开始也进入了Java框架层
        #if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
	......
}

我们是不是都会有点疑虑,我们的Android代码要么用java要么用kotlin,在native层才用c++语言,那什么时候native层才会结束

答案等会儿就可以揭晓了

我们可以看到runtime.start()代码的内部它先创建了一个Java虚拟机,然后在java虚拟机中注册了JNI方法

通过JNI方法,我们调用ZygoteInit的main方法,成功让Zygote由Native层转到Java框架层

这后面我们看到的代码就是java的了

我们本来应该乘胜追击看看ZygoteInit里面的main()方法是怎么实现的,但是在此之前,我们先来看看什么叫JNI?

2.1.2.1JNI

JNI(Java Native Interface)方法是一种 Java 编程语言本地(即非 Java)编程语言交互的机制。它允许 Java 应用程序调用本地代码(通常是 C 或 C++ 代码),从而实现对底层系统或硬件的直接访问

2.1.3ZygoteInit的main()方法的实现

代码有点长,所以我还是用我自己的表达来叙述,

一般代码太长的,我也不会去看它,而是去看相关的注释

它的第一步是创建一个Server端Socket

我们是通过

zygoteServer.registerServerSocket(socketName);

来完成,我们可以看见它是通过这个ZygoteServer来调用registerServerSocket

然后第二步是

preload(bootTimingsTraceLog);

这步是用来预加载类和资源

第三是调用

startSystemServer(abiList,socketName,zygoteServer);

我们把上面注册Server端的Socket还有socketName还有abiList这3个参数传入来启动SystemServer

第四步通过

zygoteServer.runSelectLoop(abiList);

用这个方法来等待AMS请求

第一步创建Server端的Socket和第二步预加载类和资源这个没什么说的,我们重点来看3,4这2个步骤

2.1.3.1启动SystemServer

我们通过创建一个args[]数组,因为我们是要启动SystemServer,所以很明显这个数组是用来存储启动SystemServer的参数的

然后把这个数组封装成forkSystemServer函数所需要的参数,通过这些参数,我们可以创建子进程,如果创建的这个子进程的pid==0就代表当前代码运行在新创建的子进程中,在这个基础上,我们调用handlerSystemServerProcess()来处理SystemServer进程

这样启动就算结束了

但是我有一点不明白的就是当时候我们init进程启动的时候创建了一个子进程,然后当它的pid==0的时候才能执行start方法

然后在启动SystemServer的过程中我们也要创建一个子进程,当它的子进程的pid==0的时候才能继续进行**handlerSystemServerProcess()**来进行下面的处理

那么这个创建子进程到底是在干什么

2.1.3.1.2创建子进程的作用

Zygote 进程是一个特殊的进程,它主要负责启动其他的 Java 进程和应用程序。在启动一个新的进程时,如果每次都需要重新启动一个 Zygote 进程,会导致系统资源的浪费和进程启动速度的下降。因此,在 Android 系统中,通常会使用 fork() 函数来创建一个新的进程,从而提高进程启动速度和系统资源利用率。

forkSystemServer() 函数的作用就是使用 fork() 函数来创建一个新的进程,并在新进程中执行 handlerSystemServerProcess() 函数。这个新进程就是 SystemServer 进程,它是 Android 系统中非常重要的进程之一,负责管理系统服务、应用程序和其他系统组件。使用 forkSystemServer() 函数可以快速启动 SystemServer 进程,从而提高 Android 系统的启动速度和稳定性。

另外,forkSystemServer() 函数还实现了子进程和父进程之间的通信,通过 socket 描述符来共享信息。这种方式可以实现进程间的同步和数据共享,从而提高系统的协作能力和效率。

2.1.3.2runSelectLoop

我们看到Loop我们就可以猜到它肯定又是一个进行无限循环然后把相关消息交给其它进行处理

果不其然

我们在它的源码看到的最显眼的就是那个

while(true)

runSelectLoop的主要作用就是监听刚才创建Server端的Socket,通过这个runSelectLoop无限循环来等待AMS的请求并创建新的应用程序的进程

2.2总结

我们以它处于Native还是Java框架层为分界点,分为两个时期

第一段是仍然处于Native层,我们先判断service的main函数在不在Zygote进程,如果在的话,给它相关的标志设置为true。如果不在,但是它在SystemServer这个进程中,那么我们把这个的相关标志设置为true。在Zygote的相关标志为true的情况下,我们启动Zygote进程

start的内部我们会创建Java虚拟机,然后注册JNI方法,通过JNI方法 我们调用ZygoteInit的main方法,成功让Zygote由Native层转到Java框架层

从此第一阶段结束

第二阶段就是我们通过JNI方法创建ZygoteInitmain方法之后进入java框架层

在这里面我们主要进行了4个操作

第一个操作是创建了Server端的Socket,以供后面第四步的runSelectLoop使用

第二个操作就是预加载类和资源

第一个和第二个操作没有怎么讲,因为感觉确实没啥讲的

第三个操作应该是监听,但是它的方法竟然在启动SystemServer后面就很离谱,监听调用的就是runSelectedLoop()方法,监听第一步获得的Server端的Socket然后等待AMS请求并对相关的套接字做出回应

第四个操作是启动SystemServer,我们通过创建子进程,然后判断子进程的pid是否为0,如果为0的话,那么我们就**handlerSystemServerProcess()**来处理SystemServer。(这个步骤和init启动Zygote的步骤极为相似)

至此Zygote进程的启动流程结束

我们可以看出它的主要的作用

1.从Native层跳到Java的虚拟机层

2.无限循环监听AMS的请求,并作出回应

3.启动SystemServer
main方法之后进入java框架层

猜你喜欢

转载自blog.csdn.net/m0_61130325/article/details/131253963
今日推荐