文章目录

  • 硬件基础
    • 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相关的接口,分别是:
  1. virsh capabilites: 探测主机包含的cpu feature。该接口可以搜集主机支持的cpu feature集合。
  2. virsh cpu-baseline: 给出多个cpu feature的集合,计算这些cpu feature集合的基线,实际上就是取给出的所有cpu feature集合的交集。该接口可以用于计算多个主机共同的的cpu feature集合,从而得到可以保证迁移成功的虚拟机cpu feature配置。
  3. 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(&copy, 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(&copy, &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, &copy, 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相关推荐

  1. 排查cpu feature 缺少x2apic原因

    服务器cpu feature 缺少x2apic,经排查bios 未开x2apic mode配置,排查方法如下: 1.缺少x2apic feature # cat /proc/cpuinfo grep ...

  2. 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设备直接分 ...

  3. 【KVM系列07】使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照

    第七章 使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 1. QEMU/KVM 快照 1.1 概念 1.2 使用 virsh 实验 1.3 外部快照的删除 2. OpenSt ...

  4. 检测cpu是否支持VT

    检测cpu是否支持VT 最近研究云,一个前提基本就是是否支持VT,以前我以为外面的cpu,基本都支持,不过发现不是.只是我的笔记本的cpu,不支持,去年才买的. 检测这个,通过cpu-z好像是不行,需 ...

  5. 计算机cpu散热方式,调整电脑CPU散热风扇转速的简单方法【图文】

    电脑风扇对于电脑的使用和寿命有着密不可分的关系,怎么调节电脑的风扇的转速来控制电脑CPU的散热?首先电脑风扇转速的控制,通常有两种方法可以进行调节,第一种就是一刀切式的调整,直接到BIOS里面手动将风 ...

  6. 华硕计算机cpu怎么超频怎么设置,华硕主板超频设置图解

    华硕主板超频设置图解 电脑的超频就是通过计算机操作者的'超频方式将CPU.显卡.内存等硬件的工作频率提高,下面是小编为大家带来的华硕主板超频设置图解,希望对大家有所帮助. 华硕主板超频设置图解 首先, ...

  7. 硬件基础知识----(20)KVM 深入理解

    KVM 介绍(1):简介及安装 http://www.cnblogs.com/sammyliu/p/4543110.html 学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 ...

  8. 虚拟化KVM技术详解

    KVM 介绍(1):简介及安装 http://www.cnblogs.com/sammyliu/p/4543110.html 学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 ...

  9. KVM详解,学习kvm系列文章

    目录 (1):简介及安装 1. KVM 介绍 1.0 虚拟化简史 1.1 KVM 架构 2. KVM 的功能列表 3. KVM 工具集合 4. RedHat Linux KVM 安装 4.1 在安装  ...

最新文章

  1. 在敏捷研发过程中,Scrum Master角色的哪些事
  2. 判断是否是ie浏览器 前端js_JS判断是否是IE浏览器
  3. MySQL 中 AUTO_INCREMENT 的“坑”--id不连续
  4. win2008修改远程端口
  5. c++画多边形_如何画出超漂亮的极光绘画教程
  6. 2011年华科计算机考研复试笔试算法、数据库(回忆版)
  7. python画椭圆形_手残党福音:用Python画出机器人Dev
  8. sam音高修正_Melodyneplugin音高修正插件使用入门(精)
  9. 群晖 kodi mysql,用群晖为 Kodi 注入多设备同步能力
  10. 【资讯】创业加速器Satoshi Block Dojo——为BSV前沿初创企业的发展提速
  11. 没有互联网,如何本地获取到LoRaWAN的终端数据?
  12. UNITY自动化测试简单方法
  13. 精品Linux应用分享推荐
  14. 用ESP32与Python实现物联网(IoT)火焰检测报警系统
  15. 【2020年高被引学者】 陶大程 悉尼大学
  16. php语言缺点,php语言优缺点分析
  17. 沭阳学爬虫01HTTP基本原理
  18. 信息化项目网络安全方案编制
  19. ACP敏捷项目管理认证考试科普
  20. 其实你一直在成功的旁边

热门文章

  1. ubuntu16.04下编译ORB_SLAM3时出现的问题及解决方法
  2. 【吃瓜教程】《机器学习公式详解》西瓜书与南瓜书公式推导
  3. 笔记本的外壳用的是复合材料,复合材料是ABS工程塑料吗?如果不是那又是什么材料...
  4. Nginx总结(反向代理、负载均衡、动静分离)篇
  5. 4.Volum(存储技术)
  6. 【屌丝程序的口才逆袭演讲稿50篇】第六篇:两个年轻人挣钱的故事 【张振华.Jack】
  7. 07 Android 植物人大战僵尸-修复放置卡片重叠Bug
  8. Ubuntu16.04安装软件提示E: Encountered a section with no Package: header错误
  9. 什么是焊锡机器人?如何使用?
  10. unity3D游戏-打飞碟游戏改进版