KVM的执行引擎(下) — 指令集

本文介绍了Java虚拟机指令集的基本概念及其分类,并通过几个典型指令的实现案例,展示了这些指令如何在KVM虚拟机中被执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

指令集是虚拟机中最底层也是最核心的部分,Java程序中的变量赋值、函数调用等所有操作最后都要被转化为一条条的指令来执行。

指令集是在Java虚拟机规范中定义的,各种虚拟机实现要给予精确的实现,下面就来介绍一下指令集的分类以及在KVM中是如何实现的。

在头文件kvm/vmcommon/h/interpret.h中有如下对指令集种类的定义:

typedef enum {
        NOP         
= 0x00,
        ACONST_NULL 
= 0x01,
        ICONST_M1   
= 0x02,
……
        LASTBYTECODE          
= 0xDF
} ByteCode ;

以及每条指令的名字:

#define BYTE_CODE_NAMES {              
    
"NOP",              /*  0x00 */  
    
"ACONST_NULL",      /*  0x01 */  
"ICONST_M1",        /*  0x02 */  
……
"CUSTOMCODE"            /*  0xDF */ }

Java虚拟机的指令集非常多,大概有200种左右,本篇不详细介绍每一条指令的功能和参数,只选取几个典型的指令作为例子,介绍它们是如何实现的。

KVM中,所有指令的实现都放在kvm/vmcommon/src/bytecodes.c中,每一条指令都遵从如下的形式:

SELECT(指令号)
    {operations}
DONE(跳转位置)

注:
#define SELECT(l1)                      case l1: {
#define SELECT2(l1, l2)                 case l1: case l2: {
#define SELECT3(l1, l2, l3)             case l1: case l2: case l3: {
#define SELECT4(l1, l2, l3, l4)         case l1: case l2: case l3: case l4: {
#define SELECT5(l1, l2, l3, l4, l5)     case l1: case l2: case l3: case l4: case l5: {
#define SELECT6(l1, l2, l3, l4, l5, l6) case l1: case l2: case l3: case l4: case l5: case l6: {
#define DONE(n)    } goto next##n;
#define DONEX      }
#define DONE_R     } goto reschedulePoint;

{operations}部分是该指令的具体实现。

整个bytecodes.c文件其实是一个switch分支结构中的cases部分,这个文件中定义了所有的case。这个文件会被源文件kvm/vmcommon/src/execute.c所包含,execute.c中定义有一个方法

void SlowInterpret(ByteCode token);

