Android启动过程详解(3)——Zygote

由于Android系统是基于Linux的,所以在Android系统存在两个不一样的空间,Android空间(Java空间)以及Native空间。系统启动的时候当然是Native空间,所以必须有一个进程来打开Android空间。同时,尽管Android的设计者在淡化进程的概念,强化Activity,Service, BroadcastReceiver等等组件的概念,但从Linux的视角来看,每一个应用都是寄生在一个进程上的,那么创建进程也同样需要从Native空间去创建。在Android世界中Zygote就担任了这个角色,所以所有应用程序进程的父进程都是Zygote。Zygote的意思是受精卵,所以从名字上就能看出来它的作用。今天就来讨论一下Zygote的作用。

1.Zygote的启动

在上一篇博客介绍init进程启动的时候讲到了init.rc文件的解析,其中就有关于Zygote的启动:

#zygote service
service zygote /system/bin/app_process -Xzygote/system/bin –zygote \
 --start-system-server 
   socketzygote stream 666  #socket关键字表示OPTION
   onrestart write /sys/android_power/request_state wake #onrestart也是OPTION
   onrestart write /sys/power/state on
   onrestart restart media

从中可以看到zygote的启动是通过/system/bin/app_process进程启动的。所以zygote最初的名字叫“app_process”,这个名字是在Android.mk文件中被指定的,但app_process在运行过程中,通过Linux下的pctrl系统调用将自己的名字换成了“zygote”,所以我们通过ps命令看到的进程名是“zygote”。接下来看看app_process的启动:

[-->App_main.cpp]
int main(int argc, const char* const argv[])
{
  /*
   Zygote进程由init通过fork而来,我们回顾一下init.rc中设置的启动参数:
    -Xzygote/system/bin --zygote --start-system-server
  */
    mArgC= argc;
    mArgV= argv;
   mArgLen = 0;
    for(int i=0; i<argc; i++) {
       mArgLen += strlen(argv[i]) + 1;
    }
   mArgLen--;
   AppRuntime runtime;
    // 调用Appruntime的addVmArguments,这个函数很简单,读者可以自行分析
    int i= runtime.addVmArguments(argc, argv);
    if (i< argc) {
       //设置runtime的mParentDir为/system/bin
       runtime.mParentDir = argv[i++];
    }
    if (i< argc) {
       arg = argv[i++];
        if(0 == strcmp("--zygote", arg)) {
          //我们传入的参数满足if的条件,而且下面的startSystemServer的值为true
           bool startSystemServer = (i < argc) ?
                    strcmp(argv[i],"--start-system-server") == 0 : false;
           setArgv0(argv0, "zygote");
//设置本进程名为zygote,这正是前文所讲的“换名把戏”。
            set_process_name("zygote");
                 //①调用runtime的start,注意第二个参数startSystemServer为true
           runtime.start("com.android.internal.os.ZygoteInit",
                              startSystemServer);
        }
        ......
    }
         ......
}

在这个函数中一共做了两件重要的事:

  • 创建Android运行时环境,AppRuntime
  • 启动SystemServer
    SystemServer是管理Android所有服务的部件,这一部分放到下一篇博客讲,重点来讲一下AppRumtime的创建。


    这里写图片描述

    从上面这幅图可以看到,AppRuntime继承自AndroidRumtime,所以在app_process进程中调用的runtime.start其实是调用的AndroidRumtime的start函数:

