Framework 的启动过程

  摘自:柯元旦 《Android内核剖析》
  在讲述Linux系统的启动过程中,在启动过程的最后,内核将读取init.rc文件,并启动该文件中定义的各种服务程序。由于Android系统相对于Linux内核而言仅仅是一个Linux应用程序而已,只是在init.rc中被设置为默认启动
 1. Framework运行环境综述
 任何系统启动过程的本质都是要建立一套系统运行所需的环境;android系统的启动其实就是建立dalvik虚拟机运行所需的环境,总体如下:
                  
 1.  android系统启动的第一个进程为zygote,意思为“受精卵”,因为接下来所有的Dalvik虚拟机进程都是通过它孵化出来的;该进程包括2个核心模块:
   1> Socket服务器:用于接收启动新的Dalvik进程的命令;
   2> Framework共享类与资源:所有Dalvik虚拟机进程都会需要的共享类及资源,其实就是android.jar中的大部分内容,待加载的类和资源列表分别通过preload-classes(共享类)/preload-resources(共享资源)定义;

2.  zygote孵化出的第一个Dalvik虚拟机进程为SystemServer;该进程会负责加载android应用所需的所有基础服务(比如PowerManagerService、AlarmManagerService、WifiService...),并且会创建一个由Ams(具体的东西详见后面的第19章说明)管理的Socket客户端,该客户端用于向zygote中的服务端发送启动新Dalvik进程命令;
3.  从之前的运行环境架构图可以看出有如下特点:
   1> 在android系统中每一个应用就是一个独立的dalvik进程,他们之间相互独立,这样就保证了单个应用的crash不会影响其他应用;而且可以设置单个应用的资源和权限限制,比如内存等;
  2> 公共的类和资源只会加载一份,这样对应用而言只需加载自身所需的类和资源即可,这样就有效的节省了对内存的消耗、加载效率也会提升;

这点和PC上运行多个JAVA应用是不一样的,在PC上一般不存在多个JAVA进程共享一份类和资源的情况;
2. Dalvik虚拟机相关的可执行程序
1. dalvikvm
当Java程序运行时,都是由一个虚拟机来解释Java的字节码,它将这些字节码翻译成本地CPU的指令码,然后执行。
  dalvikvm的作用就是创建一个虚拟机并执行参数中指定的java类。
  dx工具的作用是将.class转换成dex文件,因为Dalvik虚拟机为了提高执行效率,将标准的Jar文件转换成dex文件执行。
2. dvz
 dvz的作用是从zygote进程中孵化出一个新的进程,当然也是一个Dalvik虚拟机。该进程与dalvikvm启动的虚拟机相比,区别在于该进程已经预装了Framework的大部分类和资源。
3. app_process
  Framework在启动时需要加载运行两个特定Java类,一个是ZygoteInit.java,另一个是SystemServer.java.系统提供了app_process进程来自动运行这两个类。app_process的本质就是使用dalvikvm启动ZygoteInit.java,并在启动后加载Framework中的大部分类和资源。
3. Zygote进程的启动过程
 Zygote进程是所有APK应用进程的父进程。
1. 在加载点linux系统根目录下的init.rc中配置的zygote启动参数
   init.rc在系统根目录下,文件中和zygote相关配置具体如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

该以上配置命令中核心信息包括:

 service指令:  告诉操作系统将zygote程序加入到系统服务中,service的语法如下:
    service service_name  可执行程序的路径  可执行程序自身所需的参数列表
