Dalvik寄存器
Dalvik虚拟机基于寄存器架构,在代码中大量地使用到了寄存器。
Dalvik将部分寄存器映射到了ARM寄存器上(ARM架构的CPU本身集成了多个寄存器),还有一部分则通过调用栈进行模拟。
Dalvik用到的寄存器都是32位的,支持任何类型。
Dalvik指令的语法为“op vAAAA,vBBBB”,每个大写字母代表4位(16进制),即最大值为2^16=65536,所以寄存器的取值范围为v0~v65535。
Dalvik虚拟机为每个进程维护一个调用栈,这个调用栈其中一个作用就是用来“虚拟”寄存器。
每个函数都在函数头部使用.registers指定函数用到的寄存器数目,当虚拟机执行到这个函数时会根据寄存器的数目分配适当的栈空间,用来存放寄存器实际的值。
虚拟机通过处理字节码对寄存器进行读写操作,其实都是在写栈空间。
Android SDK中有一个名为 dalvik.bytecode.Opcodes 的接口,它定义了一份完整的 Dalvik 字节码列表。处理这些字节码的函数为一个宏函数 HANDLE_OPCODE() ,每个字节码的处理过程都可以在 Android 源码的 dalvik\vm\mterp\c 目录中找到。
以 OP_MOVE 做例子:
HANDLE_OPCODE($opcode /*vA, vB*/)
vdst = INST_A(inst);
vsrc1 = INST_B(inst);
ILOGV("|move%s v%d,v%d %s(v%d=0x%08x)",
(INST_INST(inst) == OP_MOVE) ? "" : "-object", vdst, vsrc1,
kSpacing, vdst, GET_REGISTER(vsrc1));
SET_REGISTER(vdst, GET_REGISTER(vsrc1));
FINISH(1);
OP_END
关于 INST_A 和 INST_B 的定义可在同目录下的 header.cpp 文件中找到:
/*
* Extract the "vA, vB" 4-bit registers from the instruction word (_inst is u2).
*/
#define INST_A(_inst) (((_inst) >> 8) & 0x0f)
#define INST_B(_inst) ((_inst) >> 12)
INST_A 将 _inst 右移8位后和 0x0f相与,即获得 _inst 高8位的低4位作为 vdst 的值;
INST_B 将 _inst 右移12位,即获得 _inst 的最高4位为 vsrc1 的值。
ILOGV 用来输出调试信息:
# define ILOGV(...) ILOG(LOG_VERBOSE, __VA_ARGS__)
INST_INST 的作用为:(获得 _inst 的低8位)
/*
* Extract instruction byte from 16-bit fetch (_inst is a u2).
*/
#define INST_INST(_inst) ((_inst) & 0xff)
GET_REGISTER 用来获取寄存器的值, SET_REGISTER 用来设置寄存器的值:
# define GET_REGISTER(_idx) (fp[(_idx)])
# define SET_REGISTER(_idx, _val) (fp[(_idx)] = (_val))
另外,操作的寄存器可以是其他的大小和类型,如 GET_REGISTER_WIDE、GET_REGISTER_FLOAT等:
# define GET_REGISTER_WIDE(_idx) getLongFromArray(fp, (_idx))
# define SET_REGISTER_WIDE(_idx, _val) putLongToArray(fp, (_idx), (_val))
# define GET_REGISTER_FLOAT(_idx) (*((float*) &fp[(_idx)]))
# define SET_REGISTER_FLOAT(_idx, _val) (*((float*) &fp[(_idx)]) = (_val))
其中,fp为ARM栈帧寄存器(从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等,C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量),在虚拟机运行到某个函数时它指向函数的局部变量区,其中就维护着一份寄存器值的列表;
GET_REGISTER 宏以 _idx 为索引返回一个“寄存器”的值,而 SET_REGISTER 则是以 _idx 为索引设置相应寄存器的值。
如果 Dalvik 虚拟机开启了寄存器数目验证,即 #ifdef CHECK_REGISTER-INDICES 为真时,在进行寄存器读写时,虚拟机会首先判断 _idx 是否小于 curMrthod -> registerSize , 如果条件不成立则说明寄存器超出引用范围,此时虚拟机会通过 assert(!"bad reg") 抛出异常(源码在 header.cpp的第 219 行起)。
最后一条指令是 FINISH ,用来完成一条指令的执行,功能由 ADJUST_PC 完成,主要是计算当前指令占用的长度,将 PC 寄存器加上计算出的偏移,这样一条指令执行完成后, PC 计数器会指向下一条要执行的指令。
理解有待深化。。