void AndroidRuntime::start(const char*className, const bool startSystemServer)
{
    //className的值是"com.android.internal.os.ZygoteInit"
    //startSystemServer的值是true
    char*slashClassName = NULL;
    char*cp;
   JNIEnv* env;
    blockSigpipe();//处理SIGPIPE信号
     ......
    constchar* rootDir = getenv("ANDROID_ROOT");
    
if (rootDir == NULL) {
//如果环境变量中没有ANDROID_ROOT,则新增该变量,并设置值为“/system"
       rootDir = “/system";
        ......
       setenv("ANDROID_ROOT", rootDir, 1);
    }
    //① 创建虚拟机
    if(startVm(&mJavaVM, &env) != 0)
        goto bail;
     //②注册JNI函数
    if(startReg(env) < 0) {
        goto bail;
    }
    
   jclass stringClass;
   jobjectArray strArray;
   jstring classNameStr;
   jstring startSystemServerStr;
   stringClass = env->FindClass("java/lang/String");
    //创建一个有两个元素的String数组,即Java代码 String strArray[] = new String[2]
   strArray = env->NewObjectArray(2, stringClass, NULL);
  classNameStr = env->NewStringUTF(className);
  
   //设置第一个元素为"com.android.internal.os.ZygoteInit"
   env->SetObjectArrayElement(strArray, 0, classNameStr);
   startSystemServerStr = env->NewStringUTF(startSystemServer ?
                                                "true" : "false");
   //设置第二个元素为"true",注意这两个元素都是String类型,即字符串。
   env->SetObjectArrayElement(strArray, 1, startSystemServerStr);
   jclass startClass;
   jmethodID startMeth;
   slashClassName = strdup(className);
   /*
     将字符串“com.android.internal.os.ZygoteInit”中的“. ”换成“/”,
     这样就变成了“com/android/internal/os/ZygoteInit”,这个名字符合JNI规范,
     我们可将其简称为ZygoteInit类。
   */
    for(cp = slashClassName; *cp != '\0'; cp++)
        if(*cp == '.')
           *cp = '/';
   startClass = env->FindClass(slashClassName);
    ......
    //找到ZygoteInit类的static main函数的jMethodId。
   startMeth = env->GetStaticMethodID(startClass, "main",
                                             "([Ljava/lang/String;)V");
     ......
     /*
        ③通过JNI调用Java函数,注意调用的函数是main,所属的类是
          com.android.internal.os.ZygoteInit,传递的参数是
          “com.android.internal.os.ZygoteInit true”,
          调用ZygoteInit的main函数后,Zygote便进入了Java世界!
          也就是说,Zygote是开创Android系统中Java世界的盘古。
     */
      env->CallStaticVoidMethod(startClass,startMeth, strArray);
    //Zygote退出,在正常情况下,Zygote不需要退出。
    if(mJavaVM->DetachCurrentThread() != JNI_OK)
       LOGW("Warning: unable to detach main thread\n");
    if(mJavaVM->DestroyJavaVM() != 0)
       LOGW("Warning: VM did not shut down cleanly\n");
bail:
   free(slashClassName);
}

上面这段代码主要做了三件事:

  • startVM:注册Java虚拟机,并设置虚拟机相关参数
  • startReg:注册JNI函数,因为后续Java世界用到的一些函数是采用native方式来实现的,所以才必须提前注册这些函数。
  • 调用ZygoteInit.main正式进入Java世界

接下来重点讲一下第三点。首先来看看源码:

[-->ZygoteInit.java]

public static void main(String argv[]) {
  try {
       SamplingProfilerIntegration.start();
       //①注册Zygote用的socket
       registerZygoteSocket();
       //②预加载类和资源
       preloadClasses();
       preloadResources();
       ......
       // 强制一次垃圾收集
       gc();
      //我们传入的参数满足if分支
      if (argv[1].equals("true")) {
          startSystemServer();//③启动system_server进程
       }else if (!argv[1].equals("false")) {
          thrownew RuntimeException(argv[0] + USAGE_STRING);
        }
      // ZYGOTE_FORK_MODE被定义为false,所以满足else的条件
       if(ZYGOTE_FORK_MODE) {
            runForkMode();
       }else {
          runSelectLoopMode();//④zygote调用这个函数
       }
       closeServerSocket();//关闭socket
        }catch (MethodAndArgsCaller caller) {
           caller.run();//⑤很重要的caller run函数,以后分析
        }catch (RuntimeException ex) {
          closeServerSocket();
           throw ex;
        }
     ......
    }

在这个函数中主要做了四件事:

  • 创建IPC通信Socket
  • 预加载类和资源
  • 启动SystemServer
  • 监听其他进程发出来的请求

1.1 创建IPC通信接口Socket——registerZygoteSocket

Zygote和其他进程之间的IPC并不是通过binder机制,而是通过一个localsocket来完成的,所以要首先创建一个socket:

