Skip to content

Latest commit

 

History

History
133 lines (61 loc) · 9.98 KB

8.虚拟机.md

File metadata and controls

133 lines (61 loc) · 9.98 KB

8.虚拟机

通常而言,应用程序直接运行在PC或服务器的操作系统上。每台PC或服务器在同一时间只运行一个操作系统。因此,应用程序供应商需要为每个操作系统或平台重写应用程序的部分代码,才能使应用能够得到系统支持的运行。要支持多种操作系统,应用程序供应商需要创建、管理和维护多种硬件与操作系统基础设施,这一过程需要耗费昂贵的代价和大量的资源。处理这个问题的有效策略之一称为虚拟化(virtualization),虚拟化技术可使一台PC或服务器同时运行多个操作系统或一个系统的多个会话。一台运行虚拟化软件的机器能在同一平台上运行大量的应用程序,包括那些运行在不同操作系统上的应用程序。实际上,主机操作系统能支持多个虚拟机(virtual machines),每个虚拟机都有特定操作系统的特性。

启用虚拟化的解决方案是虚拟机监视器(VMM),现在通常称为虚拟机管理程序(Hypervisor)。该软件介于硬件和虚拟机之间,以资源代理的形式存在。简而言之,它使多个虚拟机安全地共存于一台物理服务器主机并共享主机的资源。

img

Java虚拟机

尽管Java虚拟机(JVM)用“虚拟机”作为其名称的一部分,但其实现和用途与我们前面所讲的模型不同。虚拟机管理程序支持在主机上运行一个或多个虚拟机。这些虚拟机独立的处理工作负载,支持操作系统和应用,且在它们自身看来,访问一系列提供计算、存储和输入/输出的资源。Java虚拟机的目的是,无须更改任何Java代码就可在任意硬件平台的任意操作系统上,提供运行时空间。两种模型的目的都是通过使用某种程度的抽象化来实现平台无关性。

JVM可描述为一个抽象的计算设备,它包含指令集、一个PC(程序计数器)寄存器、一个用来保存变量和结果的栈、一个保存运行时数据和垃圾收集的堆、一个存储代码和常量的方法区。

JVM支持多个线程,每个线程都有自己的寄存器和堆栈区,且所有线程共享栈和方法区。

Android虚拟机

Android平台的虚拟机称为Dalvik,Dalvik VM(DVM)执行格式为Dalvik Executable(.dex格式)的文件,即为高效存储和内存映射执行而优化的格式。DVM可以运行由Java编译器编译的类,该编译器以用“dx”工具转换为本地格式。

虚拟机运行在Linux内核的顶部,它依赖于底层的功能(如线程和底层的内存管理)。Dalvik核心类库的目的是,为那些使用标准Java编程的人员提供熟悉的开发环境,但它是专门为满足小型移动设备的需要而设计的。

每个Android应用程序都运行在自己的进程中,有自己的Dalvik运行实例。Dalvik是可以在一台设备上高效执行多个副本的虚拟机。

当Java程序运行时,都是由一个虚拟机来解释Java的字节码,它将这些字节码翻译成本地CPU的指令码,然后执行。对Java程序而言,负责解释并执行的就是一个虚拟机,而对于Linux而言,这个进程只是一个普通的进程,它与一个只有一行代码的Hello World可执行程序无本质区别。所以启动一个虚拟机的方法就跟启动任何一个可执行程序的方法是相同的,那就是在命令行下输入可执行程序的名称,并在参数中指定要执行的Java类。而dalvikvm的作用就是创建一个虚拟机并执行参数中指定的Java类。

dex文件系统

DVM运行Java语言的应用和代码。标准的Java编译器将源代码(写在文本文件中)转换为字节码,然后将字节码翻译成DVM虚拟机可读和可用的.dex文件。本质上,类文件被转换为.dex文件(像Java虚拟机中的jar文件),然后在DVM上读取和执行。类文件中的重复数据在.dex文件中只包含一次,以节省空间开销。在安装时,这个可执行文件还可根据移动设备进一步修改和优化。

img

上图中的.jar文件的布局,它包含一个或多个类文件。这些类文件聚合为一个.dex文件,并存储为一个android安装包文件(.apk)。所有类文件中的不同常量池集中为单个常量池,在.dex文件中组织为常量类型。允许类的常量池共享,因此可使常量值的重复减至最低。类似的,类文件中的类、域、方法、属性也在.dex文件中集中到一起。

DVM示例

