为什么需要虚拟机?

Dan*_*iel 26 compiler-construction vm-implementation

我正在阅读这个问题,以找出Java虚拟机和.NET CLR之间的差异,Benji的回答让我想知道为什么虚拟机首先是必要的.

根据我对Benji的解释的理解,虚拟机的JIT编译器将中间代码解释为在CPU上运行的实际汇编代码.它必须这样做的原因是因为CPU通常具有不同数量的寄存器,并且根据Benji的说法,"一些寄存器是特殊用途的,并且每个指令都要求其操作数在不同的寄存器中." 这是有道理的,因此需要像虚拟机这样的中间解释器,以便可以在任何CPU上运行相同的代码.

但是,如果是这种情况,那么我不明白为什么编译成机器代码的C或C++代码能够在任何计算机上运行,​​只要它是正确的操作系统.那么为什么我在使用Pentium的Windows机器上编译的C程序能够在我使用AMD的其他Windows机器上运行?

如果C代码可以在任何CPU上运行,那么虚拟机的目的是什么?是否可以在任何操作系统上运行相同的代码?我知道Java在几乎任何操作系统上都有VM版本但除了Windows之外还有其他操作系统的CLR吗?

或者还有其他我想念的东西?操作系统是否对其运行的汇编代码做了一些其他解释,以使其适应特定的CPU或其他东西?

我很好奇这一切是如何运作的,所以我们将非常感谢一个明确的解释.

注意:我之所以不在JVM与CLR问题中发表我的查询作为评论的原因是因为我没有足够的积分发表评论但= b.

编辑:感谢所有的好答案!所以我似乎缺少的是,虽然所有处理器都有差异,但是有一个共同的标准化,主要是X86架构,它提供了足够大的通用功能集,因此在一个X86处理器上编译的C代码将在大多数情况下工作在另一个X86处理器上.这进一步推动了虚拟机的正当性,更不用说我忘记了垃圾收集的重要性.

Ada*_*vis 38

AMD和intel处理器使用相同的指令集和机器架构(从执行机器代码的角度来看).

C和C++编译器编译为机器代码,其头文件适用于他们所针对的操作系统.一旦编译,它们就不再以任何方式,形状或形式与它们编译的语言相关联,并且仅仅是二进制可执行文件.(有些工件可能会显示它是从哪种语言编译而来的,但这不是重点)

因此,一旦编译,它们就与机器(X86,intel和amd指令集和体系结构)和操作系统相关联.

这就是为什么它们可以运行在任何兼容的x86机器和任何兼容的操作系统(win95到winvista,对于某些软件).

但是,它们无法在OSX计算机上运行,​​即使它在intel处理器上运行 - 除非您运行其他仿真软件(例如parallels或带有Windows的VM),否则二进制文件不兼容.

除此之外,如果你想在ARM处理器,MIPS或PowerPC上运行它们,那么你必须运行一个完整的机器指令集模拟器,它将二进制机器代码从X86解释为你正在运行它的任何机器.

与.NET对比.

.NET虚拟机的制作好像世界上有更好的处理器 - 理解对象,内存分配和垃圾收集的处理器,以及其他高级构造.它是一个非常复杂的机器,现在无法直接在硅片中构建(具有良好的性能),但可以编写一个仿真器,允许它在任何现有的处理器上运行.

突然之间,您可以为要运行.NET的任何处理器编写一个特定于机器的仿真器,然后可以在其上运行任何.NET程序.无需担心操作系统或底层CPU架构 - 如果有.NET VM,则软件将运行.

但是,让我们再进一步 - 一旦你掌握了这种共同语言,为什么不让编译器将任何其他书面语言转换成它呢?

所以现在你可以使用C,C#,C++,Java,javascript,Basic,python,lua或任何其他语言编译器来转换已编写的代码,以便它可以在这个虚拟机上运行.

你已经将机器与语言分离了2度,并且没有太多的工作你可以让任何人编写任何代码并让它在任何机器上运行,只要存在编译器和VM来映射两个分离度.

如果您仍然想知道为什么这是一件好事,请考虑早期的DOS机器,以及微软对这个世界的真正贡献是:

Autocad必须为每个可以打印的打印机编写驱动程序.莲花1-2-3也是如此.事实上,如果您想要打印软件,则必须编写自己的驱动程序.如果有10个打印机和10个程序,则必须单独和独立地编写100个不同的基本相同的代码.

Windows 3.1尝试实现的目标(以及GEM和许多其他抽象层)使打印机制造商为其打印机编写了一个驱动程序,程序员为Windows打印机类编写了一个驱动程序.

现在有10个程序和10个打印机,只需要编写20个代码,并且由于代码的microsoft方面对每个人来说都是相同的,因此来自MS的示例意味着您几乎没有什么工作要做.

