Libvirt CPU Feature
文章目录
- 硬件基础
- 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信息。
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.getHostvirCPUx86GetHost/* 首先通过嵌入式汇编执行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.baselinevirCPUx86Baseline/* 首先取第一个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;/* 否则使用指定的策略 */elsefpol = 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) ......cpuDriverX86.comparevirCPUx86Comparex86Compute(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;elseret = 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相关推荐
- 排查cpu feature 缺少x2apic原因
服务器cpu feature 缺少x2apic,经排查bios 未开x2apic mode配置,排查方法如下: 1.缺少x2apic feature # cat /proc/cpuinfo grep ...
- KVM 介绍(7):使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 (Nova Instances Snapshot Libvirt)...
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- 【KVM系列07】使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照
第七章 使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 1. QEMU/KVM 快照 1.1 概念 1.2 使用 virsh 实验 1.3 外部快照的删除 2. OpenSt ...
- 检测cpu是否支持VT
检测cpu是否支持VT 最近研究云,一个前提基本就是是否支持VT,以前我以为外面的cpu,基本都支持,不过发现不是.只是我的笔记本的cpu,不支持,去年才买的. 检测这个,通过cpu-z好像是不行,需 ...
- 计算机cpu散热方式,调整电脑CPU散热风扇转速的简单方法【图文】
电脑风扇对于电脑的使用和寿命有着密不可分的关系,怎么调节电脑的风扇的转速来控制电脑CPU的散热?首先电脑风扇转速的控制,通常有两种方法可以进行调节,第一种就是一刀切式的调整,直接到BIOS里面手动将风 ...
- 华硕计算机cpu怎么超频怎么设置,华硕主板超频设置图解
华硕主板超频设置图解 电脑的超频就是通过计算机操作者的'超频方式将CPU.显卡.内存等硬件的工作频率提高,下面是小编为大家带来的华硕主板超频设置图解,希望对大家有所帮助. 华硕主板超频设置图解 首先, ...
- 硬件基础知识----(20)KVM 深入理解
KVM 介绍(1):简介及安装 http://www.cnblogs.com/sammyliu/p/4543110.html 学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 ...
- 虚拟化KVM技术详解
KVM 介绍(1):简介及安装 http://www.cnblogs.com/sammyliu/p/4543110.html 学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 ...
- KVM详解,学习kvm系列文章
目录 (1):简介及安装 1. KVM 介绍 1.0 虚拟化简史 1.1 KVM 架构 2. KVM 的功能列表 3. KVM 工具集合 4. RedHat Linux KVM 安装 4.1 在安装 ...
最新文章
- 在敏捷研发过程中,Scrum Master角色的哪些事
- 判断是否是ie浏览器 前端js_JS判断是否是IE浏览器
- MySQL 中 AUTO_INCREMENT 的“坑”--id不连续
- win2008修改远程端口
- c++画多边形_如何画出超漂亮的极光绘画教程
- 2011年华科计算机考研复试笔试算法、数据库(回忆版)
- python画椭圆形_手残党福音:用Python画出机器人Dev
- sam音高修正_Melodyneplugin音高修正插件使用入门(精)
- 群晖 kodi mysql,用群晖为 Kodi 注入多设备同步能力
- 【资讯】创业加速器Satoshi Block Dojo——为BSV前沿初创企业的发展提速
- 没有互联网,如何本地获取到LoRaWAN的终端数据?
- UNITY自动化测试简单方法
- 精品Linux应用分享推荐
- 用ESP32与Python实现物联网(IoT)火焰检测报警系统
- 【2020年高被引学者】 陶大程 悉尼大学
- php语言缺点,php语言优缺点分析
- 沭阳学爬虫01HTTP基本原理
- 信息化项目网络安全方案编制
- ACP敏捷项目管理认证考试科普
- 其实你一直在成功的旁边
热门文章
- ubuntu16.04下编译ORB_SLAM3时出现的问题及解决方法
- 【吃瓜教程】《机器学习公式详解》西瓜书与南瓜书公式推导
- 笔记本的外壳用的是复合材料,复合材料是ABS工程塑料吗?如果不是那又是什么材料...
- Nginx总结(反向代理、负载均衡、动静分离)篇
- 4.Volum(存储技术)
- 【屌丝程序的口才逆袭演讲稿50篇】第六篇:两个年轻人挣钱的故事 【张振华.Jack】
- 07 Android 植物人大战僵尸-修复放置卡片重叠Bug
- Ubuntu16.04安装软件提示E: Encountered a section with no Package: header错误
- 什么是焊锡机器人?如何使用?
- unity3D游戏-打飞碟游戏改进版