KVM的常量池

class文件中,“常量池”是最复杂也最值得关注的内容。

Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如intlong等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:

类和接口的全限定名;

字段的名称和描述符;

方法和名称和描述符。

C语言中,如果一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;

而在Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的话,记录进class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。

常量池由多条“常量池项”组成,每一个常量池项又由两部分组成,这里分别称为“常量池项头”和“常量池项体”。

常量池项头表明常量池项的类型,常量池项共分为11种类型,分别为:

常量池项类型

说明

CONSTANT_Utf8

1

UTF-8编码的Unicode字符串

CONSTANT_Integer

3

int型常量

CONSTANT_Float

4

Float型常量

CONSTANT_Long

5

Long型常量

CONSTANT_Double

6

double型常量

CONSTANT_Class

7

对一个class的符号引用

CONSTANT_String

8

String型常量

CONSTANT_Fieldref

9

对一个字段的符号引用

CONSTANT_Methodref

10

对一个类方法的符号引用

CONSTANT_InterfaceMedthodref

11

对一个接口方法的符号引用

CONSTANT_NameAndType

12

对名称和类型的符号引用

常量池项体中存放的就是对应的常量数据,比如各种数值型的常量或者字符串等等。

以下介绍kvm中的常量池是如何组织起来的。

 

数据结构:

KVM的头文件kvm/vmcommon/h/pool.h中,有以下对常量池项类型的定义:

#define CONSTANT_Utf8                       1
#define CONSTANT_Integer                    3
#define CONSTANT_Float                      4
#define CONSTANT_Long                       5
#define CONSTANT_Double                     6
#define CONSTANT_Class                      7
#define CONSTANT_String                     8
#define CONSTANT_Fieldref                   9
#define CONSTANT_Methodref                  10
#define CONSTANT_InterfaceMethodref    11
#define CONSTANT_NameAndType            12
 以及常量池项体结构的定义:
union constantPoolEntryStruct {
    
struct { 
        unsigned 
short classIndex;
        unsigned 
short nameTypeIndex;
    }               method;  
/* Also used by Fields */
    CLASS           clazz;
    INTERNED_STRING_INSTANCE String;
    cell           
*cache;   /* Either clazz or String */
    cell            integer;
    
long            length;
    NameTypeKey     nameTypeKey;
    NameKey         nameKey;
    UString         ustring;
};

class文件中,常量池项有很多种类,每一个常量池项的大小都不同,而对于常量池的使用又是如此之多,最好能够使用数组来索引,这样可以提高效率,所以KVM里使用union来代表一个常池项,union的每一项是常量池项的一种可能的数据类型,这样每一项都有了相同的大小,可以构造数组。

显然,这个数组就将是常量池的核心内容,那么这个数组放在哪里呢?就在下面这个结构中:

 

struct constantPoolStruct { 
    union constantPoolEntryStruct entries[
1];
};

这就是常量池。这个常量池的设计很有意思:

1、这个结构体中只有一个指针,指向一个常量池项体数组,数组中元素的个数是常量池项数+1,数组中的第一项(即序号为0的那一项)不是实际的常量池项体,而是存放了常量池项的数目,即表明了数组中接下来的元素数。要取得数组的长度信息,只有一个办法,就是读数组的第一个元素,为不造成空指针错误,所以constantPoolStruct在定义的时候就要保证数组的第0个元素必须存在,所以上面的entries在定义时就被指定为长度为1的数组。

单纯从数据结构的设计角度来看,我认为constantPoolStruct的设计并不是很清晰,使用数组的第一个无素来表示数组的长度多少一点显得混乱,明明可以在constantPoolStruct的结构里增加一个变量来表明数组长度,这样不是更清晰吗?之所以这样做,我想也是与class文件中常量池的设计惯例有关。在class文件中, constant_pool紧跟在constant_pool_count之后,而constant_pool_count = constant_pool中实际的项数+1,相当于constant_pool_count也把自己当成了常量池中的第一项。

由此可见,KVM的常量池设计与class文件如出一辙。

2、常量池项体以一个union来表示,而union不带有自身类型的信息,如何知道一个常量池项的类型呢?