现在,一个程序并不仅限于他们选择支持的10台打印机,而是制造商为Windows提供驱动程序的所有打印机.

应用程序开发中也出现了同样的问题.由于我不使用MAC,因此我无法使用真正的应用程序.有大量的重复(我们真正需要多少世界级的文字处理器?).

Java本来是为了解决这个问题,但它有很多限制,其中一些并没有真正解决.

.NET更接近,但没有人为Windows以外的平台开发世界级的虚拟机(单声道是如此接近......但还不完全存在).

所以...这就是我们需要虚拟机的原因.因为我不想仅仅因为他们选择了与我自己不同的操作系统/机器组合,所以我不想将自己限制在较小的受众群体中.

-亚当


cas*_*One 7

您假设C代码可以在任何处理器上运行是不正确的.寄存器和字节序之类的东西会使编译的C程序在一个平台上根本不起作用,而它可能在另一个平台上起作用.

但是,处理器共享有一些相似之处,例如,英特尔x86处理器和AMD处理器共享足够大的属性集,大多数代码编译的属性将在另一个上运行.但是,如果要使用特定于处理器的属性,则需要一个编译器或一组库来执行此操作.

至于为什么你想要一个虚拟机,除了它将为你处理处理器差异的声明之外,还有一个事实是虚拟机为今天用C++(未管理)编译的程序不可用的代码提供服务.

提供的最突出的服务是垃圾收集,由CLR和JVM提供.这两个虚拟机都免费为您提供此服务.他们为您管理记忆.

还提供了诸如边界检查,访问违规(尽管仍然可能,它们非常困难)之类的事情.

CLR还为您提供了一种代码安全形式.

对于许多不与虚拟机一起运行的其他语言,这些都不作为基本运行时环境的一部分提供.

您可以通过使用库来获取其中的一些,但随后会强制您使用库的模式,而在.NET和Java服务中,通过CLR和JVM提供给您的访问是一致的.


Ray*_*yat 5

从本质上讲,它允许“托管代码”,这正是它所说的——虚拟机在运行时管理代码。这样做的三个主要好处是即时编译、托管指针/垃圾收集和安全控制。

对于即时编译,虚拟机监视代码执行,因此随着代码运行的更频繁,它会重新优化以运行得更快。您无法使用本机代码执行此操作。

托管指针也更容易优化,因为虚拟机在它们运行时跟踪它们,根据它们的大小和生命周期以不同的方式管理它们。在 C++ 中很难做到这一点,因为你无法真正知道指针将去哪里,只是阅读代码。

安全性是不言自明的,虚拟机阻止代码做它不应该做的事情,因为它在监视。我个人认为这可能是 Microsoft 为 C# 选择托管代码的最大原因。

基本上我的观点是,因为虚拟机可以在代码发生时观察它,它可以做一些事情,让程序员的生活更轻松,并使代码更快。


Sco*_*ski 5

大多数编译器,甚至本机代码编译器,都使用某种中间语言。

这样做主要是为了降低编译器的构建成本。世界上有许多 (N) 种编程语言。世界上也有很多(M)个硬件平台。如果编译器在不使用中间语言的情况下工作,则需要编写以支持所有硬件平台上的所有语言的“编译器”总数将是 N*M。

但是,通过定义一种中间语言并将编译器分为前端和后端两部分,前端将源代码编译为 IL,后端将 IL 编译为机器代码,您可以摆脱只编写N+M 编译器。这最终会节省大量成本。

CLR/JVM 编译器和本机代码编译器之间的最大区别在于前端和后端编译器相互链接的方式。在本机代码编译器中,这两个组件通常组合成同一个可执行文件,当程序员在 IDE 中点击“构建”时,它们都会运行。

使用 CLR/JVM 编译器,前端和后端在不同的时间运行。前端在编译时运行,生成实际交付给客户的 IL。然后后端包含在一个单独的组件中,该组件在运行时被调用。

因此,这就引出了另一个问题,“将后端编译延迟到运行时有什么好处”?

答案是:“这取决于”。

通过将后端编译延迟到运行时,可以发布一组可以在多个硬件平台上运行的二进制文件。它还使程序无需重新部署即可利用后端编译技术的改进。它还可以为有效实现许多动态语言功能提供基础。最后,它提供了在单独编译的动态链接库 (dll) 之间引入安全性和可靠性约束的能力,这是前期机器代码编译无法实现的。

然而,也有缺点。实现广泛的编译器优化所需的分析可能很昂贵。这意味着“JIT”后端通常比前端后端做的优化更少。这会损害性能。此外,在运行时调用编译器的需要也增加了加载程序所需的时间。使用“预先”编译器生成的程序没有这些问题。