应用进程是如何创建出来的

简介

APP各自运行于自己的进程中,每一个进程中都拥有一个独立的Dalvik虚拟机实例,拥有了Dalvik,Android的Java程序才能运行起来。可以理解为,进程在以隔离了用户环境下运行,使各不干扰。常用的四大组件,要能运行起来,首先就需要APP的进程已准备完毕。

本文的目的,是学习进程是如如何被创建出来的。

note:源码版本为8.0,各版本间有差异,但不影响核心设计

进程启动必要参数准备

如在Activity的启动过程中,当AMS发现此Activity所依赖的应用进程还没有启动起来,则会请求Zygote进程将此应用进程启动起来。
传送门:Activity启动时发生了什么

代码移步ActivityManagerService.startProcessLocked()

    private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
            ......
        try {
            ......
            // 进程uid
            int uid = app.uid;
            // GIDS
            int[] gids = null;
            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
            if(!app.isolated){
            ......
                // 记住资源访问权限
                if (ArrayUtils.isEmpty(permGids)) {
                    gids = new int[3];
                } else {
                    gids = new int[permGids.length + 3];
                    System.arraycopy(permGids, 0, gids, 3, permGids.length);
                }
                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                // shareUserId
                gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
            }
            ......
            // 指明应用程序入口
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            if(...){
            
            } else{
                //交给 Zygote 去孵化
                startResult = Process.start(entryPoint,
                        app.processName, uid, uid, gids, debugFlags, mountExternal,
                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                        app.info.dataDir, invokeWith, entryPointArgs);
            }
        } 
    }

代码总app为ProcessRecord,ProcessRecord描述了一个进程,与进程相关的信息存于ProcessRecord中,AMS也是通过ProcessRecord才能得知进程状况。

UID

UID标识一个应用进程,应用程序在安装时被分配UID,当此应用程序续存期间(没卸载),UID不变。普通应用程序而言,GID = UID。
通过命令

adb shell ps | grap packageName

可以查看在运行的应用程序信息


查看应用pid.jpg

图中APP使用了多进程,因此根据包名查处了两个进程信息。横行信息依次为 当前用户(UID)、进程ID(PID)、父进程ID(PPID)、进程的虚拟内存大小、实际内存占用大小、进程正在睡眠的内核函数名称。

如所说,无论多少次杀掉此APP进程再重启,UID均不变。

GIDS

GIDS则与Application申请的具体权限有关,申请的相应permission被granted时,则有对应的GIDS,因此,GIDS是关于允许或限制应用程序访问设备资源。比如常在manifest申请访问网络权限

<uses-permission android:name="android.permission.INTERNET"/>

被允许时,组在GIDS中有相应的ID

shareUserId

在GIDS中,shareUserId也比较重要,需简单了解沙箱模型。


沙箱模型1.png

各个应用程序运行与各自的环境中(沙箱),默认情况下,程序间无法交互。运行在沙箱内的应用程序如果没有被分配权限,无法访问系统或资源,因此运行去Dalvik虚拟机的应用程序都能得到同样的安全保护,被限制在各自沙箱内的程序互不干涉,使得对其它或受其它程序的损害降到最低。在沙箱机制下,应用程序间互不信任,相互隔离,各自运行。

沙箱模型2.png

通过shareUserId,可是程序相互信任,公共运行与同一进程空间内,可以像访问自有程序一样访问对方程序资源。
以上二图来源

回到正文,在准备好启动进程的相关数据后,交接给下一节点继续执行。还需注意被赋值为“android.app.ActivityThread”的变量entryPoint,后续会通过此变量启动ActivityThread.main(),也就指定了应用程序的入口。

启动准备

此后调用链路为
Process.start()
-> ZygoteProcess.start()
-> ZygoteProcess.startViaZygote()

    private Process.ProcessStartResult startViaZygote(final String processClass,
       final String niceName,final int uid, final int gid,final int[] gids,
       int debugFlags, int mountExternal, int targetSdkVersion, String seInfo,
       String abi, String instructionSet, String appDataDir, String invokeWith,
       String[] extraArgs) throws ZygoteStartFailedEx {

        // 存储启动参数
        ArrayList<String> argsForZygote = new ArrayList<String>();

        // 以下三参数必有
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
        // 其它参数看具体情况
        ......
       
        synchronized(mLock) {
            // 获取LocalkSocket以连接Zygote进程,后将参数通过LocalSocket
            // 写入Zygote进程,让Zygote进程进行创建
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
       }

视情况准备进程启动需要的参数,再获取用以和进程进行连接的ZygoteState,便可以向Zygote进程写入此次新建进程的请求。

    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");

        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState = ZygoteState.connect(mSocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
        }

        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }

        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
        }

        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

primaryZygoteState 和 secondaryZygoteState 均为 ZygoteState, Android 5.0以后,Android开始支持64位编译,Zygote进程也随之引入了32/64位的区别。顾名思义,primaryZygoteState为主Zygote,secondaryZygoteState为辅Zygote。至于是32位为主,还是64位辅,就要看具体Zygote文件的配置了。这里以ABI架构类型为引子,找到合适的ZygoteSate。

如果所查找的ZygoteState未创建或连接状态已关闭,则会获取此ZygoteState。
ZYGOTE_SOCKET

        public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();

            try {
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));

                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }

                throw ex;
            }

            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: "
                    + abiListString);

            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }

新建的LocalSocket为ZygoteState成员变量,ZygoteState使用的输入流与输出流均从LocalSocket拿到。参数socketAddress为Process创建ZygoteProcess传入的ZYGOTE_SOCKET,即"zygote"。因此LocalSocket.connect()将与Zygote进程中名为“zygote”的Socket进行连接。在有了能与Zygote进程通信的媒介之后,已可以向Zygote进程传入数据或从Zygote进程传输数据。

创建进程

回到
-> ZygoteProcess.startViaZygote()
-> zygoteSendArgsAndGetResult()

    private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
            ......
          // 写入启动参数长度
            writer.write(Integer.toString(args.size()));
            writer.newLine();
            
            for (int i = 0; i < sz; i++) {
                String arg = args.get(i);
                // 写入启动参数
                writer.write(arg);
                writer.newLine();
            }

            writer.flush();

            // Should there be a timeout on this?
            Process.ProcessStartResult result = new Process.ProcessStartResult();

            // 拿到进程创建的结果
            result.pid = inputStream.readInt();
            result.usingWrapper = inputStream.readBoolean();

            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }
            return result;
        }

通过输出流向Zygote进程写入启动数据后,进入阻塞状态,直到能接受到Zygote进程反馈的数据。拿到了创建的结果,意味着进程创建的结束。

进程创建,简.png

目前来看,Process通过LocalSocket与Zygote进程进行通信,传输关键数据即可创建进程,因此还可对Zygote如何创建进程加以了解。

Zygote进程

简要了解一下Zygote进程的创建,见ZygoteInit.main()

   public static void main(String argv[]) {
        // 创建ZygoteServer
        ZygoteServer zygoteServer = new ZygoteServer();
        ......
        try{
        ......
        // 注册名为 "zygote的socket"
        zygoteServer.registerServerSocket(socketName);
        ......
        // 接收请求
        zygoteServer.runSelectLoop(abiList);
        ......
        }
   }

在Zygote进程创建时,向ZygoteServer注册一个名为"zygote"的服务端Socket,之前所说的新建进程请求到来时,即是与此Socket进行通信。当请求到来,在zygoteServer.runSelectLoop()处接收请求。

    void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
        ......
        boolean done = peers.get(i).runOnce(this);
    }

实际由ZygoteConnection.runOnce()进程处理

    boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
        String args[];
        Arguments parsedArgs = null;
        FileDescriptor[] descriptors;
        
        try {
            // 获取LocalSocket传来的启动参数
            args = readArgumentList();
            descriptors = mSocket.getAncillaryFileDescriptors();
        }
        ......
        tyr{
            // 解析参数
            parsedArgs = new Arguments(args);
            ......
            // frok()
            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                    parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
                    parsedArgs.appDataDir);
        }
        ......
        try{
            // pid = 0 说明创建了进程
            if (pid == 0) {
                // in child
                zygoteServer.closeServerSocket();
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;
                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);

                return true;
            }
        }

在接收并解析出LocalSocket所传输过来的启动参数后,通过Zygote.forkAndSpecialize()让native真正fork()出进程。当PID = 0 时,说明Zygote进程创建出了子进程。

当前情况下,需要Zygote进程孵化出的进程。Zygote进程作为Android进程的母体,包含各应用进程所需要的进程信息和资源信息,因从从Zygote进程fork()出的子进程具有Zygote进程信息如数据段、代码段等,自然也包括Dalvik虚拟机实体。

在进程创建完后,由
ZygoteConnection.handleChildProc()
-> ZygoteInit.zygoteInit() 处理进一步事宜

    public static final void zygoteInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        // 创建Binder线程池
        ZygoteInit.nativeZygoteInit();
        // 当AMS要求新创建线程时,唤醒ActivityThread.main()
        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }

applicationInit()通过反射唤醒ActivityThread.main(),之前在AMS请求启动新进程时特意调到的entryPoint,也一并通过LocalSocket作为参数之一传入Zygote进程,因此在这里可以知道此唤醒信息。

AMS在请求启动新进程的同时,不会一直等待进程创建,延迟发送消息PROC_START_TIMEOUT_MSG,如果进程为内在PROC_START_TIMEOUT_MSG时间内成功创建,则AMS会认为进程启动失败。因此在,在唤醒ActivityThread.main()后attach()时机,会通知AMS取消PROC_START_TIMEOUT_MSG消息,以后保证后续流程进行。

至此,也就了解了进程时如何被创建出来。

总结

进程创建,完整.png

进程创建如下:
1、AMS启动各类组件发现相应进程没有启动起来时,获取进程启动所需参数,通过Process.start()请求启动进程。
2、Process通过LocalSocket与Zygote进程通信,Zygote进程对应Socket为"zogote",在Zygote进程创建时启动。
3、ZygoteServer收到启动进程请求后,由ZygoteConnection.runOnce()进行处理,交由native进行fork(),在成功创建子进程后,唤醒ActivityThread.main()。
4、AMS不会无限等待进程创建,因此ActivityThread.main()被唤醒后,会告知AMS取消PROC_START_TIMEOUT_MSG,以能进行后续Business。

参考

浅析Android沙箱模型
Android -- 系统进程Zygote的启动分析
Android ABI的浅析

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

推荐阅读更多精彩内容