它是解释执行Java指令的主要函数,参数token就是一条指令,在本函数中会有一个switch()结构来选择token的执行路径:
void SlowInterpret(ByteCode token) {

switch (token) {

#include 
"bytecodes.c"

next3:  ip
++;
next2:  ip
++;
next1:  ip
++;
next0:
reschedulePoint:
    
return;
}

函数结尾处的几个标签是指令完成后会跳转到的地方。

 

依据Java虚拟机规范,虚拟机指令可以分为装载和存储指令、运算指令、类型转换指令、对象创建与操纵指令、操作数栈管理指令、控制转移指令、方法调用和返回指令、抛出和处理异常指令、实现finally指令和同步指令等10类,下面从中选取几个简单的指令来看一看它们是如何设计的:

1ICONST_0

说明:

无参数,向操作数栈中压入int型常量0

实现代码:

SELECT(ICONST_0)       /* Push integer constant 0 onto the operand stack */
        pushStack(
0);
DONE(
1)
宏经适当展开后为:
case ICONST_0: {
    
*++GlobalState.gs_sp = 0;
goto next1;

GlobalState.gs_sp是当前帧内操作数栈的指针,ICONST_0指令要做的只是把指针向后移动一个字(注意是“字”而不是“字节”),然后给新字赋值为0;最后程序计数器ip自加1,表明没有跳转,接着执行下一条指令。

2DSTORE

说明:

本指令带有一个字节的参数offset,作用是从操作数栈中读取一个double型的值(双字)并存放到局部变量区中的offsetoffset+1位置。

实现代码:

SELECT(DSTORE)           /* Store double into local variable */
        unsigned 
int index = ip[1];
        lp[index
+1= popStack();
        lp[index]   
= popStack();
DONE(
2)
宏展开为:
case DSTORE: {
        unsigned 
int index = GlobalState.gs_ip[1];
        GlobalState.gs_lp[index
+1= *GlobalState.gs_sp --;
        GlobalState.gs_lp[index]   
= *GlobalState.gs_sp --;
goto next2;

首先从程序计数器的下一个字节中取出目标位置的偏移量index,然后从操作数栈中弹出两个字分别作为double型数的底位和高位存入局部变量lp所指向的区域中的合适位置。

3I2L

说明:

无参数,将操作数栈中的当前操作数由int型转换为long型。

实现代码:

SELECT(I2L)                              /* Convert integer to long */
        
long value = *(long *)sp;
#if BIG_ENDIAN
        ((
long *)sp)[1= value;
        ((
long *)sp)[0= value >> 31;
#elif LITTLE_ENDIAN || !COMPILER_SUPPORTS_LONG
        ((
long *)sp)[1= value >> 31;
#else
        SET_LONG(sp, value);
#endif
        getSP()
++;
DONE(
1)

由于longint表示的范围大,所以在扩展时多出来的高位只是用于符号扩展。先从操作数栈中取出int型整数并把它作为一个long型,如果定义了宏BIG_ENDIAN,说明操作数栈中的存储规则是高字节在前,这时要把value的值向后移一个字作为低字来用,高字用于作符号扩展;如果操作数栈中是低位在前的话,原位置中的字不用动,只要把下一个字作符号扩展即可。最近,由于当前操作数由一个字变为两个字,所以sp要自加1

4LMUL

说明:

无参数;从栈中弹出两个long型数,相乘,然后将所得long型结果压回栈。

实现代码:

SELECT(LMUL)                             /* Mul long */
        long64 rvalue 
= GET_LONG(sp - 1);
        long64 lvalue 
= GET_LONG(sp - 3);
        SET_LONG(sp 
- 3, ll_mul(lvalue, rvalue));
        getSP() 
-= 2;
DONE(
1)

 

先从操作数栈中分别取出两个双字长的长整型数,使用ll_mul()宏把它们相乘(这个宏的实现是依赖于操作系统的),然后再把相乘的结果写入栈中。整个操作从栈中弹出了四个字而压入两个,在过程中指针sp都没有变过,所以最后要把sp向前移两个字。

 

以下作为例子的都是一些简单的指令,但并不是所有指令都这样简单,像对象操作、异常处理和方法调用几类指令都十分的复杂,本篇只是演示指令的原理,所以不介绍太复杂的指令。

 

### 关于《计算机系统要素》与虚拟机的相关内容 《计算机系统要素》这本书的核心理念是从零开始构建一个完整的计算机系统,涵盖了从硬件到软件的各个层面[^5]。虽然该书并未专门针对虚拟机(VM)展开详细讨论,但它提供了一个全面的基础框架,可以帮助读者理解虚拟机的工作原理及其底层支持。 #### 1. **虚拟机的概念背景** 虚拟机本质上是一个模拟真实计算机环境的软件层,它依赖于宿主机的操作系统和硬件资源来运行。虚拟机的关键在于其抽象能力——通过将物理硬件的功能抽象成逻辑接口,使得多个独立的操作系统可以在同一台机器上并发运行[^2]。 #### 2. **《计算机系统要素》中的相关知识点** 尽管书中未直接提及虚拟机的具体实现细节,但以下几个章节的内容间接支撑了对虚拟机的理解: - **第7章:虚拟内存** 这一章介绍了操作系统如何利用虚拟地址空间管理程序的数据访问。虚拟内存是虚拟机技术的重要组成部分之一,因为它允许不同虚拟机之间相互隔离,同时高效地共享宿主机的实际物理内存。 - **第9章:汇编语言与虚拟机解释器** 此处探讨了编写简单的虚拟机解释器的过程,这是理解更复杂商业级虚拟机(如 KVM 或 VMware)的一个起点。通过这一实践环节,读者能体会到指令集架构的设计原则以及执行引擎的基本运作机制。 - **第10章:编译器与高级语言处理** 编译器负责将源代码转换为目标代码,在某些情况下还会生成中间表示形式供后续优化阶段使用。对于基于 JIT (Just-In-Time) 技术的虚拟机而言,这部分知识尤为重要,因为它们需要动态翻译字节码至本地机器码以便快速执行。 #### 3. **对比分析:KVM vs VMware** 根据已知信息[KVM 是一种开源解决方案][^1],而 [VMware 则代表闭源商业化产品]。两者均实现了不同程度上的硬件加速特性以提升性能表现;不过具体配置流程可能有所差异,请参阅官方文档获取最新指导说明[^4]。 ```bash # 示例命令行操作演示启动虚拟机实例 virsh start my-kvm-instance # 使用 libvirt 控制 KVM 实例 ./vmrun start /path/to/vm.vmx nogui # 启动无界面模式下的 VMware 虚拟机 ``` 上述脚本片段展示了两种主流方案各自的控制方式区别所在。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值