在一个class文件的常量池被载入后,生成了constantPoolStruct结构体的实例,在其中constantPoolEntryStruct数组的最后一项之后,一定会跟随一个字节数组,这个数组中的每一个字节就是一个“常量池项头”,长度与实际的常量池项数相同,即constant_pool_count-1,在这个字节中就指明了相应常量池项的类型。

 

程序实现:

构造常量池的代码段主要在kvm/vmcommon/src/loader.cloadConstantPool()函数中,函数原形如下:

static POINTERLIST

loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass)

两个参数分别为类文件的句柄以及当前被载入类的指针。

这个函数的总体流程如下:

1- 循环读取文件中常量池中所有项,把,把各项内容存入临时数组RowPool中;(L649~L740)

2- 计算常量池所占空间大小(以constantPoolEntryStruct枚举体数计),并申请常量池空间;(L742~L757)

3- 循环读取暂存在RowPool中的常量信息,为常量池赋值。

其中第2步值得一看,记算空间大小的那一行如下:

 

int tableSize = numberOfEntries + ((numberOfEntries + (4 - 1)) >> 2);

一个constantPoolEntryStruct枚举体的大小为4,前面讲过,在constantPoolEntryStruct数组的后要跟有一个字节数组来存放常量池项的类型信息,即每一个constantPoolEntryStruct要对应1个字节的常量池项头,所以当以constantPoolEntryStruct枚举体数为单位给常量池项头数组申请空间时,需要向4字节对齐,每多1~4个常量池项头,就要多申请一个constantPoolEntryStruct。这一句就是这个意思。

loadConstantPool函数执行过程中,会把新生成的常量池指针赋给CurrentClass->constPool,这样,这个类实例中就有完整的常量池了。

### 创建和配置KVM中的NFS存储池 #### 准备工作 为了创建基于NFS的KVM存储池,需先准备好一个能够提供网络文件系统的服务器。这通常涉及安装并配置NFS服务端软件包,在目标目录上启用共享功能,并确保防火墙允许必要的通信流量通过[^1]。 #### 配置NFS服务器 假设已经有一台名为`storage`的机器作为NFS服务器,则应在该设备上的/etc/exports文件中定义要分享给其他计算机使用的路径及其权限设置;之后重启nfs-server服务使更改生效。例如: ```bash /images *(rw,sync,no_subtree_check) ``` 此命令表示将`/images`目录以读写模式公开给所有客户端访问[^2]。 #### 客户端操作 对于打算利用上述资源运行虚拟机实例的工作站(比如这里的`node1`),则需要执行以下步骤完成连接到远程位置的操作: - **挂载 NFS 文件系统** 使用mount指令把远端提供的image库映射成本地可用的空间。具体做法如下所示: ```bash [root@jay-linux kvm_demo]# mount storage:/images /mnt/nfs_images/ ``` 这里假定已提前建立了/mnt/nfs_images这个本地挂载点用于接收来自storage主机的数据传输[^3]。 - **创建 QCOW2 虚拟硬盘** 接下来可以借助qemu-img工具构建新的磁盘镜像文件,指定其格式为qcow2的同时还可以关联一个基础镜像作为快照源。示例代码片段如下: ```bash [root@jay-linux kvm_demo]# qemu-img create -f qcow2 \ -o backing_file=/mnt/nfs_images/base_image.img,size=20G new_vm_disk.qcow2 ``` - **启动 KVM 实例** 终于到了最后一步——激活新搭建好的环境!只需调用kvm/qemu应用程序指明所需的硬件参数即可轻松实现这一点。下面给出了一组典型的选项组合供参考: ```bash [root@jay-smp cpus=2 -m ram=1024M \ -net user,hostfwd=tcp::2222-:22 -net nic,model=virtio \ -drive file=new_vm_disk.qcow2,if=virtio,cache=writethrough \ -vnc :0,password -daemonize ``` 以上过程展示了怎样在一个支持KVM技术架构下的Linux平台上成功部署由外部NFS供给支撑起来的持久化数据卷解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值