admin管理员组文章数量:1633334
文章目录
- 硬件基础
- vendor
- signature
- feature
- 数据结构
- 物理抽象
- virCPUx86CPUID
- virCPUx86DataItem
- virCPUx86Data
- 配置信息
- virCPUx86Vendor
- virCPUx86Feature
- virCPUx86Model
- virCPUx86Map
- 概念抽象
- virCPUFeatureDef
- virCPUDef
- 工具函数
- 数据加载
- virCPUx86LoadMap
- 基本操作
- virCPUx86DataItemCmp
- virCPUx86DataNext
- virCPUx86DataGet
- virCPUx86DataItemAndBits
- virCPUx86DataItemClearBits
- 集合操作
- x86DataAdd
- x86DataSubtract
- x86DataIntersect
- 信息提取
- x86DataToCPUFeatures
- x86DataToVendor
- virsh 工具
- capabilities
- cpu-baseline
- cpu-compare
硬件基础
- 虚拟机的迁移需要保证两端的主机能够暴露给虚机的cpu feature相同,如果不相同无法保证迁移后虚机能够正常运行,因此迁移前会判断两端虚机特性,不同会报错,终止迁移。为了让资源池中新加入一台服务器并能够顺利将虚机迁移过去,需要计算源和目的两端服务器的cpu feature交集,然后暴露给虚机,保证迁移顺利,这是虚拟化组件需要解决的事情。本节主要介绍其硬件基础。
- Intel通过cpuid指令查询cpu的signature和feature,signature标识着cpu的版本信息,feature包含了cpu的支持的硬件特性,比如虚拟化相关的vmx(Virtual Machine Extensions)特性,内存管理相关的pae(Physical Address Extension)特性,或者MSR(Model Specific Registers)等。通过cpuid指令可以查到两类信息,一类是cpu基本(Basic)信息,一类是cpu扩展(Extended)信息。
- cpuid指令虽然没有操作数,但它的输出较其它有操作数的指令更为复杂,它将寄存器(EAX,ECX)作为输入,将寄存器(EAX,EBX,ECX,EDX)作为输出。输入的不同,执行cpuid指令得到的输出信息不同。比如,在EAX=0,ECX=0的情况下,如果执行cpuid指令,得到的是cpu的vendor信息和输入EAX的最大值;在EAX=1,ECX=0的情况下,如果执行cpuid指令,得到的是cpu的signature和feature。CPU feature相关的cpuid指令就是上面这两个,下面具体介绍它们的输出格式,以及如何获取vendor、signature和feature信息。关于cpu指令的详细介绍,参考intel手册的vol 2A-3.3-CPU Identification小节的介绍。demo可参考:cpuid demo
vendor
- 当EAX被设置成0,作为输入时,执行cpuid指令,它的输出分别是EAX,保存EAX作为输入的最大值和vendor信息。Intel手册中关于vendor信息的获取说明如下:
- 假设最大输入值为Maximum,那么EAX作为输入的取值范围就是[0, Maximum]。当EAX在这区间范围内作为输入时,执行cpuid指令输出的信息为Basic类信息。vendor为厂商信息,由EBX/ECX/ECX三个寄存器共同提供。对于x86架构有三个厂商信息,分别是:GenuineIntel(Intel)、AuthenticAMD(AMD)和HygonGenuine(Hygon)
signature
- 当EAX被设置成1,作为输入时,执行cpuid指令,它的输出寄存器中,EAX保存cpu的signature的信息。ECX和EDX保存cpu的feature信息,Intel手册说明如下:
- EAX的signature信息格式如下:
feature
- feature信息和signature一样,通过设置EAX为1执行cpuid指令得到。它的信息保存在两个寄存器ECX、EDX中,寄存器的每个bit标识着一个cpu的特性,如果该bit被置位,表示cpu支持该特性,反之,如果该bit被清零,标志cpu不支持该特性。
- ECX和ECX表示的feature格式如下:
数据结构
- Libvirt提供了探测主机cpu feature的工具、计算指定cpu feature集合与主机cpu feature集合关系的工具,以及计算不同cpu feature交集的工具。通过这些工具,上层应用可以计算出两个cpu之间feature的交集,从而决定如何暴露给虚拟机,顺利实现虚机的迁移。本节主要介绍Libvirt中与cpu feature相关的数据结构。
物理抽象
virCPUx86CPUID
- virCPUx86CPUID用于描述执行一条cpuid指令前后的输入和输出。
typedef struct _virCPUx86CPUID virCPUx86CPUID;
typedef virCPUx86CPUID *virCPUx86CPUIDPtr;
struct _virCPUx86CPUID {
uint32_t eax_in; /* 输入:寄存器EAX的值 */
uint32_t ecx_in; /* 输入:寄存器ECX的值 */
uint32_t eax; /* 输出:寄存器EAX的值 */
uint32_t ebx; /* 同上 */
uint32_t ecx;
uint32_t edx;
};
- 在Libvirt中,使用嵌入式汇编调用cpuid指令,在执行过程中用到了virCPUx86CPUID结构,如下:
cpuidCall(virCPUx86CPUID *cpuid)
{
asm("xor %%ebx, %%ebx;" /* clear the other registers as some cpuid */
"xor %%edx, %%edx;" /* functions may use them as additional arguments */
"cpuid;"
: "=a" (cpuid->eax), /* 将eax的值作为输出保存到cpuid->eax中*/
"=b" (cpuid->ebx), /* 原理同上 */
"=c" (cpuid->ecx),
"=d" (cpuid->edx)
: "a" (cpuid->eax_in), /* 指定寄存器eax的值从cpuid->eax_in中读取 */
"c" (cpuid->ecx_in)); /* 指定寄存器ecx的值从cpuid->ecx_in中读取 */
}
virCPUx86DataItem
- virCPUx86DataItem在type为VIR_CPU_X86_DATA_CPUID时,保存的是一条cpuid指令输入输出。
typedef struct _virCPUx86DataItem virCPUx86DataItem;
typedef virCPUx86DataItem *virCPUx86DataItemPtr;
struct _virCPUx86DataItem {
virCPUx86DataType type; /* 当type=VIR_CPU_X86_DATA_CPUID时,data的cpuid值有效 */
union {
virCPUx86CPUID cpuid; /* 保存一条cpuid的输入输出*/
virCPUx86MSR msr;
} data;
};
virCPUx86Data
- 对于一个cpu来说,当使用cpuid指令查询它的相关信息时,它会有很多的输出,每改变一次输入的值,执行cpuid查到的输出值意义就不一样,因此cpuid的输入输出组成的条目非常多,Libvirt通过virCPUx86Data来描述执行cpuid的所有输入输出组成的数组。virCPUx86Data可以认为保存的是cpuid指令查询后,得到的原始数据。
typedef struct _virCPUx86Data virCPUx86Data;
struct _virCPUx86Data {
size_t len; /* 数组的大小,数组的每个元素表示cpuid指令的一个输入输出 */
virCPUx86DataItem *items; /* 数组的基地址 */
};
配置信息
- Libvirt为了管理cpu feature,将支持的所有架构的所有feature组织成xml文件,持久化到磁盘上,同时将支持的所有model和vendor也组织成xml文件,持久化到磁盘上。在libvirt获取host capabilities或者计算feature交集时,会首先将支持的所有feature,vendor,model都加载到内存中,用于feature集合的计算。xml所在目录为/usr/share/libvirt/cpu_map/,下面主要介绍这些xml格式在内存中的数据结构。
virCPUx86Vendor
- vendor信息可以通过cpuid指令查询得到,virCPUx86Vendor结构用于存放查询vendor的cpuid指令的输入输出,组成的一个item。
typedef struct _virCPUx86Vendor virCPUx86Vendor;
typedef virCPUx86Vendor *virCPUx86VendorPtr;
struct _virCPUx86Vendor {
char *name; /* 厂商名称,对于x86架构,可能的名称就是Intel、AMD和Hygon */
virCPUx86DataItem data; /* cpuid查询得到的条目,它的输出EBX、ECX和EDX就是厂商名称的accii码值 */
};
- Libvirt保存了支持的vendor信息到xml中,当virsh工具需要处理vendor相关信息时,Libvirt从xml中读取支持的vendor信息,加载到内存,对应的数据结构就是virCPUx86Vendor。Libvirt的vendor信息保存在/usr/share/libvirt/cpu_map/x86_vendors.xml 中,如下:
<cpus>
<vendor name='Intel' string='GenuineIntel'/> /* 厂商名: Intel; 输出EBX/ECX/EDX组成的字符串为'GenuineIntel' */
<vendor name='AMD' string='AuthenticAMD'/>
<vendor name='Hygon' string='HygonGenuine'/>
</cpus>
virCPUx86Feature
- cpu的feature信息也通过cpuid指令查询得到,virCPUx86Feature的data域存放的数组通常只有一个元素,代表一个bit对应的feature。
typedef struct _virCPUx86Feature virCPUx86Feature;
typedef virCPUx86Feature *virCPUx86FeaturePtr;
struct _virCPUx86Feature {
/* feature名 */
char *name;
/* feature对应的寄存器值,对应寄存器中的一个bit
* 这里看上去data包含的是一个item数组
* 但实际上通常情况下只有一个元素 */
virCPUx86Data data;
/* 如果该feature不影响迁移(可迁移)
* 设置为true,反之,设置为false */
bool migratable;
};
- libvirt将所有feature以一定格式组织起来,存放到/usr/share/libvirt/cpu_map/x86_features.xml中,这个xml描述了x86架构下所有厂商的feature属性,包括该通过什么输入得到,输出的feature值对应寄存器的哪一位;该feature是否可以迁移等。每当Libvirt需要计算feature时,将这些feature加载到内存,进行操作。xml中一个典型的feature描述如下:
<feature name='vmx'> /* feature名字: vmx*/
<cpuid eax_in='0x01' ecx='0x00000020'/> /* 获取feature时输入EAX的值为0x01,该feature对应输出ECX的第5bit */
</feature>
virCPUx86Model
- cpu的model信息通过cpuid指令查到,得到的原始数据被保存到一个virCPUx86Data数据结构中。同时,virCPUx86Model结构中还将原数据解析出来,分别存放到vendor和signature中。
typedef struct _virCPUx86Model virCPUx86Model;
typedef virCPUx86Model *virCPUx86ModelPtr;
struct _virCPUx86Model {
char *name; /* Model名 */
virCPUx86VendorPtr vendor;
size_t nsignatures;
uint32_t *signatures;
virCPUx86Data data;
};
virCPUx86Map
typedef struct _virCPUx86Map virCPUx86Map;
typedef virCPUx86Map *virCPUx86MapPtr;
struct _virCPUx86Map {
size_t nvendors;
virCPUx86VendorPtr *vendors;
size_t nfeatures;
virCPUx86FeaturePtr *features;
size_t nmodels;
virCPUx86ModelPtr *models;
size_t nblockers;
virCPUx86FeaturePtr *migrate_blockers;
};
概念抽象
virCPUFeatureDef
- 使用cpuid指令查询得到的cpu feature被保存到两个寄存器中,寄存器的每个bit代表一个feature,Libvirt对应地将每个feature抽象成一个virCPUFeatureDef数据结构。结构中的policy描述了vcpu采用该feature的策略。
typedef enum {
VIR_CPU_FEATURE_FORCE, /* vcpu需要强行应用该feature,无论主机是否支持此feature */
VIR_CPU_FEATURE_REQUIRE, /* vcpu请求使用该feature,如果主机不支持或者hypervisor无法模拟,虚机在start的时候会报错 */
VIR_CPU_FEATURE_OPTIONAL, /* vcpu只有在探测到主机支持该feature时,才使用该feature,因此这种策略不会虚机启动不会报错 */
VIR_CPU_FEATURE_DISABLE, /* vcpu被主机禁止使用该feature */
VIR_CPU_FEATURE_FORBID, /* 禁止主机支持该feature。如果主机支持,虚机启动时报错*/
VIR_CPU_FEATURE_LAST
} virCPUFeaturePolicy;
typedef struct _virCPUFeatureDef virCPUFeatureDef;
typedef virCPUFeatureDef *virCPUFeatureDefPtr;
struct _virCPUFeatureDef {
char *name; /* feature名字,比如'vmx'、'msr'等 */
int policy; /* enum virCPUFeaturePolicy */
};
virCPUDef
- cpu的capabilities信息展示给用户,通常包含架构、厂商、Model、特性等等,Libvirt将这些cpu相关的信息包含在virCPUDef结构中。
typedef struct _virCPUDef virCPUDef;
typedef virCPUDef *virCPUDefPtr;
struct _virCPUDef {
int type; /* enum virCPUType */
int mode; /* enum virCPUMode */
int match; /* enum virCPUMatch */
virCPUCheck check;
virArch arch;
char *model;
char *vendor_id; /* vendor id returned by CPUID in the guest */
int fallback; /* enum virCPUFallback */
char *vendor; /* 厂商名 */
......
size_t nfeatures;
size_t nfeatures_max;
virCPUFeatureDefPtr features; /* CPU包含的除Model外的所有feature集合 */
......
};
工具函数
数据加载
virCPUx86LoadMap
- 该函数主要负责将/usr/share/libvirt/cpu_map目录下的所有信息加载到内存中,最终保存在静态全局变量cpuMap中,virCPUx86LoadMap函数只在Libvirtd启动时执行一次。一旦cpuMap被加载到内存中,后续的所有关于cpu feature集合的计算都使用这个结构
基本操作
virCPUx86DataItemCmp
int virCPUx86DataItemCmp(const virCPUx86DataItem *item1, const virCPUx86DataItem *item2)
- 比较item1和item2是否相同,比较的标准是判断查询cpuid的输入寄存器eax_in和ecx_in的值,如果值相等表示相同,反之则不同。
virCPUx86DataNext
virCPUx86DataItemPtr virCPUx86DataNext(virCPUx86DataIteratorPtr iterator)
- 根据迭代器中的位置pos,取出其指向的非零item。
virCPUx86DataGet
virCPUx86DataItemPtr virCPUx86DataGet(const virCPUx86Data *data, const virCPUx86DataItem *item)
- 根据item中的eax_in和ecx_in,查找data集合中对应的item,取出data集合中对应的item。
virCPUx86DataItemAndBits
void virCPUx86DataItemAndBits(virCPUx86DataItemPtr item, const virCPUx86DataItem *mask)
- 将item中对应的eax,ebx,ecx,edx中与mask中eac,ebx,ecx,edx进行位与操作
virCPUx86DataItemClearBits
void virCPUx86DataItemClearBits(virCPUx86DataItemPtr item, const virCPUx86DataItem *mask)
- 根据mask提供的掩码位,将item中对应的eax,ebx,ecx,edx中对应的位请零
集合操作
x86DataAdd
int x86DataAdd(virCPUx86Data *data1, const virCPUx86Data *data2)
- 函数将data2集合中的所有非空的item添加到data1集合中,所谓空item,item中的eax,ebx,ecx,edx都为0。添加有两种情况,如果data1中已经存在对应的item,将data2中的item与data1中对应的item取并集操作,否则直接将data2中的item内容拷贝到data1集合中。该函数提供了feature集合的加操作。
static int
x86DataAdd(virCPUx86Data *data1,
const virCPUx86Data *data2)
{
virCPUx86DataIterator iter;
virCPUx86DataItemPtr item;
/* 初始化遍历data2集合的迭代器
* 为遍历data2做准备 */
virCPUx86DataIteratorInit(&iter, data2);
/* 将data2中所有非空的item取出 */
while ((item = virCPUx86DataNext(&iter))) {
/* 将item添加到data1中 */
if (virCPUx86DataAddItem(data1, item) < 0)
return -1;
}
return 0;
}
x86DataSubtract
void x86DataSubtract(virCPUx86Data *data1, const virCPUx86Data *data2)
- 从data1集合中,减去data2集合中包含的所有item。该函数提供了feature集合的减操作。
static void
x86DataSubtract(virCPUx86Data *data1,
const virCPUx86Data *data2)
{
virCPUx86DataIterator iter;
virCPUx86DataItemPtr item1;
virCPUx86DataItemPtr item2;
/* 初始化遍历data1集合的迭代器 */
virCPUx86DataIteratorInit(&iter, data1);
/* 遍历data1,依次取出它包含的item */
while ((item1 = virCPUx86DataNext(&iter))) {
/* 遍历data2集合中的item,
* 如果有与data1中item相等的
* 将data1中的对应item清零 */
item2 = virCPUx86DataGet(data2, item1);
virCPUx86DataItemClearBits(item1, item2);
}
}
x86DataIntersect
void x86DataIntersect(virCPUx86Data *data1, const virCPUx86Data *data2)
- 该函数将data1集合中的item依次与data2集合中的item比较,如果data1集合中的item在data2集合中没有,则将data1中的Item删除掉,如果有,则将data2中的对应item也取出来,两个item取交集,结果存放在data1中。该函数实现了取集合交集的操作。
static void
x86DataIntersect(virCPUx86Data *data1,
const virCPUx86Data *data2)
{
virCPUx86DataIterator iter;
virCPUx86DataItemPtr item1;
virCPUx86DataItemPtr item2;
/* 初始化遍历data1集合的迭代器 */
virCPUx86DataIteratorInit(&iter, data1);
/* 遍历data1,依次取出它包含的item */
while ((item1 = virCPUx86DataNext(&iter))) {
/* 查找data2,如果集合中有包含data1中的item将其取出 */
item2 = virCPUx86DataGet(data2, item1);
if (item2)
/* 如果data2中有相同的item,与data1中的item进行位与操作 */
virCPUx86DataItemAndBits(item1, item2);
else
/* 如果data2中没有对应item,将data1中的item清零 */
virCPUx86DataItemClearBits(item1, item1);
}
}
信息提取
x86DataToCPUFeatures
int x86DataToCPUFeatures(virCPUDefPtr cpu, int policy, virCPUx86Data *data, virCPUx86MapPtr map)
- 将data集合中包含的所有属于map的feature添加到cpu的features域中。遍历map中包含的所有feature,检查它的每一个item是否属于data集合的子集,如果是,将data中对应的item减去,同时将feature添加加入到cpu的features中。注意,加入到cpu的features中的feature在data集合中就被删除了
/* also removes all detected features from data */
static int
x86DataToCPUFeatures(virCPUDefPtr cpu,
int policy,
virCPUx86Data *data,
virCPUx86MapPtr map)
{
size_t i;
for (i = 0; i < map->nfeatures; i++) {
/* 依次取出x86_features.xml中的所有feature */
virCPUx86FeaturePtr feature = map->features[i];
/* 如果取出的feature属于data表示的集合 */
if (x86DataIsSubset(data, &feature->data)) {
/* 将feature从data集合中删除 */
x86DataSubtract(data, &feature->data);
/* 同时将feature添加到cpu->features中 */
if (virCPUDefAddFeature(cpu, feature->name, policy) < 0)
return -1;
}
}
return 0;
}
x86DataToVendor
- 返回map中与data集合中匹配的第一个vendor。从data集合取出vendor,与map中包含的所有vendor比较,如果有相同的item,首先清零data中对应的item,同时返回map中指向的vendor。注意,在map匹配到对应vendor后,同时会删除data中对应的vendor。
/* also removes bits corresponding to vendor string from data */
static virCPUx86VendorPtr
x86DataToVendor(const virCPUx86Data *data,
virCPUx86MapPtr map)
{
virCPUx86DataItemPtr item;
size_t i;
for (i = 0; i < map->nvendors; i++) {
/* 依次取出x86_vendors.xml中所有的vendor */
virCPUx86VendorPtr vendor = map->vendors[i];
/* 如果data集合中包含了该vendor对应的item(输入值相同) */
if ((item = virCPUx86DataGet(data, &vendor->data)) &&
/* 同时取出的item与vendor中对应item的输出值也相同 */
virCPUx86DataItemMatchMasked(item, &vendor->data)) {
/* 将data中包含的vendor对应的item清零 */
virCPUx86DataItemClearBits(item, &vendor->data);
/* 返回map中包含的vendor */
return vendor;
}
}
return NULL;
}
virsh 工具
- Libvirt提供了三个与cpu feature相关的接口,分别是:
- virsh capabilites: 探测主机包含的cpu feature。该接口可以搜集主机支持的cpu feature集合。
- virsh cpu-baseline: 给出多个cpu feature的集合,计算这些cpu feature集合的基线,实际上就是取给出的所有cpu feature集合的交集。该接口可以用于计算多个主机共同的的cpu feature集合,从而得到可以保证迁移成功的虚拟机cpu feature配置。
- virsh cpu-compare: 给出一个cpu feature的集合,将它与主机的cpu feature作集合的比较,得到两个集合的关系(超集、子集、相等或者非包含关系)。该接口可以用于判断远端的一个host上的虚机是否可以迁移到本地。
- 下面分别介绍三个virsh命令的内部实现流程,三个virsh命令的前半段代码路径是类似的。
capabilities
- virsh capabilities命令用于显示主机的能力,其中一项显示主机cpu支持的feature集合。这个feature以model加feature的形式显示出来,以下面的一个命令输出为例,Model为SandyBridge的cpu本身包含了一个feature集合,这个集合可以隐式地通过/usr/share/libvirt/cpu_map/x86_SandyBridge.xml查询得到,同时通过显示地也指定了一个feature集合。两个集合取并集,就是整个cpu包含的feature集合。
<capabilities>
<host>
<cpu>
<!-- 主机cpu匹配到的架构 -->
<arch>x86_64</arch>
<!-- 主机cpu匹配到的Model,该Model隐式地包含一系列的feature集合 -->
<model>SandyBridge</model>
<!-- 主机cpu的生产厂商 -->
<vendor>Intel</vendor>
......
<!-- 显示指定的主机包含的feature -->
<feature name='vme'/>
<feature name='ds'/>
<feature name='acpi'/>
......
</cpu>
......
</host>
......
</capabilities>
- capabilities流程首先通过cpuid命令查询主机包含的所有feature,然后遍历Libvirt在xml中预定义的所有Model,找到最匹配主机的那一个Model,最后用主机的feature集合减去Model包含的feature集合,得到需要显示指定的feature集合或者显示禁止的feature集合,将其增加到virCPUDef的features域中。所谓最匹配的model,就是签名和主机cpu相同,同时包含尽可能多的feature的那个预定义model。
1. 总体流程
cmdCapabilities
......
qemuConnectGetCapabilities
......
/* 调用x86上的driver接口搜集host相关的capabilities */
cpuDriverX86.getHost
virCPUx86GetHost
/* 首先通过嵌入式汇编执行cpuid指令
* 获取主机cpu feature,之后的所有操作
* 就是要在预定义的model之中找到最匹配
* 该feature集合的model,将其封装成virCPUDef
* 作为结果输出,所有通过cpuid查询得到的信息被存放到cpuData中 */
cpuidSet(CPUX86_BASIC, cpuData)
cpuidSet(CPUX86_EXTENDED, cpuData)
/* 解析cpuid查询得到的数据集合cpudata,找到最合适的model
* 将其封装到指向virCPUDef的cpu指针中 */
x86DecodeCPUData(cpu, cpuData, models)
x86Decode
/* 加载/usr/share/libvirt/cpu_map下的所有预定义信息 */
map = virCPUx86GetMap()
/* 根据data中的vendor信息,在map中找到对应的预定义vendor信息
* 比如主机的vendor信息是intel,那么map中找到的就是x86_vendors.xml中
* <vendor name='Intel' string='GenuineIntel'/>这一个item加载到内存中的信息 */
vendor = x86DataToVendor(&data, map)
/* 从cpudata中找到签名信息,我们知道签名信息是通过eax_in=1,ecx_in=0查询cpuid得到
* 因此x86DataToSignature的核心内容就是从data中查找匹配eax_in=1,ecx_in=0的这个item
* 返回对应的结果 */
signature = x86DataToSignature(&data)
/* 签名找到之后,解析出其中的family,model和stepping信息,因为在之后遍历预定义Model
* 的过程中,会使用Model信息和签名信息来做判断,获取最匹配的预定义Model */
virCPUx86SignatureFromCPUID(signature, &sigFamily, &sigModel, &sigStepping)
/* 遍历预定义的cpu model */
for (i = map->nmodels - 1; i >= 0; i--) {
/* 取出候选的model */
candidate = map->models[i]
/* 如果主机的vendro和预定义model的vendor不同,首先pass掉 */
if (vendor && candidate->vendor && vendor != candidate->vendor) {
VIR_DEBUG("CPU vendor %s of model %s differs from %s; ignoring",
candidate->vendor->name, candidate->name, vendor->name);
continue;
}
/* 将data中的feature集合'变成'预定义的model+feature集合的形式
* 存放在virCPUDef指针cpuCandidate中,其中virCPUDef.model存放预定义的
* model名字,virCPUDef.features存放需要显示指明的主机cpu feature集合 */
cpuCandidate = x86DataToCPU(&data, candidate, map, hvModel)
/* 判断预定义的model是否是最匹配主机cpu feature的 */
if ((rc = x86DecodeUseCandidate(model, cpuModel,
candidate, cpuCandidate,
signature, preferred,
cpu->type == VIR_CPU_TYPE_HOST))) {
virCPUDefFree(cpuModel);
cpuModel = cpuCandidate;
model = candidate;
if (rc == 2)
break;
}
}
2. 具体算法:
2.1 怎么把主机查询得到的cpu feature集合转化成预定义的model+feature集合?
x86DataToCPU(const virCPUx86Data *data,
virCPUx86ModelPtr model,
virCPUx86MapPtr map,
virDomainCapsCPUModelPtr hvModel,
virCPUType cpuType)
data:主机cpuid数据
model:预定义的model
map:libvirt预定义的所有信息
/* 首先拷贝一份cpuid查询得到的数据到copy中 */
x86DataCopy(©, data)
/* 再拷贝一份预定义的model包含的cpuid数据到modelData中 */
x86DataCopy(&modelData, &model->data)
/* 核心步骤:
* 让主机包含的feature集合减去预定义model包含的feature集合
* copy是主机feature集合,modelData是预定义model包含的feature集合
* 操作完成后,copy中剩下的就是不包含model的feature集合
* 这些集合会被添加到virCPUDef.features中,这些feature都是主机
* 拥有的,因此虚机vcpu可以使用,policy被设置为VIR_CPU_FEATURE_REQUIRE */
x86DataSubtract(©, &modelData)
/* 同上,将model包含的feature集合减去data包含的feature集合
* 操作完成后,model中剩下的集合就是data中不包含的,这些feature是主机
* 不具备的feature,因此如果使用预定义的model,需要显示的禁止掉这些feature
* policy需要被设置为VIR_CPU_FEATURE_DISABLE */
x86DataSubtract(&modelData, data)
/* 将主机包含的、但预定义model中不包含的feature集合显示地添加到virCPUDef.features中
* 策略是vcpu可以使用:VIR_CPU_FEATURE_REQUIRE */
x86DataToCPUFeatures(cpu, VIR_CPU_FEATURE_REQUIRE, ©, map)
/* 将预定义model包含的、但主机cpu不包含的feature集合显示地添加到virCPUDef.features中
* 显示地禁止:VIR_CPU_FEATURE_DISABLE */
x86DataToCPUFeatures(cpu, VIR_CPU_FEATURE_DISABLE, &modelData, map)
2.2 怎么选择最优的预定义model
x86DecodeUseCandidate
/* 首先判断签名:首先要找到和主机cpu相同签名的预定义model */
/* Ideally we want to select a model with family/model equal to
* family/model of the real CPU. Once we found such model, we only
* consider candidates with matching family/model.
*/
if (signature &&
virCPUx86SignaturesMatch(current->signatures, signature) &&
!virCPUx86SignaturesMatch(candidate->signatures, signature)) {
VIR_DEBUG("%s differs in signature from matching %s",
cpuCandidate->model, cpuCurrent->model);
return 0;
}
/* 如果存在签名相同的多个model情况:选取包含feature集合多的那个预定义model */
if (cpuCurrent->nfeatures > cpuCandidate->nfeatures) {
VIR_DEBUG("%s results in shorter feature list than %s",
cpuCandidate->model, cpuCurrent->model);
return 1;
}
cpu-baseline
- cpu-baseline命令接受多个feature集合作为输入(以model+显式feature集的形式给出),它从这些feature集合中,在预定义的model中找到最合适的哪一个model,将其作为基线返回,如果多个feature集合没有任何交集,那么返回不兼容的cpu model。
1. 总体流程
cmdCPUBaseline
......
qemuConnectBaselineCPU
......
cpuDriverX86.baseline
virCPUx86Baseline
/* 首先取第一个feature集作为输入,将其'变成'Libvirt预定义的model,
* 将此model作为基线model */
base_model = x86ModelFromCPU(cpus[0], map, -1)
modelName = cpus[0]->model
/* 处理所有输入cpu model隐式包含的feature集合 */
for (i = 1; i < ncpus; i++) {
/* 依次取出所有的输入feature集合,将其'变成'预定义的model形式 */
model = x86ModelFromCPU(cpus[i], map, -1)
/* 对所有这些feature集合进行取交集操作,base_model中最后剩下的就是所有feature集合的交集 */
x86DataIntersect(&base_model->data, &model->data)
}
/* 处理所有输入cpu 显示指定的feature集合 */
if (features) {
for (i = 0; features[i]; i++) {
if ((feat = x86FeatureFind(map, features[i])) &&
x86DataAdd(&featData->data.x86, &feat->data) < 0)
goto cleanup;
}
x86DataIntersect(&base_model->data, &featData->data.x86)
}
/* 最终,将所有的这些输入feature集合取交集后,base model没有数据了
* 说明这些feature集合之间没有任何交集,返回不兼容的cpu */
if (x86DataIsEmpty(&base_model->data)) {
virReportError(VIR_ERR_OPERATION_FAILED,
"%s", _("CPUs are incompatible"));
goto error;
}
/* 如果输入feature集合有交集,将这些数据转化成virCPUDef作为输出 */
x86Decode(cpu, &base_model->data, models, modelName, migratable)
2. 具体算法
怎么将输入的feature集合转化成预定义的cpu model?
x86ModelFromCPU
/* 首先处理model中隐式包含的feature集合
* 取出要比较的cpu的model,该model隐式指明了feature的集合
* 在预定义的model中查找是否有相同的model,没有则报错 */
if (cpu->model &&
(policy == VIR_CPU_FEATURE_REQUIRE || policy == -1)) {
if (!(model = x86ModelFind(map, cpu->model))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown CPU model %s"), cpu->model);
return NULL;
}
/* 拷贝匹配到的预定义model的cpuid数据 */
model = x86ModelCopy(model);
/* 处理输入中显式指定的feature */
for (i = 0; i < cpu->nfeatures; i++) {
/* 如果输入的feature没有指定使用策略,默认使用require策略 */
if (cpu->features[i].policy == -1)
fpol = VIR_CPU_FEATURE_REQUIRE;
/* 否则使用指定的策略 */
else
fpol = cpu->features[i].policy;
/* 首先在预定义的feature中检查,是否有输入的feature,如果没有报错 */
if (!(feature = x86FeatureFind(map, cpu->features[i].name))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown CPU feature %s"), cpu->features[i].name);
return NULL;
}
/* 根据feature的策略,将输入的feature添加到model中,或者从model删除 */
switch (fpol) {
case VIR_CPU_FEATURE_FORCE:
case VIR_CPU_FEATURE_REQUIRE:
/* 如果策略是require,添加到model包含的feature集合中 */
if (x86DataAdd(&model->data, &feature->data) < 0)
return NULL;
break;
case VIR_CPU_FEATURE_DISABLE:
case VIR_CPU_FEATURE_FORBID:
/* 将feature从model包含的feature集合中删除 */
x86DataSubtract(&model->data, &feature->data);
break;
/* coverity[dead_error_condition] */
case VIR_CPU_FEATURE_OPTIONAL:
case VIR_CPU_FEATURE_LAST:
break;
}
cpu-compare
- virsh cpu-compare工具用于将输入的feature集合与主机cpu的feature集合比较,输出的结果有4个:
typedef enum {
VIR_CPU_COMPARE_ERROR = -1, /* 比较出错 */
/* 与主机不兼容,这里包含两种集合关系:
* 一是两者不相关
* 二是主机cpu feature是输入cpu feature的子集
* 子集说明输入cpu feature集合中包含了主机无法提供的feature
* 因此无法迁移,返回不兼容 */
VIR_CPU_COMPARE_INCOMPATIBLE = 0,
VIR_CPU_COMPARE_IDENTICAL = 1, /* 与主机相同 */
/* 主机的cpu feature是输入cpu feature的超集,说明可以迁移*/
VIR_CPU_COMPARE_SUPERSET = 2,
} virCPUCompareResult;
1. 总体流程
cmdCPUCompare
......
qemuConnectCompareCPU
/* 获取主机的cpu feature集合 */
cpu = virQEMUDriverGetHostCPU(driver)
/* 将主机的cpu feature集合与xmlDesc中描述的cpu feature集合比较 */
virCPUCompareXML(driver->hostarch, cpu, xmlDesc, failIncompatible, validateXML)
......
cpuDriverX86pare
virCPUx86Compare
x86Compute(host, cpu, NULL, &message)
/* 如果做比较的cpu与主机的cpu厂商不同,报错不兼容 */
if (cpu->vendor &&
(!host->vendor || STRNEQ(cpu->vendor, host->vendor))) {
VIR_DEBUG("host CPU vendor does not match required CPU vendor %s",
cpu->vendor);
return VIR_CPU_COMPARE_INCOMPATIBLE;
}
/* 加载Libvirt预定义的信息,并且根据要比较的cpu feature集合
* 在预定义的model中组装各个属性的feature集合
* 将要比较的要求提供的cpu feature集合存放在cpu_require中 */
if (!(map = virCPUx86GetMap()) ||
!(host_model = x86ModelFromCPU(host, map, -1)) ||
!(cpu_force = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_FORCE)) ||
!(cpu_require = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_REQUIRE)) ||
!(cpu_optional = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_OPTIONAL)) ||
!(cpu_disable = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_DISABLE)) ||
!(cpu_forbid = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_FORBID)))
return VIR_CPU_COMPARE_ERROR;
/* 如果主机的cpu feature集合中包含了要比较的cpu feature中禁止提供的feature
* 返回不兼容 */
x86DataIntersect(&cpu_forbid->data, &host_model->data)
if (!x86DataIsEmpty(&cpu_forbid->data)) {
virX86CpuIncompatible(N_("Host CPU provides forbidden features"),
&cpu_forbid->data);
return VIR_CPU_COMPARE_INCOMPATIBLE;
}
/* 首先获取比较cpu feature集合中实际需要提供的feature集合 */
x86DataSubtract(&cpu_require->data, &cpu_force->data);
x86DataSubtract(&cpu_require->data, &cpu_optional->data);
x86DataSubtract(&cpu_require->data, &cpu_disable->data);
/* 比较host_model与cpu_require包含的集合关系 */
result = x86ModelCompare(host_model, cpu_require);
/* 如果host_model是cpu_require的子集,返回不兼容
* 说明要比较的cpu feature集合中包含了主机中无法提供的feature */
if (result == SUBSET || result == UNRELATED) {
x86DataSubtract(&cpu_require->data, &host_model->data);
virX86CpuIncompatible(N_("Host CPU does not provide required "
"features"),
&cpu_require->data);
return VIR_CPU_COMPARE_INCOMPATIBLE;
}
/* 至此,host与输入cpu的集合关系只有两种:
* 一是相同、二是host是cpu的超集
* 下面的步骤就是判断应该属于那种情况 */
/* 首先取出host的所有feature集合 */
diff = x86ModelCopy(host_model);
/* 减去输入cpu中涉及到的所有feature集合 */
x86DataSubtract(&diff->data, &cpu_optional->data);
x86DataSubtract(&diff->data, &cpu_require->data);
x86DataSubtract(&diff->data, &cpu_disable->data);
x86DataSubtract(&diff->data, &cpu_force->data);
/* 减去之后,如果还存在一些feature
* 说明这些feature与输入的cpu feature集合无关的
* host就是输入cpu feature的超集
* 如果不存在feature了,说明两者一样 */
if (x86DataIsEmpty(&diff->data))
ret = VIR_CPU_COMPARE_IDENTICAL;
else
ret = VIR_CPU_COMPARE_SUPERSET;
2. 具体算法
怎么比较两个集合model1、model2的关系
x86ModelCompare(virCPUx86ModelPtr model1,
virCPUx86ModelPtr model2)
virCPUx86CompareResult result = EQUAL;
/* 首先遍历model1中的feature,将其与model2集合比较
* 如果它不存在于model2中,那model1肯定不是model2的子集了
* 如果是的话,model1的任何元素都应该存在于model2中 */
while ((item1 = virCPUx86DataNext(&iter1))) {
virCPUx86CompareResult match = SUPERSET;
if ((item2 = virCPUx86DataGet(&model2->data, item1))) {
if (virCPUx86DataItemMatch(item1, item2))
continue;
else if (!virCPUx86DataItemMatchMasked(item1, item2))
match = SUBSET;
}
/* 如果result为初始值,将其赋值为SUPERSET */
if (result == EQUAL)
result = match;
/* 如果model1中存在model2中没有的元素
* match肯定被赋值为SUBSET,两者不相同返回不相关 */
else if (result != match)
return UNRELATED;
}
/* 如果程序走到这里,model2集合必然大于等于model1
* 判断具体的两种情况:等于或者大于 */
while ((item2 = virCPUx86DataNext(&iter2))) {
virCPUx86CompareResult match = SUBSET;
if ((item1 = virCPUx86DataGet(&model1->data, item2))) {
if (virCPUx86DataItemMatch(item2, item1))
continue;
else if (!virCPUx86DataItemMatchMasked(item2, item1))
match = SUPERSET;
}
if (result == EQUAL)
result = match;
else if (result != match)
return UNRELATED;
}
版权声明:本文标题:Libvirt CPU Feature 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1729159004a1188195.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论