Zygote进程原理简单介绍,源码解析

1. Zygote介绍

我们的Android系统是基于Linux系统,所以当我们开机的时候,第一个启动的是Init进程,而后面所有的进程都是Init的子进程,zygote就是Init进程通过解析init.rc文件之后,启动的一个进程

Zygote的进程的主要作用只有两个:

  1. 启动SystemServer进程。SystemServer是一个用于启动手机内部各种服务的进程,我们常说的PMS,AMS等都是由SystemServer所启动。
  2. 在系统运行过程中,即时的去孵化APP进程,也就是我们每次点击APP图标启动的APP的时候,zygote就开始运作了。

关于孵化:
Zygote进程创建别的进程的时候,用的不是创建,而是孵化这个词,那怎么理解孵化这个意思呢?

  1. 我们Android系统的程序,都是基于虚拟机所启动的,如果我们每次启动一个APP都要新启动一个虚拟机,那未免也太卡太慢了。
    所以为了避免这种场景,zygote进程在启动的时候,就会直接预加载虚拟机所需要的内存等资源,等后面创建应用需要用到的时候,直接共享使用,这样就避免了多次启动虚拟机的情况。
  2. zygote进程在创建进程的时候,最后会调到Linux内核自带的的fork方法,来复制一个子进程,从而达到1所说的共享虚拟机内容的情况。
  3. zygote进程的作用,就是生成别的进程,自己是不负责做事的。

关于fork:
fork在zygote进程中是一个很重要的概念,这是Linux内核自带的一个方法,这个方法的作用就是复制一个子进程。那么,怎么理解这个复制的意思,简单来说,就是从成员变量,到内存空间,再到当前所执行的代码指令,都会生成一个副本后放到子进程中。

当进程A调用fork后,进程A和复制的子进程B,都会得到fork方法的return值,并且继续往下执行。
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

进程A在开辟一块内存空间之后,持有这个内存空间的引用的话,在fork之后,子进程B也会持有这个内存空间。

2. fork进程源码解析

2.1 Native层启动Zygote进程

由于也不怎么做C语言的开发工作,这部分就快速略过,简单来说就是init进程在读取init.rc文件之后,根据文件里面的指令:

  1. 启动了虚拟机。
  2. 注册了JNI(Java Native Interface,也就是我们常看见的那些native方法啥的)。
  3. 启动了Zygote进程,具体启动方式就是通过反射,拿到ZygoteInit.java这个类,然后去调用里面的main方法。

2.2 启动SystemServer

我还是先搬出我的时序图,对这个启动过程先做一个总结。
在这里插入图片描述

fork生成SystemServer进程
public class ZygoteInit {

	public static void main(String argv[]) {
		// 省略了无关代码
		
		if (startSystemServer) {
           Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);

           // 如果返回的值不为空,就代表当前处于子进程,执行子进程找到的main方法
           if (r != null) {
               r.run();
               return;
           }
       } 
	}

	private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        // 省略了无关代码
        
        int pid;
        /* fork生成SystemServer进程 */
        pid = Zygote.forkSystemServer(
                parsedArgs.mUid, parsedArgs.mGid,
                parsedArgs.mGids,
                parsedArgs.mRuntimeFlags,
                null,
                parsedArgs.mPermittedCapabilities,
                parsedArgs.mEffectiveCapabilities);
                
        // 0表示当前是子进程,也就是SystemServer进程
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        }
		// 非0表示当前是父进程,也就是Zygote进程
        return null;
    }
}

public class Zygote {
	 static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        ZygoteHooks.preFork();

        int pid = nativeForkSystemServer(
                uid, gid, gids, runtimeFlags, rlimits,
                permittedCapabilities, effectiveCapabilities);

        // Set the Java Language thread priority to the default value for new apps.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

        ZygoteHooks.postForkCommon();
        return pid;
    }
    
	/** 最后是调用了内核的fork方法来复制进程 */
	private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
}

这里就可以看出来,对于fork方法的基本处理,因为生成的进程和父进程唯一的区别就是return的值不一样,所以就根据这个return值来区分是父进程还是子进程,从而选择接下来要处理的逻辑。

SystemServer进程寻找main方法

从handleSystemServerProcess开始继续看。

public class ZygoteInit {
	private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
		// 省略了无关代码
        return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                parsedArgs.mDisabledCompatChanges,
                parsedArgs.mRemainingArgs, cl);
	}

	public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        // 省略了无关代码
        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
                classLoader);
    }
}

public class RuntimeInit {

	protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        // 省略了无关代码
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }
	
	/** 
	 * 找到className中的静态main方法 
	 * 其实就是用反射去找类中的这个方法而已
  	*/
	protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }

        /** 这个return的Runnable,最终会在ZygoteInit的main方法中被执行。*/
        return new MethodAndArgsCaller(m, argv);
    }

	/** RuntimeInit中封装的Runnable,run方法就是去跑找到的method而已 */
	static class MethodAndArgsCaller implements Runnable {
        /** method to call */
        private final Method mMethod;

        /** argument array */
        private final String[] mArgs;

        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }

        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
        
    }
    
}

这段代码不难看出,SystemServer进程做的事,就是通过反射去找到SystemServer.java这个类的静态main方法去执行,同时将ZygoteInit的main方法给return了。

2.3 启动APP

启动APP主要是靠ZygoteServer类,这里还是先搬出时序图
在这里插入图片描述
看着有点小乱,主要是因为套了一个死循环方法。

class ZygoteInit {
	public static void main(String argv[]) {
		// 省略无关代码
		ZygoteServer zygoteServer = null;
		Runnable caller;
		try {
			zygoteServer = new ZygoteServer(isPrimaryZygote);
			// SelcetLoop方法在fork之后的子进程中会return出一个caller
            // SelectLoop方法会在Zygote进程中无限循环不停止。
			caller = zygoteServer.runSelectLoop(abiList);
		} catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
        } finally {
            if (zygoteServer != null) {
                zygoteServer.closeServerSocket();
            }
        }
		// 执行子进程中return出来的Runnable方法
		if (caller != null) {
            caller.run();
        }
	}
}

class ZygoteServer {

	Runnable runSelectLoop(String abiList) {
		// 省略无关代码

		// 将SocketFD添加到列表头
		ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
		socketFDs.add(mZygoteSocket.getFileDescriptor());
		
		while (true) {
			StructPollfd[] pollFDs = null;
			pollFDs = new StructPollfd[socketFDs.size()];

			int pollIndex = 0;
            for (FileDescriptor socketFD : socketFDs) {
                pollFDs[pollIndex] = new StructPollfd();
                pollFDs[pollIndex].fd = socketFD;
                pollFDs[pollIndex].events = (short) POLLIN;
                ++pollIndex;
            }
			
			try {
				// 等待文件描述符上的POLLIN事件,代码会阻塞在这里直到响应之后才继续往下执行
                Os.poll(pollFDs, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }

			while (--pollIndex >= 0) {

				// 如果没有实际发生的事件并不是可读事件就跳过该文件描述符
				if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                    continue;
                }
				
				if (pollIndex == 0) {
                    // 创建一个连接
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    socketFDs.add(newPeer.getFileDescriptor());
                } else if (pollIndex < usapPoolEventFDIndex) {
                	try {
                		// 获取Zygote连接
                        ZygoteConnection connection = peers.get(pollIndex);
                        // 获取一个方法,实际上就是获取子进程的静态Main方法
                        final Runnable command = connection.processOneCommand(this);

						// 成员变量,当fork完子进程的时候,子进程会将该变量设置为true
                        if (mIsForkChild) {
                            if (command == null) {
                                throw new IllegalStateException("command == null");
                            }

                            return command;
                        else {
                        	//在主进程中关闭掉该连接
                       	    if (connection.isClosedByPeer()) {
                               connection.closeSocket();
                               peers.remove(pollIndex);
                               socketFDs.remove(pollIndex);
                            }
                        }
         			} catch (Exception e) { }
                }
                
			}
			
		}
		
	}
	
}

这个代码看着其实和Looper.loop非常相似,都是先阻塞在一个地方,等到响应之后才开始继续往下处理。

文件描述符FileDescriptor

在runSelectLoop这个方法里面,会出现FileDescriptor或者FD,这个东西是文件描述符,我把百科的话简单提炼介绍一下:

就是我们每个进程在运行的时候,在虚拟机底层都会有个表(一段连续的存储地址),这个表专门用于记录我们每次打开文件时的信息,包括目标文件的地址,应用层对文件所进行的操作等信息。

每次在对文件进行打开操作的时候,我们都可以获得该文件在这个文件表的下标。这个下标就叫做文件描述符,我们可以根据这个下标通过LINUX系统的API来操作或者监听那些曾经被打开过的文件。

还想具体了解的话,见百科:https://baike.baidu.com/item/%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6/9809582?fr=aladdin

OS.poll

OS.poll,让runSelectLoop阻塞的方法,这个方法的底层调用,就是调用linux系统的poll方法,具体功能为:

等待文件描述符上的某个事件,具体等待什么事件,根据设置决定,看一下StructPollfd 这个对象

public final class StructPollfd {
 /** The file descriptor to poll. */
    public FileDescriptor fd;