[-->ZygoteInit.java]
private static void registerZygoteSocket() {
    if(sServerSocket == null) {
        intfileDesc;
        try{
           //从环境变量中获取Socket的fd,还记得第3章init中介绍的zygote是如何启动的吗?
//这个环境变量由execv传入。
          String env = System.getenv(ANDROID_SOCKET_ENV);
          fileDesc = Integer.parseInt(env);
       }
       
       try{
         //创建服务端Socket,这个Socket将listen并accept Client
         sServerSocket= new LocalServerSocket(createFileDescriptor(fileDesc));
       }
       }

}

1.2 预加载类和资源

这部分就不细讲了,逻辑比较简单,就是提前加载好一些运行时经常会用到的类和资源,提升系统运行的效率,但这同时会导致系统在启动的时候时间过长,所以这是一个取舍的问题。

1.3 启动SystemServer——startSystemServer

这是Zygote非常重要的一个功能,因为Android启动的所有Service(ActivityManagerService,WindowManagerService,PackageManagerService)都是通过SystemServer来启动的。

private static boolean startSystemServer()
           throws MethodAndArgsCaller, RuntimeException {
        //设置参数
       String args[] = {
            "--setuid=1000",//uid和gid等设置
           "--setgid=1000",
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,
                            3001,3002,3003",
           "--capabilities=130104352,130104352",
           "--runtime-init",
           "--nice-name=system_server", //进程名,叫system_server
           "com.android.server.SystemServer", //启动的类名
        };

       ZygoteConnection.Arguments parsedArgs = null;
       int pid;
       try {
          //把上面字符串数组参数转换成Arguments对象。具体内容请读者自行分析。
           parsedArgs = new ZygoteConnection.Arguments(args);
           int debugFlags = parsedArgs.debugFlags;
          //fork一个子进程,看来,这个子进程就是system_server进程。
           pid = Zygote.forkSystemServer(
                    parsedArgs.uid,parsedArgs.gid,
                    parsedArgs.gids,debugFlags, null);
        }catch (IllegalArgumentException ex) {
           throw new RuntimeException(ex);
        }

        if(pid == 0) {
         //① system_server进程的工作
           handleSystemServerProcess(parsedArgs);
        }

       //zygote返回true
       return true;
    }

逻辑还是比较简单的,就是通过fork创建出SystemServer进程,具体SystemServer启动如何工作下一篇博客会详细介绍。

1.4 监听其他进程发出来的请求——runSelectLoopMode

registerZygoteSocket中注册了一个用于IPC的Socket,不过那时还没有地方用到它。它的用途将在这个runSelectLoopMode中体现出来,请看下面的代码:

[-->ZygoteInit.java]

private static void runSelectLoopMode()
throws MethodAndArgsCaller {
       ArrayList<FileDescriptor> fds = new ArrayList();
       ArrayList<ZygoteConnection> peers = new ArrayList();
       FileDescriptor[] fdArray = new FileDescriptor[4];

      //sServerSocket是我们先前在registerZygoteSocket建立的Socket
       fds.add(sServerSocket.getFileDescriptor());
       peers.add(null);
       int loopCount = GC_LOOP_COUNT;

       while (true) {
           int index;

             try {
               fdArray = fds.toArray(fdArray);
        /*
          selectReadable内部调用select,使用多路复用I/O模型。
          当有客户端连接或有数据时,则selectReadable就会返回。
        */
              index = selectReadable(fdArray);
           }

          else if (index == 0) {
             //如有一个客户端连接上,请注意客户端在Zygote的代表是ZygoteConnection
               ZygoteConnection newPeer = acceptCommandPeer();
               peers.add(newPeer);
               fds.add(newPeer.getFileDesciptor());
           } else {
               boolean done;
              //客户端发送了请求,peers.get返回的是ZygoteConnection
             //后续处理将交给ZygoteConnection的runOnce函数完成。
               done = peers.get(index).runOnce();
        }
    }

runSelectLoopMode比较简单,就是:

  • 处理客户连接和客户请求。其中客户在Zygote中用ZygoteConnection对象来表示;
  • 客户的请求由ZygoteConnection的runOnce来处理。

2.处理请求

Zygote在启动过程中的最后一步是进入runSelectLoopMode来监听socket,接收请求。比如当ActivityManagerService在接收到一个打开Activity的请求,并发现该Activity所属的应用并没有启动时就会创建一个进程,具体的过程可以参考我的另一篇博客:Android应用框架之应用启动过程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容