Libvirt CPU Feature

硬件基础

  • 虚拟机的迁移需要保证两端的主机能够暴露给虚机的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相关的接口,分别是:
  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.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(&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.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)               
        ......
        cpuDriverX86.compare
        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;
    }
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

享乐主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值