热搜
您的位置:首页 >> 旅游

KVM的执行引擎下指令集

2019年11月05日 栏目:旅游

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

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

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

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

typedefenum{

NOP=0x00,

ACONST_NULL=0x01,

ICONST_M1=0x02,

……

LASTBYTECODE=0xDF

}ByteCode;

以及每条指令的名字:

#defineBYTE_CODE_NAMES{

"NOP",/*0x00*/

"ACONST_NULL",/*0x01*/

"ICONST_M1",/*0x02*/

……

"CUSTOMCODE"/*0xDF*/}

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

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

SELECT(指令号)

{operations}

DONE(跳转位置)

注:

#defineSELECT(l1)casel1:{

#defineSELECT2(l1,l2)casel1:casel2:{

#defineSELECT3(l1,l2,l3)casel1:casel2:casel3:{

#defineSELECT4(l1,l2,l3,l4)casel1:casel2:casel3:casel4:{

#defineSELECT5(l1,l2,l3,l4,l5)casel1:casel2:casel3:casel4:casel5:{

#defineSELECT6(l1,l2,l3,l4,l5,l6)casel1:casel2:casel3:casel4:casel5:casel6:{

#defineDONE(n)}gotonext##n;

#defineDONEX}

#defineDONE_R}gotoreschedulePoint;

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

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

void SlowInterpret(ByteCode token);

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

switch(token){

#include"bytecodes.c"

next3:ip++;

next2:ip++;

next1:ip++;

next0:

reschedulePoint:

return;

}

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

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

1、ICONST_0

说明:

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

实现代码:

SELECT(ICONST_0)/*Pushintegerconstant0ontotheoperandstack*/

pushStack(0);

DONE(1)

宏经适当展开后为:

caseICONST_0:{

*++GlobalState.gs_sp=0;

}gotonext1;

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

2、DSTORE

说明:

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

实现代码:

SELECT(DSTORE)/*Storedoubleintolocalvariable*/

unsignedintindex=ip[1];

lp[index+1]=popStack();

lp[index]=popStack();

DONE(2)

宏展开为:

caseDSTORE:{

unsignedintindex=GlobalState.gs_ip[1];

GlobalState.gs_lp[index+1]=*GlobalState.gs_sp--;

GlobalState.gs_lp[index]=*GlobalState.gs_sp--;

}gotonext2;

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

3、I2L

说明:

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

实现代码:

SELECT(I2L)/*Convertintegertolong*/

longvalue=*(long*)sp;

#ifBIG_ENDIAN

((long*)sp)[1]=value;

((long*)sp)[0]=value>>31;

#elifLITTLE_ENDIAN||!COMPILER_SUPPORTS_LONG

((long*)sp)[1]=value>>31;

#else

SET_LONG(sp,value);

#endif

getSP()++;

DONE(1)

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

4、LMUL

说明:

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

实现代码:

SELECT(LMUL)/*Mullong*/

long64rvalue=GET_LONG(sp-1);

long64lvalue=GET_LONG(sp-3);

SET_LONG(sp-3,ll_mul(lvalue,rvalue));

getSP()-=2;

DONE(1)

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

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

郑州风湿医院
贵阳哪家医院治疗癫痫病
南阳治疗睾丸炎的男科医院
郑州治疗宫颈炎费用
浙江治疗男科医院
  • 友情链接
  • 合作媒体