    /**
     * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set,
     * POLLOUT to the write fd set.
     */
    public short events;

    /** The events that actually happened. */
    public short revents;
}

其中,fd就代表了文件描述符,event代表了关注的事件,revent代表了实际发生的事件。

套入到我们的场景当中,我们等待的就是POLLIN这个事件,也就是可读事件。

创建连接

在if (pollIndex == 0) 中看一下如何创建一个连接,主要是acceptCommandPeer这个方法,所以就从这里看起

class ZygoteServer {
	private ZygoteConnection acceptCommandPeer(String abiList) {
        try {
        	// mZygoteSocket的类型为LocalServerSocket
            return createNewConnection(mZygoteSocket.accept(), abiList);
        } catch (IOException ex) {
            throw new RuntimeException(
                    "IOException during accept()", ex);
        }
    }
	
	protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
            throws IOException {
        return new ZygoteConnection(socket, abiList);
    }
}


public class LocalServerSocket implements Closeable {

	private final LocalSocketImpl impl;
	
	public LocalSocket accept() throws IOException
    {
        LocalSocketImpl acceptedImpl = new LocalSocketImpl();

        impl.accept(acceptedImpl);

        return LocalSocket.createLocalSocketForAccept(acceptedImpl);
    }
}

class LocalSocketImpl {
	
	protected void accept(LocalSocketImpl s) throws IOException {
        if (fd == null) {
            throw new IOException("socket not created");
        }

        try {
        	//重点就是这行Os.accept
            s.fd = Os.accept(fd, null /* address */);
            s.mFdCreatedInternally = true;
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    }
}

最后核心的部分是,调用了Os.accept,这个方法的功能简单来说就是,当调用Os.accept后,就会根据参数的fd创建一个新的Socket连接,并返回该连接的FD。

启动一个新的进程流程

综合一下前面的内容,我们就可以总结出这个死循环总体的功能了

  1. 在一开始的时候,我们对mZygoteSocket的FD监听POLLIN事件(此时socketFDs 列表里面只有mZygoteSocket对象)。
  2. 另一个进程A通过ZygoteProcess.connect这个方法,来创建一个连接并发出消息(此时调用了Os.socket,创建Socket连接)。
  3. Os.poll触发,此时一定是触发if (pollIndex == 0) 的条件,然后创建一个新的Zygote连接(此时调用了Os.accept)。
  4. 进程A通过在Zygote.connect这个方法的流程中,调用LocalSocketlmpl.connectLocal方法,确认连接。
  5. 然后第二次监听POLLIN的时候,socketFDs里面就有mZygoteSocket和我们在刚刚创建的新连接。
  6. 此时触发的是else if (pollIndex < usapPoolEventFDIndex) ,去fork一个新的进程,并且找到对应的main方法运行,子进程就算创建完成了。

附:时序图代码

@startuml

participant ZygoteInit as init
participant Zygote as zygote
participant RuntimeInit as rinit

 
init -> init : main
activate init
init -> init : forkSystemServer
activate init
	init -> zygote : forkSystemServer
	activate zygote
	zygote -> zygote : nativeForkSystemServer\n调用内核的方法复制进程。
	activate zygote
	 zygote --> zygote : return pid\n父进程返回子进程的进程id\n子进程返回0
	deactivate zygote
	zygote --> init : return pid
deactivate zygote
alt pid != 0
	init --> init :return null
else pid == 0
	init -> init : handleSystemServerProcess\n处理SystemServer进程
	activate init
		init -> init : zygoteInit
		init -> rinit : applicationInit
		activate rinit
		rinit -> rinit : findstaticMain\n寻找静态main方法
		rinit --> init : return mainMethod\n返回找到的Main方法\n就是SystemServer.Main
		deactivate rinit
end 

deactivate init

alt return null
	init -> init : 代表这是zygote进程,处理后续逻辑。
else return mainMethod
	init -> init : run MainMethod\n代表这是SystemServer进程
	init -> init : return
end

deactivate init
deactivate init

@enduml

参考材料

fork(函数)_百度百科
https://baike.baidu.com/item/fork/7143171?fr=aladdin
Android源码分析 - Zygote进程 - 掘金
https://juejin.cn/post/7051507161955827720
zygote - 简书
https://www.jianshu.com/p/cbb44fb9d989

Android29源码中贴的文档

https://man7.org/linux/man-pages/man2/accept.2.html

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值