文章目錄 硬件基礎 數據結構 物理抽象 virCPUx86CPUID virCPUx86DataItem virCPUx86Data 配置信息 virCPUx86Vendor virCPUx86Feature virCPUx86Model virCPUx86Map 概念抽象 virCPUFeatureDef virCPUDef 工具函數 數據加載 基本操作 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 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。