不太严谨了,应该算是添加新cap的虚拟化
学习
先找一个当前已经存在的feature看一下这个过程,比如KVM_CAP_S390_IRQCHIP这个cap。内核commit为(“KVM: s390: irq routing for adapter interrupts.”)
libvirt侧:virsh start
##virsh start启动一个虚机
cmdStart
--->virDomainCreateWithFlags ##去调用具体驱动的create domian,一般是qemu
--->qemuDomainCreateWithFlags
--->qemuProcessBeginJob
--->qemuDomainObjStart
--->qemuProcessStart
--->qemuProcessLaunch
--->qemuBuildCommandLine ##构建qemu命令
--->virCommandRun
--->virCommandRunAsync
--->virExec
qemu侧:开始创建虚机
main ##softmmu/main.,这主要就三条,qemu_init,qemu_main_loop,qemu_cleanup
--->qemu_init ##这里会parse传入的参数,然后根据参数调用对应的函数
--->configure_accelerators ##配置加速器,其实一般也就是kvm
--->do_configure_accelerator
--->accel_init_machine ##这里会调用acc->init_machine(ms);,kvm的话,也就是kvm_init
--->kvm_init
--->kvm_irqchip_create
--->kvm_check_extension(s, KVM_CAP_S390_IRQCHIP) ##检测是否存在这个feture(实际上是一个发一个KVM_CHECK_EXTENSION的ioctl),也就是实际上是检查kvm是否支持这个feature
--->kvm_vm_enable_cap(s, KVM_CAP_S390_IRQCHIP, 0); ##如果存在的话就enable
#########看一下kvm_vm_enable_cap函数
##其实是个宏,大致看上去就是初始化一个kvm_enable_cap结构体,其中cap是KVM_CAP_S390_IRQCHIP,flag是0
##然后初始化一个临时数组把后面的可变参数复制过来
##然后获取args_tmp和cap.args长度的最小值,应该是怕如果传入的可变参数太多导致cap.args溢出
##然后把可变参数的args_tmp数组内容copy到cap.args
##最后去调用kvm_vm_ioctl
#define kvm_vm_enable_cap(s, capability, cap_flags, ...) \
({ \
struct kvm_enable_cap cap = { \
.cap = capability, \
.flags = cap_flags, \
}; \
uint64_t args_tmp[] = { __VA_ARGS__ }; \
size_t n = MIN(ARRAY_SIZE(args_tmp), ARRAY_SIZE(cap.args)); \
memcpy(cap.args, args_tmp, n * sizeof(cap.args[0])); \
kvm_vm_ioctl(s, KVM_ENABLE_CAP, &cap); \
})
int kvm_vm_ioctl(KVMState *s, int type, ...)
{
int ret;
void *arg;
##这块其实是对可变参数的处理,这是一个可变参数的列表
va_list ap;
##初始化ap,指向type后的第一个参数
va_start(ap, type);
##从ap中提取一个void *类型的参数
arg = va_arg(ap, void *);
va_end(ap);
trace_kvm_vm_ioctl(type, arg);
accel_ioctl_begin();
ret = ioctl(s->vmfd, type, arg);
accel_ioctl_end();
if (ret == -1) {
ret = -errno;
}
return ret;
}
可以看到其实跟KVM_CAP_S390_IRQCHIP相关的就是初始化过程中通过KVM_CHECK_EXTENSION类型的ioctl去查kvm是否支持该feature,如果支持的话,就发KVM_ENABLE_CAP类型的ioctl去enable它
kernel侧:
##首先它在include/uapi/linux/kvm.h中定义了这个宏
#define KVM_CAP_S390_IRQCHIP 99
##然后看接收KVM_CHECK_EXTENSION ioctl的处理
kvm_vm_ioctl
--->kvm_vm_ioctl_check_extension_generic
--->kvm_vm_ioctl_check_extension ##这里直接返回了1,所以qemu的ioctl收到1就认为kvm支持这个feature
##在qemu的流程中,这里KVM_CHECK_EXTENSION的ioctl查到kvm支持这个feature了,就发KVM_ENABLE_CAP的ioctl去enable了
/* for KVM_ENABLE_CAP */
struct kvm_enable_cap {
/* in */
__u32 cap;
__u32 flags;
__u64 args[4];
__u8 pad[64];
};
kvm_vm_ioctl
--->copy_from_user ##从userspace把kvm_enable_cap结构体的内容copy过来
--->kvm_vm_ioctl_enable_cap_generic
--->kvm_vm_ioctl_enable_cap ##然后这里检查cap中的一些内容,然后打开进行enable的相关操作
另外,需要注意下,如果要对一些msr进行虚拟化或透传的话,如果是只有读操作还好,如果会往msr中进行写操作,就需要考虑很多事情了,比如:是否需要考虑虚机和宿主机侧修改msr数据不一致导致问题,是否需要补偿?vcpu从cpu间进行迁移的话需要怎么做?多个vcpu运行在一个cpu上需要怎么做?
实验
有一个MSR_PPIN msr,这个在虚机中肯定是没有虚拟化的,将它虚拟化试试看。添加一个新cap,协商cap并允许虚机访问这个msr
首先先看一下我的宿主机上这个msr的内容(左图)是可以读到的,然后看一下我的虚机中,是读不到的
开搞!!!
- 修改
qemu侧修改:
diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index be2a7adba7..2a37beaac9 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -2661,6 +2661,10 @@ static int kvm_init(MachineState *ms)
}
}
+ if(kvm_vm_check_extension(s, KVM_CAP_PPIN)) {
+ kvm_vm_enable_cap(s, KVM_CAP_PPIN, 0);
+ }
+
#ifdef KVM_CAP_VCPU_EVENTS
s->vcpu_events = kvm_check_extension(s, KVM_CAP_VCPU_EVENTS);
#endif
diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index 20a3755fa5..28f70cf1f9 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -1114,6 +1114,7 @@ struct kvm_ppc_resize_hpt {
#define KVM_CAP_ARM_MTE 205
#define KVM_CAP_XSAVE2 208
#define KVM_CAP_SYS_ATTRIBUTES 209
+#define KVM_CAP_PPIN 250
#define KVM_CAP_APERFMPERF 500
#define KVM_CAP_APERFMPERF2 501
这块其实主要就是在linux-headers/linux/kvm.h新定义一个KVM_CAP_PPIN cap号;然后通过kvm_vm_check_extension和kvm_vm_enable_cap查询和开启这个cap
kernel侧修改:
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 97c9363700ef..b3355fa61149 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -958,6 +958,7 @@ struct kvm_vcpu_arch {
#if IS_ENABLED(CONFIG_HYPERV)
hpa_t hv_root_tdp;
#endif
+ u64 ppin;
};
struct kvm_lpage_info {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 50841b390a2b..45db5ff78505 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4149,6 +4149,9 @@ int kvm_get_msr_common(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
msr_info->data = vcpu->arch.guest_fpu.xfd_err;
break;
+ case MSR_PPIN:
+ msr_info->data = vcpu->arch.ppin;
+ break;
#endif
default:
if (kvm_pmu_is_valid_msr(vcpu, msr_info->index))
@@ -4327,6 +4330,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_VAPIC:
case KVM_CAP_ENABLE_CAP:
case KVM_CAP_VM_DISABLE_NX_HUGE_PAGES:
+ case KVM_CAP_PPIN:
r = 1;
break;
case KVM_CAP_EXIT_HYPERCALL:
@@ -6366,6 +6370,9 @@ split_irqchip_unlock:
}
mutex_unlock(&kvm->lock);
break;
+ case KVM_CAP_PPIN:
+ r = 0;
+ break;
default:
r = -EINVAL;
break;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 85365bd8ec8d..2f5959fea170 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1184,6 +1184,7 @@ struct kvm_ppc_resize_hpt {
#define KVM_CAP_S390_ZPCI_OP 221
#define KVM_CAP_S390_CPU_TOPOLOGY 222
#define KVM_CAP_DIRTY_LOG_RING_ACQ_REL 223
+#define KVM_CAP_PPIN 250
#define KVM_CAP_APERFMPERF 500
#define KVM_CAP_APERFMPERF2 501
这块的主要修改有:新定义一个KVM_CAP_PPIN cap号,和qemu中相同;在kvm_vm_ioctl_check_extension和kvm_vm_ioctl_enable_cap中分别提供该cap的查询和enable;在kvm_vcpu_arch定义一个新的ppin变量,主要用于虚机访问msr时 导致vm exit退出时返回的msr的值;kvm_get_msr_common中处理MSR_PPIN,给msr_info->data赋值,也就是刚才的ppin
这里说明一下,这个并不是一个完整的例子,只是简单测试走一下流程。实际上一般来说,在enable这个cap的时候还需要做一些操作,比如设置一些标志之类,然后在create vcpu的时候根据标志进行处理;然后并没有给ppin进行赋值,实际上应该在vcpu_enter_guest中在vm enter和vm exit的时候对ppin进行赋值,这样在虚机中访问的时候得到的就是和宿主机上相同的值了。在在我上面的例子中,虚机中输出的应该是0,因为并没有给ppin赋值。
- 结果
此时虚机侧已经可以看到
后记
读msr的方法
- msr-tools
然后rdmsr 0x0000004f这样就可以读,比如
ps:但是我的虚机没有网,没法安装msr-tools
- 内核模块
可以写内核模块,使用内核rdmsr系列函数
ps:但是我的宿主机和虚机内核不是同一个内核,还得重编内核或者安装devel包,也有点麻烦
- 自己编写c程序
strace msr-tools的rdmsr过程可以看到,其实这个是通过读/dev/cpu/X/msr来实现的
那同样我们也可以读这个设备文件来实现,类似下面这样
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#define MSR_PPIN 0x0000004f
int main()
{
uint64_t val;
int fd = open("/dev/cpu/0/msr", O_RDONLY);
if (fd < 0) {
printf("open msr failed\n");
return -1;
}
if(pread(fd, &val, sizeof(val), MSR_PPIN) != sizeof(val)) {
printf("read msr failed\n");
close(fd);
return -1;
}
printf("MSR_PPIN val is %lx\n", val);
return 0;
}
参考
https://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 857879363@qq.com