用/system/bin/app_process命令启动名称为zygote的系统服务(注意:该进程无法在DDMS中直接查看,他属于系统服务);
其作用可以简单理解为就是启动一个Dalvik虚拟机,并通过指定参数启动一个JAVA应用进程,和执行java命令本质上是一样的;
该命令的源码位于./frameworks/base/cmds/app_process/app_main.cpp
源码会依赖于./frameworks/base/include/android_runtime/AndroidRuntime.h
具体实现位于./frameworks/base/core/jni/AndroidRuntime.cpp
启动Dalvik虚拟机的具体指令路径为:
app_main.cpp中的main() ==> AndroidRuntime.cpp中的start() ==> AndroidRuntime.cpp中的startVm() ==> ./dalvik/vm/Jni.cpp中的JNI_CreateJavaVM() ==> ./dalvik/vm/Init.cpp中的dvmStartup();
期间会完成多个dalvik基础服务启动以及dalvik.system.NativeStart.main()的执行;
Dalvik虚拟机环境准备完毕之后,利用FindClass/CallStaticVoidMethod执行具体Java文件的main方法;
--zygote标示以com.android.internal.os.ZygoteInit类为虚拟机执行入口;
--start-system-server仅在指定--zygote参数下有效,标示ZygoteInit启动完毕后孵化出第一个Dalvik进程SystemServer;
socket标示zygote进程中启动的Socket服务端类型以及端口等信息;
onrestart标示该服务重启后需要触发的操作,即需要唤醒电源、重启media/netted服务;                                                                       
当zygote服务从app_process启动后,会启动一个Dalvik虚拟机,而虚拟机执行的第一个Java类就是ZygoteInit.java.
  1. ZygoteInit.main()核心加载逻辑
    1. registerZygoteSocket(); 构造LocalServerSocket启动Socket服务端,用以接收启动新的Dalvik进程的命令;                            Socket编程中有两种方式去触发Socket数据读操作。一种是使用listen()监听某个端口,然后调用read()从这个端口读数据,这种方式被称为阻塞读方式,若端口没有数据,read()函数将一直等待,直到数据准备好后才返回;另一种是使用select()函数将需要监测的文件描述符作为select()函数的参数,当该文件描述符上出现新的数据后,自动触发一个中断,在中断处理函数中再去读指定文件描述符上的数据,这种方式被称为非阻塞式读操作。LocalServerSocket中使用的是后者。 
    2. preloadClasses(); 加载Framework共享类,待加载列表位于framework.jar/preloaded-classes文件;
      preloaded-classes源文件位于./frameworks/base/preloaded-classes;
      而该文件又可以通过java -Xss512M -cp ./out/host/darwin-x86/framework/preload.jar WritePreloadedClassFile ./frameworks/base/tools/preload/XXXXXX.compiled来生成;
      preload源码位于./frameworks/base/tools/preload/,最后一个参数XXXXXX.compiled二进制文件即是真正的待加载列表;而至于这个.compiled二进制文件如何来的,还未知!
    3. preloadResources(); 加载Framework共享资源,包括图片和颜色;待加载资源位于/frameworks/base/core/res/res/values/arrays.xml;
      其中定义了2个array,分别为preloaded_drawables/preloaded_color_state_lists;
    4. startSystemServer(); 孵化出第一个Dalvik虚拟机进程SystemServer;
      1. 指定新进程启动相关参数,主要是进程名称system_server(为什么DDMS中看到的进程名称为system_process呢?)、以及将要执行的第一个Java类:com.android.server.SystemServer
      2. 执行Zygote.forkSystemServer(…)创建新进程;
        该进程实际上是通过Linux系统的一个系统调用fork(); 完成的,其作用是复制当前进程所包括的所有信息,并产生一个新进程;这样做的目的主要是为了确保Zygote进程中加载的共享类和资源只会存在一份,从而有效节省系统资源;
      3. 执行handleSystemServerProcess(…);
        1. closeServerSocket(); 关闭从zygote进程中copy过来的Socket服务器;
        2. RuntimeInit.zygoteInit(…); 初始化一些额外的运行环境,并执行RuntimeInit.invokeStaticMain(…) 完成对SystemServer.main()函数的运行,至此就完成了新的Java进程SystemServer的创建了;
          而至于main()中执行的具体逻辑如下:
          1. 执行 Looper.prepareMainLooper(); 为主线程做异步消息队列准备;
          2. 加载android应用所需的所有基础服务,比如:Ams、Wms、Pms、WifiService…
          3. 执行 ActivityManagerService.self().systemReady(…); 
            1. 首先会执行 startSystemUi(…); 启动com.android.systemui.SystemUIService服务(可以通过DDMS查看到com.android.systemui进程);
              该进程主要是完成顶部状态栏PhoneStatusBar、电池电量PowerUI、铃声RingtonePlayer这三个服务的加载;
              注意:该部分逻辑在老版本中可能没有,书中也没提到,我是以4.2版本中的源码为准的;
            2. 执行多个服务的systemReady()方法,因为很多服务需要等Ams初始化完成才能真正ready;
            3. 执行 mMainStack.resumeTopActivityLocked(); 启动任务列表中的最上面的一个Activity;
               startHomeActivityLocked()执行过程中,系统会查询所有能响应category为CATEGORY_HOME的Activity列表,若出现多个程序能够响应这个intent,则弹出一个对话框,可以由用户自己选择,并允许用户记住该选择;
              至此android系统的主页面应该就加载出来了,包括系统状态栏、以及Home;
              从上可以看出,任何人都可以做一个Laucher应用,只要申请可以响应对应的category即可,就像Facebook做的Home应用一样;
          4. 执行 Looper.loop(); 主线程进入接受消息状态;
    5. runSelectLoopMode(); 让之前新建好的Socket服务端进入非阻塞读操作,等待接收命令;
      当接收到请求时会调用ZygoteConnection.runOnce(),该方法的核心作用就是孵化出新的应用进程;
      1. 具体创建新进程是靠执行 Zygote.forkAndSpecialize(…) 完成的;与SystemServer进程的创建对比可以看出,两者的方式并不完全相同,但都是基于fork模式来复制进程;
      2. 对复制出来进程善后处理,这个和SystemServer新建后的处理一致,主要是关闭Socket服务器、以及启动新的目标Java文件ActivityThread中的main函数;
使用fork启动新的进程
   fork是linux的一个系统调用,其作用是复制当前进程,产生一个新的进程。新进程将拥有和父进程完全相同的进程信息,除了进程id不同。进程信息包括该进程所打开的文件描述符列表、所分配的内存等。当新进程被创建后,两个进程将共享已经分配的内存空间,直到其中一个需要向内存写入数据时,操作系统才负责复制一份目标地址空间,并将要写的数据写到新的地址中,这就是所谓的copy-on-write机制,即“仅当写的时候才复制”,这种机制可以最大限度地在多个进程中共享物理内存。
    在操作系统中,启动新的进程包含3个过程:
第一个过程,内核创建一个进程数据结构,用于表示将要启动的进程。
第二个过程,内核调用程序装载器函数,从指定的程序文件读取程序代码,并将这些程序代码装载到预先设定的内存地址。
第三个过程,装载完毕后,内核将程序指针指向到目标程序地址的入口处开始执行的指定的进程。
 fork()返回进程id号,若大于0,代表父进程;若等于0,代表子进程。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值