Android学习之zygote启动流程
zygote 概述
在Android系统中,DVM(Dalvik虚拟机)、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。
zygote的英文名为受精卵,由于他能不断孵化进程,因此也是名字的由来。
它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建DVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个DVM的实例拷贝
init.rc中配置
在init进程创建时,会读取 init.zygote.rc 中的配置文件
init.zygote.rc
这个是在rc文件中的配置,下面我们会一次介绍各个命令的作用,这些命令具体都会在源码分析中看到
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readprocsocket zygote stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks
service
service指令告诉系统将zygote程序加入到系统服务中,基本语法如下
service service_name 可执行程序的路径 可执行程序自身所需的参数列表
这里我们的服务是 zygote 他的程序路径在 /system/bin/app_process ,后面那一堆,就是程序执行的参数,在 app_process 的main函数中会从argc和argv中取出
-Xzygote
作为虚拟机启动时所需要的参数,在AndroidRuntime.cpp中的 startVm() 函数中调用 JNI_CreateJavaVM 使用到
/system/bin
代表虚拟机程序所在目录,因为 app_process 可以不和虚拟机在一个目录,所以 app_process 需要知道虚拟机所在的目录
–zygote
指明以 ZygoteInit 类作为入口,否则需要指定需要执行的类名
–start-system-server
仅在有 --zygote 参数时可用,告知 ZygoteInit 启动完毕后孵化出的第一个进程是 SystemServer
其他命令
后续的命令是和socket相关,名称、类型、端口号、重启后的操作等等
app_process
该文件在 frameworks/base/cmds/app_process 中,找到 main 函数
//前面的操作都是对参数进行一些赋值等等,就是之前的命令中配置的参数
......
if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {runtime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}
这个 runtime 是 AppRuntime
AppRuntime.start
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{......JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;//创建虚拟机相关信息if (startVm(&mJavaVM, &env, zygote) != 0) {return;}onVmCreated(env);//JNI方法的注册if (startReg(env) < 0) {ALOGE("Unable to register all android natives\n");return;}......char* slashClassName = toSlashClassName(className != NULL ? className : "");jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {......} else {jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");if (startMeth == NULL) {......} else {env->CallStaticVoidMethod(startClass, startMeth, strArray);}}
}
对虚拟机和JNI方法的一些注册后,通过 CallStaticVoidMethod 来调用传过来的类名的main函数,我们传递过来的类名是 com.android.internal.os.ZygoteInit
ZygoteInit.main
该文件在 frameworks/base/core/java/com/android/internal/os 中
public static void main(String argv[]) {ZygoteServer zygoteServer = new ZygoteServer();......final Runnable caller;try {......zygoteServer.registerServerSocket(socketName);preload(); ......if (startSystemServer) {Runnable r = forkSystemServer(abiList, socketName, zygoteServer);if (r != null) {r.run();return;}}caller = zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {......} finally {zygoteServer.closeServerSocket();}if (caller != null) {caller.run();}
}
registerServerSocket : 用于注册socket
preload : 预加载类和资源
forkSystemServer : 启动system_server
runSelectLoop : socket的处理
registerServerSocket
注册一个socket,来接收启动新进程一些命令操作,建立socket通道,zygote作为通信的服务端,用于响应客户端请求
void registerServerSocket(String socketName) {if (mServerSocket == null) {int fileDesc;final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;try {String env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);} catch (RuntimeException ex) {throw new RuntimeException(fullSocketName + " unset or invalid", ex);}try {FileDescriptor fd = new FileDescriptor();fd.setInt$(fileDesc);mServerSocket = new LocalServerSocket(fd);} catch (IOException ex) {throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);}}
}
fileDesc 是一个文件描述符
最终会构造一个 LocalServerSocket ,创建socket的本地服务端
关于socket
在Linux系统中,所有的资源都可以看做是文件,socket也是,所以传递了一个文件描述符来构建socket
注意
socket也是IPC的一种方式
socket中有两种方式进行通信
阻塞式
使用listen()监听某个端口,调用read()读取数据,没有数据时,会一直等待
非阻塞式
使用select()将需要检测的文件描述符作为 select() 函数的参数,当文件描述符上出现新的数据后,自动触发一个中断,然后在中断处理函数中再去读取指定文件描述符的数据
LocalServerSocke 用的是第二种 非阻塞式,读取的文件为 /dev/socket/zygote
LocalServerSocke 对应的客户端
前面的代码讲解的是关于socket服务端的,有服务端那就有客户端
它对应的客户端是进程 SystemServer 中创建出来的
preload
为了能够让 zygote 孵化进程进行的资源加载,预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率
static void preload() {//预加载位于/system/etc/preloaded-classes文件中的类preloadClasses();//预加载资源,包含drawable和color资源preloadResources();//预加载OpenGLpreloadOpenGL();//通过System.loadLibrary()方法,//预加载"android","compiler_rt","jnigraphics"这3个共享库preloadSharedLibraries();//预加载 文本连接符资源preloadTextResources();//仅用于zygote进程,用于内存共享的进程WebViewFactory.prepareWebViewInZygote();
}
forkSystemServer
这个小节介绍SystemService的启动过程
fork
fork是Linux中的一个系统调用,作用是复制当前进程,除了进程ID不同,两个进程信息完全一致
新进程被创建后,两个进程将共享已经分配的内存空间,直到其中一个进程需要向内存写入数据的时候,系统才会复制一份目标地址空间,并将写的数据写入到新的地址中,这就是 copy-on-write 机制,“仅当写的时候才复制”,可以节省共享物理内存,调用一次,返回两次,返回值有3种类型
● 父进程中,fork返回新创建的子进程的pid
● 子进程中,fork返回0
● 当出现错误时,fork返回负数(当进程数超过上限或者系统内存不足时会出错)
fork()的主要工作是寻找空闲的进程号pid,然后从父进程拷贝进程信息,例如数据段和代码段,fork()后子进程要执行的代码等。 Zygote进程是所有Android进程的母体,包括system_server和各个App进程。zygote利用fork()方法生成新进程,对于新进程A复用Zygote进程本身的资源,再加上新进程A相关的资源,构成新的应用进程A
copy-on-write原理
写时拷贝是指子进程与父进程的页表都所指向同一个块物理内存,fork过程只拷贝父进程的页表,并标记这些页表是只读的。父子进程共用同一份物理内存,如果父子进程任一方想要修改这块物理内存,那么会触发缺页异常(page fault),Linux收到该中断便会创建新的物理内存,并将两个物理内存标记设置为可写状态,从而父子进程都有各自独立的物理内存
为什么要fork
因为两个进程存在大量的共享程序,如果使用fork,可以节省大量的共享内存
SystemServer的启动
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {......String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010","--capabilities=" + capabilities + "," + capabilities,"--nice-name=system_server","--runtime-args",//注意这里的类名,它会去装载这个类"com.android.server.SystemServer",};ZygoteConnection.Arguments parsedArgs = null;int pid;try {parsedArgs = new ZygoteConnection.Arguments(args);ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);//主要的fork操作,会调用到native层的代码进行 forkpid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,parsedArgs.gids,parsedArgs.debugFlags,null,parsedArgs.permittedCapabilities,parsedArgs.effectiveCapabilities);} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);}/* For child process 这里代表新进程*/if (pid == 0) {if (hasSecondZygote(abiList)) {waitForSecondaryZygote(socketName);}zygoteServer.closeServerSocket();return handleSystemServerProcess(parsedArgs);}return null;}
在 pid == 0 的节点中,关闭socket服务端(注意这里关闭的是从父进程复制过来的子进程里的socket,因为子进程不需要这个socket),
handleSystemServerProcess 函数的作用是调用 SystemServer 的main函数以及一些配置工作(调用新进程指定的class的main函数)
一旦 SystemServer 的配置工作完成后,就会从 SystemServer 的 main 函数开始运行
到此,调用main后,新进程就完全脱离了 zygote 进程的孵化过程了,成为一个真正的应用进程
runSelectLoop
socket创建后就是进入读操作了
Runnable runSelectLoop(String abiList) {ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();//mServerSocket 是socket通信中的服务端,即zygote进程。保存到fds[0]fds.add(mServerSocket.getFileDescriptor());peers.add(null);//无限循环等待AMS的请求while (true) {StructPollfd[] pollFds = new StructPollfd[fds.size()];for (int i = 0; i < pollFds.length; ++i) {pollFds[i] = new StructPollfd();pollFds[i].fd = fds.get(i);pollFds[i].events = (short) POLLIN;}try {//处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里Os.poll(pollFds, -1);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}for (int i = pollFds.length - 1; i >= 0; --i) {if ((pollFds[i].revents & POLLIN) == 0) {continue;}// 第一个就是刚才的操作 fds.add(mServerSocket.getFileDescriptor());if (i == 0) {//接收客户端发送过来的connect()操作,Zygote作为服务端执行accept()操作。 再后面客户端调用write()写数据,Zygote进程调用read()读数据ZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);fds.add(newPeer.getFileDesciptor());} else {try {ZygoteConnection connection = peers.get(i);final Runnable command = connection.processOneCommand(this);......} catch (Exception e) {......}}}}
}
在读取数据的操作中的 processOneCommand 里,最为核心的方法就是 Zygote.forkAndSpecialize,看名字就应该知道他的作用就是fork进程,runSelectLoop 函数随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作
总结
zygote主要做了一下几件事:
- 创建AppRuntime并调用start方法,启动zygote进程
- 创建java虚拟机并未java虚拟机注册JNI方法
- 通过JNI调用 zygoteinit的main函数进入zygote的java框架层
- 通过 registerZygoteSocket方法创建服务端 Socket,并通过 runSelectLoop 方法等待AMS的请求来创建新的应用进程
- 启动 SystemServer进程
zygote的main函数的流程可以简化为下
public static void main(String argv[]) {try {caller = zygoteServer.runSelectLoop(abiList);....} catch (RuntimeException ex) {closeServerSocket();throw ex;}if (caller != null) {caller.run();}
}
注意,之前在分析acceptCommandPeer部分的时候,父进程是直接返回null的,新进程是返回一个runnable,因此,这部分的代码在不同进程中的执行是不同的.对于父进程也就是Zygote进程,他是一直循环运行的,对于新进程,会完成启动操作后停止循环并调用目标类的main函数
- 系统启动时init进程会创建Zygote进程,Zygote进程负责后续Android应用程序框架层的其它进程的创建和启动工作。
- Zygote进程会首先创建一个SystemServer进程,SystemServer进程负责启动系统的关键服务,如包管理服务PackageManagerService和应用程序组件管理服务ActivityManagerService。
- 当我们需要启动一个Android应用程序时,ActivityManagerService会通过Socket进程间通信机制,通知Zygote进程为这个应用程序创建一个新的进程。
Android学习之zygote启动流程相关推荐
- android zygote启动流程,Android zygote启动流程详解
对zygote的理解 在Android系统中,zygote是一个native进程,是所有应用进程的父进程.而zygote则是Linux系统用户空间的第一个进程--init进程,通过fork的方式创建并 ...
- LIteOS学习笔记-7LiteOS启动流程与编译流程
LIteOS学习笔记-7LiteOS启动流程与编译流程 LiteOS启动流程 1. 启动方式 2. 启动流程 硬件初始化 内核初始化 调试串口初始化 尝试进行网络连接 启动任务调度 LiteOS编译流 ...
- 【Android 源码学习】Zygote启动原理
Android 源码学习 Zygote启动原理 望舒课堂 Zygote进程启动原理学习记录整理. Zygote简介 Zygote是进程在init进程启动时创建的,进程本身是app_process,来源 ...
- Android FM模块学习之一 FM启动流程
转自:http://blog.csdn.net/tfslovexizi/article/details/41283743 最近在学习FM模块,FM是一个值得学习的模块,可以从上层看到底层.上层就是FM ...
- Android应用程序进程启动流程
在学习应用程序进程启动流程前,先要弄清楚系统启动流程,如果有不清楚的同学,建议先看下以前博主的文章: Android系统启动(上篇)_AD钙奶-lalala的博客-CSDN博客 Android系统启动 ...
- Android系统完整的启动流程
Android系统完整的启动过程,从系统层次角度可分为 Linux 系统层.Android 系统服务层.Zygote进程模型三个阶段:从开机到启动 Home Launcher 完成具体的任务细节可分为 ...
- Android 13.0 SystemUI 启动流程
1.手机开机后,Android系统首先会创建一个Zygote(核心进程). 2.由Zygote启动SystemServer. 3.SystemServer会启动系统运行所需的众多核心服务和普通服务.以 ...
- Android 源码 PackageManagerService 启动流程分析
<Android 源码 installPackage 流程分析>一节着重分析了 apk 安装流程,接下来我们分析 PackageManagerService 启动时都做了些什么? 执行 P ...
- Android入门之APP启动流程
俗话说,要想优化好,流程不可少.作为一款App的开发者,首先要把它的启动流程做好了,简单明了的启动流程不仅拥有很好的体验感,还能获得更多用户对App的肯定.本篇文章就带大家了解下app启动流程的三个进 ...
最新文章
- 接口中可以有静态方法吗?
- linux命令行使用for循环,小弟我使用过的Linux命令之for - Bash中的For循环
- ASP.NET MVC: 构建不带 Web 窗体的 Web 应用程序(转载)
- 面向Linux的10款最佳剪贴板管理器
- Maven学习总结(53)——利用Maven插件构建镜像进行持续交付中的版本号管理
- PHP empty操作记录
- PTA L2-006 树的遍历-二叉树的后序遍历+中序遍历,输出层序遍历 团体程序设计天梯赛-练习集...
- 分层结构的生活例子_详解软件分层架构设计、工作原理、实例以及具体架构
- arrays中copyof复制两个数组_Java教程分享之数组知识梳理
- python读取MNIST image数据
- 有N个软件包未被升级
- Caused by: java.lang.ClassNotFoundException: Didn’t find class on path apk Android Studio解决方案
- 在线教育源码 知识付费平台源码 PC+H5+后台管理端 教育课程源码
- 为什么直播礼物特效越来越流行?
- java fastfds操作文件
- CESM mpirun noticed that process rank 1 with PID 0 on node ubuntu exited on signal 11
- win10 右键菜单管理
- 西安周边旅游++军事纪实
- 食品和饮料销售预测分析
- 离散数学复习:二元关系
热门文章
- 在Ubuntu安装完成后更改默认的语言LANG,出现Cannot set LC_CTYPE to default locale: No such file or directory错误的解决办法
- OpenCV笔记02:用cv2.imread函数读取图片
- 神经网络入门(个人理解)
- python字符串的定界符可以是_Python中,字符串不能用以下哪个符号作为定界符(): \|'|'''|;...
- 怎么用计算机按反三角函数的导数,反三角函数求导过程
- Mac安装brew及使用
- python透视变换原理_python cv2中的透视变换
- ASCII转十六进制小工具
- 如何批量生成JAN13条码
- DELL(i7版本)麦克风杂音问题解决方案