下面以一个例子来说明dalvikvm的使用方法:

  1. 首先新建一个Foo.java文件,如下:

    class Foo {
        public static void main(String[] args) {
            System.out.println("Hello dalvik");
        }
    }
  2. 然后编译该文件,并生成jar文件,如下:

    $ javac Foo.java
    $ dx --dex --output=foo.jar Foo.class

    dx工具的作用是将.class转换为dex文件,因为Dalvik虚拟机所执行的程序不是标准的Jar文件,而是将jar经过特别的转换以提高执行效率,而在转换后就是dex文件。dx工具是Android源码的一部分,其路径是在out目录下。dx执行时,--output参数用于指定jar文件的输出路径,注意该jar文件内部包含已经不是纯粹的.class文件,而是dex格式文件,jar仅仅是zip包。

  3. 生成了该jar包后,就可以把该jar包push到设备中,并执行以下命令:

    $ adb push foo.jar /data/app
    $ adb shell dalvikvm -cp /data/app/foo.jar Foo
    Hello dalvik    

    以上命令首先将jar包push到/data/app目录下,因为该目录一般用于存放应用程序,接着使用adb shell执行dalvikvm程序。dalvikvm的执行语法是: dalvikvm -cp 类路径 类名,从这里可以感觉到,dalvikvm的作用就像在pc上执行java程序一样。

程序是如何执行的

计算机只能运行本地代码。本地(native)这个术语有“母语的”意思。对CPU来说,母语就是机器语言,而转换成机器语言的程序就是本地代码。用任何编程语言编写的源代码,最后都要翻译成本地代码,否则CPU就不能理解。也就是说,即使是用不同编程语言编写的代码,转换成本地代码后,也都变成用同一种语言(机器语言)来表示了。

编译:编译器负责转换源代码

能够把C语言等高级编程语言编写的源代码转换成本地代码的程序称为编译器。每个编写源代码的编程语言都需要其专用的编译器。将C语言编写的源代码转换成本地代码的编译器称为C编译器。

编译器首先读入代码的内容,然后再把源代码转换成本地代码。编译器中就好像有一个源代码同本地代码的对应表。但实际上,仅仅靠对应表是无法生成本地代码的。读入的源代码还要经过语法解析、句法解析、语义解析等,才能生成本地代码。

根据CPU类型的不同,本地代码的类型也不同。因而,编译器不仅和编程语言的种类有关,和CPU的类型也是相关的。例如,Pentium等x86系列CPU用的C编译器,同PowerPC这种CPU用的C编译器就不同。从另一个方面来看,这其实是非常方便的。因为这样一来,同样的源代码就可以翻译成适用于不同CPU的本地代码了。

但是仅靠编译是无法得到可执行文件的。

链接

编译器转换源代码后,就会生成一个个的本地文件。不过,本地文件是无法直接运行的。为了得到可以运行的EXE文件,编译之后还需要进行“链接”处理。

把多个目标文件结合,生成1个EXE文件的处理就是链接,运行连接的程序就称为链接器(linkage editor或连结器)。

Windows下的编译和链接机制

在了解了通过程序的编译及链接来生成EXE文件的机制后,接下来看一下EXE文件的运行机制。EXE文件是作为单独的文件储存在硬盘中的。通过资源管理器找到并双击EXE文件,就会把EXE文件的内容加载到内存中运行。EXE文件中给变量及函数分配了虚拟的内存地址。在程序运行时,虚拟的内存地址会转换成实际的内存地址。链接器会在EXE文件的开头,追加转换内存地址所需的必要信息。这个信息称为再配置信息。

EXE文件的再配置信息,就成为了变量和函数的相对地址。相对地址表示的是相对于基点地址的偏移量,也就是相对距离。实现相对地址,也是需要花费一番心思的。在源代码中,虽然变量及函数是在不同位置分散记述的,但在链接后的EXE文件中,变量及函数就会变成一个连续排列的组。这样一来,各变量的内存地址就可以用相对于变量组起始位置这一基点的偏移量来表示,同样,各函数的内存地址也可以用相对于函数组起始位置这一基点的偏移量来表示。而各组基点的内存地址则是在程序运行时被分配的。

加载到内存的程序由4部分构成

EXE文件中并不存在栈及堆的组。栈和堆需要的内存空间是在EXE文件加载到内存后开始运行时得到分配的。因而,内存中的程序,就是由用于变量的内存空间、用于函数的内存空间、用于栈的内存空间、用于堆的内存空间这4部分构成的。