文章目錄 硬件基礎(chǔ) 數(shù)據(jù)結(jié)構(gòu) 物理抽象 virCPUx86CPUID virCPUx86DataItem virCPUx86Data 配置信息 virCPUx86Vendor virCPUx86Feature virCPUx86Model virCPUx86Map 概念抽象 virCPUFeatureDef virCPUDef 工具函數(shù) 數(shù)據(jù)加載 基本操作 virCPUx86DataItemCmp virCPUx86DataNext virCPUx86DataGet virCPUx86DataItemAndBits virCPUx86DataItemClearBits 集合操作 x86DataAdd x86DataSubtract x86DataIntersect 信息提取 x86DataToCPUFeatures x86DataToVendor virsh 工具 capabilities cpu-baseline cpu-compare
硬件基礎(chǔ)
虛擬機(jī)的遷移需要保證兩端的主機(jī)能夠暴露給虛機(jī)的cpu feature相同,如果不相同無法保證遷移后虛機(jī)能夠正常運(yùn)行,因此遷移前會(huì)判斷兩端虛機(jī)特性,不同會(huì)報(bào)錯(cuò),終止遷移。為了讓資源池中新加入一臺(tái)服務(wù)器并能夠順利將虛機(jī)遷移過去,需要計(jì)算源和目的兩端服務(wù)器的cpu feature交集,然后暴露給虛機(jī),保證遷移順利,這是虛擬化組件需要解決的事情。本節(jié)主要介紹其硬件基礎(chǔ)。 Intel通過cpuid指令查詢cpu的signature和feature,signature標(biāo)識(shí)著cpu的版本信息,feature包含了cpu的支持的硬件特性,比如虛擬化相關(guān)的vmx(Virtual Machine Extensions)特性,內(nèi)存管理相關(guān)的pae(Physical Address Extension)特性,或者M(jìn)SR(Model Specific Registers)等。通過cpuid指令可以查到兩類信息,一類是cpu基本(Basic)信息,一類是cpu擴(kuò)展(Extended)信息。 cpuid指令雖然沒有操作數(shù),但它的輸出較其它有操作數(shù)的指令更為復(fù)雜,它將寄存器(EAX,ECX)作為輸入,將寄存器(EAX,EBX,ECX,EDX)作為輸出。輸入的不同,執(zhí)行cpuid指令得到的輸出信息不同。比如,在EAX=0,ECX=0的情況下,如果執(zhí)行cpuid指令,得到的是cpu的vendor信息和輸入EAX的最大值;在EAX=1,ECX=0的情況下,如果執(zhí)行cpuid指令,得到的是cpu的signature和feature。CPU feature相關(guān)的cpuid指令就是上面這兩個(gè),下面具體介紹它們的輸出格式,以及如何獲取vendor、signature和feature信息。
vendor
當(dāng)EAX被設(shè)置成0,作為輸入時(shí),執(zhí)行cpuid指令,它的輸出分別是EAX,保存EAX作為輸入的最大值和vendor信息。Intel手冊(cè)中關(guān)于vendor信息的獲取說明如下: 假設(shè)最大輸入值為Maximum,那么EAX作為輸入的取值范圍就是[0, Maximum]。當(dāng)EAX在這區(qū)間范圍內(nèi)作為輸入時(shí),執(zhí)行cpuid指令輸出的信息為Basic類信息。vendor為廠商信息,由EBX/ECX/ECX三個(gè)寄存器共同提供。對(duì)于x86架構(gòu)有三個(gè)廠商信息,分別是:GenuineIntel(Intel)、AuthenticAMD(AMD)和HygonGenuine(Hygon)
signature
當(dāng)EAX被設(shè)置成1,作為輸入時(shí),執(zhí)行cpuid指令,它的輸出寄存器中,EAX保存cpu的signature的信息。ECX和EDX報(bào)錯(cuò)cpu的feature信息,Intel手冊(cè)說明如下: EAX的signature信息格式如下:
feature
feature信息和signature一樣,通過設(shè)置EAX為1執(zhí)行cpuid指令得到。它的信息保存在兩個(gè)寄存器ECX、EDX中,寄存器的每個(gè)bit標(biāo)識(shí)著一個(gè)cpu的特性,如果該bit被置位,表示cpu支持該特性,反之,如果該bit被清零,標(biāo)志cpu不支持該特性。 ECX和ECX表示的feature格式如下:
數(shù)據(jù)結(jié)構(gòu)
Libvirt提供了探測(cè)主機(jī)cpu feature的工具、計(jì)算指定cpu feature集合與主機(jī)cpu feature集合關(guān)系的工具,以及計(jì)算不同cpu feature交集的工具。通過這些工具,上層應(yīng)用可以計(jì)算出兩個(gè)cpu之間feature的交集,從而決定如何暴露給虛擬機(jī),順利實(shí)現(xiàn)虛機(jī)的遷移。本節(jié)主要介紹Libvirt中與cpu feature相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
物理抽象
virCPUx86CPUID
virCPUx86CPUID用于描述執(zhí)行一條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中,使用嵌入式匯編調(diào)用cpuid指令,在執(zhí)行過程中用到了virCPUx86CPUID結(jié)構(gòu),如下:
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時(shí),保存的是一條cpuid指令輸入輸出。
typedef struct _virCPUx86DataItem virCPUx86DataItem;
typedef virCPUx86DataItem *virCPUx86DataItemPtr;
struct _virCPUx86DataItem {virCPUx86DataType type; /* 當(dāng)type=VIR_CPU_X86_DATA_CPUID時(shí),data的cpuid值有效 */union {virCPUx86CPUID cpuid; /* 保存一條cpuid的輸入輸出*/virCPUx86MSR msr;} data;
};
virCPUx86Data
對(duì)于一個(gè)cpu來說,當(dāng)使用cpuid指令查詢它的相關(guān)信息時(shí),它會(huì)有很多的輸出,每改變一次輸入的值,執(zhí)行cpuid查到的輸出值意義就不一樣,因此cpuid的輸入輸出組成的條目非常多,Libvirt通過virCPUx86Data來描述執(zhí)行cpuid的所有輸入輸出組成的數(shù)組。virCPUx86Data可以認(rèn)為保存的是cpuid指令查詢后,得到的原始數(shù)據(jù)。
typedef struct _virCPUx86Data virCPUx86Data;
struct _virCPUx86Data {size_t len; /* 數(shù)組的大小,數(shù)組的每個(gè)元素表示cpuid指令的一個(gè)輸入輸出 */virCPUx86DataItem *items; /* 數(shù)組的基地址 */
};
配置信息
Libvirt為了管理cpu feature,將支持的所有架構(gòu)的所有feature組織成xml文件,持久化到磁盤上,同時(shí)將支持的所有model和vendor也組織成xml文件,持久化到磁盤上。在libvirt獲取host capabilities或者計(jì)算feature交集時(shí),會(huì)首先將支持的所有feature,vendor,model都加載到內(nèi)存中,用于feature集合的計(jì)算。xml所在目錄為/usr/share/libvirt/cpu_map/,下面主要介紹這些xml格式在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)。
virCPUx86Vendor
vendor信息可以通過cpuid指令查詢得到,virCPUx86Vendor結(jié)構(gòu)用于存放查詢vendor的cpuid指令的輸入輸出,組成的一個(gè)item。
typedef struct _virCPUx86Vendor virCPUx86Vendor;
typedef virCPUx86Vendor *virCPUx86VendorPtr;
struct _virCPUx86Vendor {char *name; /* 廠商名稱,對(duì)于x86架構(gòu),可能的名稱就是Intel、AMD和Hygon */virCPUx86DataItem data; /* cpuid查詢得到的條目,它的輸出EBX、ECX和EDX就是廠商名稱的accii碼值 */
};
Libvirt保存了支持的vendor信息到xml中,當(dāng)virsh工具需要處理vendor相關(guān)信息時(shí),Libvirt從xml中讀取支持的vendor信息,加載到內(nèi)存,對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)就是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域存放的數(shù)組通常只有一個(gè)元素,代表一個(gè)bit對(duì)應(yīng)的feature。
typedef struct _virCPUx86Feature virCPUx86Feature;
typedef virCPUx86Feature *virCPUx86FeaturePtr;
struct _virCPUx86Feature {/* feature名 */char *name; /* feature對(duì)應(yīng)的寄存器值,對(duì)應(yīng)寄存器中的一個(gè)bit* 這里看上去data包含的是一個(gè)item數(shù)組* 但實(shí)際上通常情況下只有一個(gè)元素 */virCPUx86Data data; /* 如果該feature不影響遷移(可遷移)* 設(shè)置為true,反之,設(shè)置為false */bool migratable;
};
libvirt將所有feature以一定格式組織起來,存放到/usr/share/libvirt/cpu_map/x86_features.xml中,這個(gè)xml描述了x86架構(gòu)下所有廠商的feature屬性,包括該通過什么輸入得到,輸出的feature值對(duì)應(yīng)寄存器的哪一位;該feature是否可以遷移等。每當(dāng)Libvirt需要計(jì)算feature時(shí),將這些feature加載到內(nèi)存,進(jìn)行操作。xml中一個(gè)典型的feature描述如下:
<feature name='vmx'> /* feature名字: vmx*/<cpuid eax_in='0x01' ecx='0x00000020'/> /* 獲取feature時(shí)輸入EAX的值為0x01,該feature對(duì)應(yīng)輸出ECX的第5bit */</feature>
virCPUx86Model
cpu的model信息通過cpuid指令查到,得到的原始數(shù)據(jù)被保存到一個(gè)virCPUx86Data數(shù)據(jù)結(jié)構(gòu)中。同時(shí),virCPUx86Model結(jié)構(gòu)中還將原數(shù)據(jù)解析出來,分別存放到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被保存到兩個(gè)寄存器中,寄存器的每個(gè)bit代表一個(gè)feature,Libvirt對(duì)應(yīng)地將每個(gè)feature抽象成一個(gè)virCPUFeatureDef數(shù)據(jù)結(jié)構(gòu)。結(jié)構(gòu)中的policy描述了vcpu采用該feature的策略。
typedef enum {VIR_CPU_FEATURE_FORCE, /* vcpu需要強(qiáng)行應(yīng)用該feature,無論主機(jī)是否支持此feature */VIR_CPU_FEATURE_REQUIRE, /* vcpu請(qǐng)求使用該feature,如果主機(jī)不支持或者h(yuǎn)ypervisor無法模擬,虛機(jī)在start的時(shí)候會(huì)報(bào)錯(cuò) */VIR_CPU_FEATURE_OPTIONAL, /* vcpu只有在探測(cè)到主機(jī)支持該feature時(shí),才使用該feature,因此這種策略不會(huì)虛機(jī)啟動(dòng)不會(huì)報(bào)錯(cuò) */VIR_CPU_FEATURE_DISABLE, /* vcpu被主機(jī)禁止使用該feature */VIR_CPU_FEATURE_FORBID, /* 禁止主機(jī)支持該feature。如果主機(jī)支持,虛機(jī)啟動(dòng)時(shí)報(bào)錯(cuò)*/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信息展示給用戶,通常包含架構(gòu)、廠商、Model、特性等等,Libvirt將這些cpu相關(guān)的信息包含在virCPUDef結(jié)構(gòu)中。
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集合 */......
};
工具函數(shù)
數(shù)據(jù)加載
virCPUx86LoadMap
該函數(shù)主要負(fù)責(zé)將/usr/share/libvirt/cpu_map目錄下的所有信息加載到內(nèi)存中,最終保存在靜態(tài)全局變量cpuMap中,virCPUx86LoadMap函數(shù)只在Libvirtd啟動(dòng)時(shí)執(zhí)行一次。一旦cpuMap被加載到內(nèi)存中,后續(xù)的所有關(guān)于cpu feature集合的計(jì)算都使用這個(gè)結(jié)構(gòu)
基本操作
virCPUx86DataItemCmp
int virCPUx86DataItemCmp(const virCPUx86DataItem *item1, const virCPUx86DataItem *item2)
比較item1和item2是否相同,比較的標(biāo)準(zhǔn)是判斷查詢cpuid的輸入寄存器eax_in和ecx_in的值,如果值相等表示相同,反之則不同。
virCPUx86DataNext
virCPUx86DataItemPtr virCPUx86DataNext(virCPUx86DataIteratorPtr iterator)
根據(jù)迭代器中的位置pos,取出其指向的非零item。
virCPUx86DataGet
virCPUx86DataItemPtr virCPUx86DataGet(const virCPUx86Data *data, const virCPUx86DataItem *item)
根據(jù)item中的eax_in和ecx_in,查找data集合中對(duì)應(yīng)的item,取出data集合中對(duì)應(yīng)的item。
virCPUx86DataItemAndBits
void virCPUx86DataItemAndBits(virCPUx86DataItemPtr item, const virCPUx86DataItem *mask)
將item中對(duì)應(yīng)的eax,ebx,ecx,edx中與mask中eac,ebx,ecx,edx進(jìn)行位與操作
virCPUx86DataItemClearBits
void virCPUx86DataItemClearBits(virCPUx86DataItemPtr item, const virCPUx86DataItem *mask)
根據(jù)mask提供的掩碼位,將item中對(duì)應(yīng)的eax,ebx,ecx,edx中對(duì)應(yīng)的位請(qǐng)零
集合操作
x86DataAdd
int x86DataAdd(virCPUx86Data *data1, const virCPUx86Data *data2)
函數(shù)將data2集合中的所有非空的item添加到data1集合中,所謂空item,item中的eax,ebx,ecx,edx都為0。添加有兩種情況,如果data1中已經(jīng)存在對(duì)應(yīng)的item,將data2中的item與data1中對(duì)應(yīng)的item取并集操作,否則直接將data2中的item內(nèi)容拷貝到data1集合中。該函數(shù)提供了feature集合的加操作。
static int
x86DataAdd(virCPUx86Data *data1,const virCPUx86Data *data2)
{virCPUx86DataIterator iter;virCPUx86DataItemPtr item;/* 初始化遍歷data2集合的迭代器* 為遍歷data2做準(zhǔn)備 */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。該函數(shù)提供了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中的對(duì)應(yīng)item清零 */item2 = virCPUx86DataGet(data2, item1);virCPUx86DataItemClearBits(item1, item2);}
}
x86DataIntersect
void x86DataIntersect(virCPUx86Data *data1, const virCPUx86Data *data2)
該函數(shù)將data1集合中的item依次與data2集合中的item比較,如果data1集合中的item在data2集合中沒有,則將data1中的Item刪除掉,如果有,則將data2中的對(duì)應(yīng)item也取出來,兩個(gè)item取交集,結(jié)果存放在data1中。該函數(shù)實(shí)現(xiàn)了取集合交集的操作。
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進(jìn)行位與操作 */virCPUx86DataItemAndBits(item1, item2);else/* 如果data2中沒有對(duì)應(yīng)item,將data1中的item清零 */virCPUx86DataItemClearBits(item1, item1);}
}
信息提取
x86DataToCPUFeatures
int x86DataToCPUFeatures(virCPUDefPtr cpu, int policy, virCPUx86Data *data, virCPUx86MapPtr map)
將data集合中包含的所有屬于map的feature添加到cpu的features域中。遍歷map中包含的所有feature,檢查它的每一個(gè)item是否屬于data集合的子集,如果是,將data中對(duì)應(yīng)的item減去,同時(shí)將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);/* 同時(shí)將feature添加到cpu->features中 */if (virCPUDefAddFeature(cpu, feature->name, policy) < 0)return -1;}}return 0;
}
x86DataToVendor
返回map中與data集合中匹配的第一個(gè)vendor。從data集合取出vendor,與map中包含的所有vendor比較,如果有相同的item,首先清零data中對(duì)應(yīng)的item,同時(shí)返回map中指向的vendor。注意,在map匹配到對(duì)應(yīng)vendor后,同時(shí)會(huì)刪除data中對(duì)應(yīng)的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對(duì)應(yīng)的item(輸入值相同) */if ((item = virCPUx86DataGet(data, &vendor->data)) &&/* 同時(shí)取出的item與vendor中對(duì)應(yīng)item的輸出值也相同 */virCPUx86DataItemMatchMasked(item, &vendor->data)) {/* 將data中包含的vendor對(duì)應(yīng)的item清零 */virCPUx86DataItemClearBits(item, &vendor->data);/* 返回map中包含的vendor */return vendor;}}return NULL;
}
virsh 工具
Libvirt提供了三個(gè)與cpu feature相關(guān)的接口,分別是:
virsh capabilites: 探測(cè)主機(jī)包含的cpu feature。該接口可以搜集主機(jī)支持的cpu feature集合。 virsh cpu-baseline: 給出多個(gè)cpu feature的集合,計(jì)算這些cpu feature集合的基線,實(shí)際上就是取給出的所有cpu feature集合的交集。該接口可以用于計(jì)算多個(gè)主機(jī)共同的的cpu feature集合,從而得到可以保證遷移成功的虛擬機(jī)cpu feature配置。 virsh cpu-compare: 給出一個(gè)cpu feature的集合,將它與主機(jī)的cpu feature作集合的比較,得到兩個(gè)集合的關(guān)系(超集、子集、相等或者非包含關(guān)系)。該接口可以用于判斷遠(yuǎn)端的一個(gè)host上的虛機(jī)是否可以遷移到本地。
下面分別介紹三個(gè)virsh命令的內(nèi)部實(shí)現(xiàn)流程,三個(gè)virsh命令的前半段代碼路徑是類似的。
capabilities
virsh capabilities命令用于顯示主機(jī)的能力,其中一項(xiàng)顯示主機(jī)cpu支持的feature集合。這個(gè)feature以model加feature的形式顯示出來,以下面的一個(gè)命令輸出為例,Model為SandyBridge的cpu本身包含了一個(gè)feature集合,這個(gè)集合可以隱式地通過/usr/share/libvirt/cpu_map/x86_SandyBridge.xml查詢得到,同時(shí)通過顯示地也指定了一個(gè)feature集合。兩個(gè)集合取并集,就是整個(gè)cpu包含的feature集合。
<capabilities><host><cpu><!-- 主機(jī)cpu匹配到的架構(gòu) --><arch>x86_64</arch><!-- 主機(jī)cpu匹配到的Model,該Model隱式地包含一系列的feature集合 --><model>SandyBridge</model><!-- 主機(jī)cpu的生產(chǎn)廠商 --><vendor>Intel</vendor>......<!-- 顯示指定的主機(jī)包含的feature --><feature name='vme'/><feature name='ds'/><feature name='acpi'/>......</cpu>......</host>......
</capabilities>
capabilities流程首先通過cpuid命令查詢主機(jī)包含的所有feature,然后遍歷Libvirt在xml中預(yù)定義的所有Model,找到最匹配主機(jī)的那一個(gè)Model,最后用主機(jī)的feature集合減去Model包含的feature集合,得到需要顯示指定的feature集合或者顯示禁止的feature集合,將其增加到virCPUDef的features域中。所謂最匹配的model,就是簽名和主機(jī)cpu相同,同時(shí)包含盡可能多的feature的那個(gè)預(yù)定義model。
1. 總體流程
cmdCapabilities......qemuConnectGetCapabilities....../* 調(diào)用x86上的driver接口搜集host相關(guān)的capabilities */cpuDriverX86.getHostvirCPUx86GetHost/* 首先通過嵌入式匯編執(zhí)行cpuid指令* 獲取主機(jī)cpu feature,之后的所有操作* 就是要在預(yù)定義的model之中找到最匹配* 該feature集合的model,將其封裝成virCPUDef* 作為結(jié)果輸出,所有通過cpuid查詢得到的信息被存放到cpuData中 */cpuidSet(CPUX86_BASIC, cpuData)cpuidSet(CPUX86_EXTENDED, cpuData)/* 解析cpuid查詢得到的數(shù)據(jù)集合cpudata,找到最合適的model* 將其封裝到指向virCPUDef的cpu指針中 */x86DecodeCPUData(cpu, cpuData, models)x86Decode/* 加載/usr/share/libvirt/cpu_map下的所有預(yù)定義信息 */map = virCPUx86GetMap()/* 根據(jù)data中的vendor信息,在map中找到對(duì)應(yīng)的預(yù)定義vendor信息* 比如主機(jī)的vendor信息是intel,那么map中找到的就是x86_vendors.xml中* <vendor name='Intel' string='GenuineIntel'/>這一個(gè)item加載到內(nèi)存中的信息 */vendor = x86DataToVendor(&data, map)/* 從cpudata中找到簽名信息,我們知道簽名信息是通過eax_in=1,ecx_in=0查詢cpuid得到* 因此x86DataToSignature的核心內(nèi)容就是從data中查找匹配eax_in=1,ecx_in=0的這個(gè)item* 返回對(duì)應(yīng)的結(jié)果 */signature = x86DataToSignature(&data)/* 簽名找到之后,解析出其中的family,model和stepping信息,因?yàn)樵谥蟊闅v預(yù)定義Model* 的過程中,會(huì)使用Model信息和簽名信息來做判斷,獲取最匹配的預(yù)定義Model */virCPUx86SignatureFromCPUID(signature, &sigFamily, &sigModel, &sigStepping)/* 遍歷預(yù)定義的cpu model */for (i = map->nmodels - 1; i >= 0; i--) {/* 取出候選的model */candidate = map->models[i]/* 如果主機(jī)的vendro和預(yù)定義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集合'變成'預(yù)定義的model+feature集合的形式* 存放在virCPUDef指針cpuCandidate中,其中virCPUDef.model存放預(yù)定義的* model名字,virCPUDef.features存放需要顯示指明的主機(jī)cpu feature集合 */cpuCandidate = x86DataToCPU(&data, candidate, map, hvModel)/* 判斷預(yù)定義的model是否是最匹配主機(jī)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 怎么把主機(jī)查詢得到的cpu feature集合轉(zhuǎn)化成預(yù)定義的model+feature集合?
x86DataToCPU(const virCPUx86Data *data,virCPUx86ModelPtr model,virCPUx86MapPtr map,virDomainCapsCPUModelPtr hvModel,virCPUType cpuType)
data:主機(jī)cpuid數(shù)據(jù)
model:預(yù)定義的model
map:libvirt預(yù)定義的所有信息/* 首先拷貝一份cpuid查詢得到的數(shù)據(jù)到copy中 */x86DataCopy(©, data)/* 再拷貝一份預(yù)定義的model包含的cpuid數(shù)據(jù)到modelData中 */x86DataCopy(&modelData, &model->data)/* 核心步驟:* 讓主機(jī)包含的feature集合減去預(yù)定義model包含的feature集合* copy是主機(jī)feature集合,modelData是預(yù)定義model包含的feature集合* 操作完成后,copy中剩下的就是不包含model的feature集合* 這些集合會(huì)被添加到virCPUDef.features中,這些feature都是主機(jī)* 擁有的,因此虛機(jī)vcpu可以使用,policy被設(shè)置為VIR_CPU_FEATURE_REQUIRE */x86DataSubtract(©, &modelData)/* 同上,將model包含的feature集合減去data包含的feature集合* 操作完成后,model中剩下的集合就是data中不包含的,這些feature是主機(jī)* 不具備的feature,因此如果使用預(yù)定義的model,需要顯示的禁止掉這些feature* policy需要被設(shè)置為VIR_CPU_FEATURE_DISABLE */x86DataSubtract(&modelData, data)/* 將主機(jī)包含的、但預(yù)定義model中不包含的feature集合顯示地添加到virCPUDef.features中* 策略是vcpu可以使用:VIR_CPU_FEATURE_REQUIRE */x86DataToCPUFeatures(cpu, VIR_CPU_FEATURE_REQUIRE, ©, map)/* 將預(yù)定義model包含的、但主機(jī)cpu不包含的feature集合顯示地添加到virCPUDef.features中* 顯示地禁止:VIR_CPU_FEATURE_DISABLE */x86DataToCPUFeatures(cpu, VIR_CPU_FEATURE_DISABLE, &modelData, map)
2.2 怎么選擇最優(yōu)的預(yù)定義model
x86DecodeUseCandidate/* 首先判斷簽名:首先要找到和主機(jī)cpu相同簽名的預(yù)定義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;}/* 如果存在簽名相同的多個(gè)model情況:選取包含feature集合多的那個(gè)預(yù)定義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命令接受多個(gè)feature集合作為輸入(以model+顯式feature集的形式給出),它從這些feature集合中,在預(yù)定義的model中找到最合適的哪一個(gè)model,將其作為基線返回,如果多個(gè)feature集合沒有任何交集,那么返回不兼容的cpu model。
1. 總體流程
cmdCPUBaseline......qemuConnectBaselineCPU......cpuDriverX86.baselinevirCPUx86Baseline/* 首先取第一個(gè)feature集作為輸入,將其'變成'Libvirt預(yù)定義的model,* 將此model作為基線model */base_model = x86ModelFromCPU(cpus[0], map, -1)modelName = cpus[0]->model/* 處理所有輸入cpu model隱式包含的feature集合 */for (i = 1; i < ncpus; i++) {/* 依次取出所有的輸入feature集合,將其'變成'預(yù)定義的model形式 */model = x86ModelFromCPU(cpus[i], map, -1)/* 對(duì)所有這些feature集合進(jìn)行取交集操作,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沒有數(shù)據(jù)了* 說明這些feature集合之間沒有任何交集,返回不兼容的cpu */if (x86DataIsEmpty(&base_model->data)) {virReportError(VIR_ERR_OPERATION_FAILED,"%s", _("CPUs are incompatible"));goto error;}/* 如果輸入feature集合有交集,將這些數(shù)據(jù)轉(zhuǎn)化成virCPUDef作為輸出 */x86Decode(cpu, &base_model->data, models, modelName, migratable)
2. 具體算法
怎么將輸入的feature集合轉(zhuǎn)化成預(yù)定義的cpu model?
x86ModelFromCPU/* 首先處理model中隱式包含的feature集合* 取出要比較的cpu的model,該model隱式指明了feature的集合* 在預(yù)定義的model中查找是否有相同的model,沒有則報(bào)錯(cuò) */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;}/* 拷貝匹配到的預(yù)定義model的cpuid數(shù)據(jù) */model = x86ModelCopy(model);/* 處理輸入中顯式指定的feature */for (i = 0; i < cpu->nfeatures; i++) {/* 如果輸入的feature沒有指定使用策略,默認(rèn)使用require策略 */if (cpu->features[i].policy == -1)fpol = VIR_CPU_FEATURE_REQUIRE;/* 否則使用指定的策略 */elsefpol = cpu->features[i].policy;/* 首先在預(yù)定義的feature中檢查,是否有輸入的feature,如果沒有報(bào)錯(cuò) */if (!(feature = x86FeatureFind(map, cpu->features[i].name))) {virReportError(VIR_ERR_INTERNAL_ERROR,_("Unknown CPU feature %s"), cpu->features[i].name);return NULL;}/* 根據(jù)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集合與主機(jī)cpu的feature集合比較,輸出的結(jié)果有4個(gè):
typedef enum {VIR_CPU_COMPARE_ERROR = -1, /* 比較出錯(cuò) *//* 與主機(jī)不兼容,這里包含兩種集合關(guān)系:* 一是兩者不相關(guān)* 二是主機(jī)cpu feature是輸入cpu feature的子集* 子集說明輸入cpu feature集合中包含了主機(jī)無法提供的feature* 因此無法遷移,返回不兼容 */VIR_CPU_COMPARE_INCOMPATIBLE = 0, VIR_CPU_COMPARE_IDENTICAL = 1, /* 與主機(jī)相同 *//* 主機(jī)的cpu feature是輸入cpu feature的超集,說明可以遷移*/VIR_CPU_COMPARE_SUPERSET = 2,
} virCPUCompareResult;
1. 總體流程
cmdCPUCompare......qemuConnectCompareCPU/* 獲取主機(jī)的cpu feature集合 */cpu = virQEMUDriverGetHostCPU(driver)/* 將主機(jī)的cpu feature集合與xmlDesc中描述的cpu feature集合比較 */virCPUCompareXML(driver->hostarch, cpu, xmlDesc, failIncompatible, validateXML) ......cpuDriverX86.comparevirCPUx86Comparex86Compute(host, cpu, NULL, &message)/* 如果做比較的cpu與主機(jī)的cpu廠商不同,報(bào)錯(cuò)不兼容 */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預(yù)定義的信息,并且根據(jù)要比較的cpu feature集合* 在預(yù)定義的model中組裝各個(gè)屬性的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;/* 如果主機(jī)的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集合中實(shí)際需要提供的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包含的集合關(guān)系 */result = x86ModelCompare(host_model, cpu_require);/* 如果host_model是cpu_require的子集,返回不兼容* 說明要比較的cpu feature集合中包含了主機(jī)中無法提供的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的集合關(guān)系只有兩種:* 一是相同、二是host是cpu的超集* 下面的步驟就是判斷應(yīng)該屬于那種情況 *//* 首先取出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集合無關(guān)的* host就是輸入cpu feature的超集* 如果不存在feature了,說明兩者一樣 */if (x86DataIsEmpty(&diff->data))ret = VIR_CPU_COMPARE_IDENTICAL;elseret = VIR_CPU_COMPARE_SUPERSET;
2. 具體算法
怎么比較兩個(gè)集合model1、model2的關(guān)系
x86ModelCompare(virCPUx86ModelPtr model1,virCPUx86ModelPtr model2)virCPUx86CompareResult result = EQUAL;/* 首先遍歷model1中的feature,將其與model2集合比較* 如果它不存在于model2中,那model1肯定不是model2的子集了* 如果是的話,model1的任何元素都應(yīng)該存在于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,兩者不相同返回不相關(guān) */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;}
總結(jié)
以上是生活随笔 為你收集整理的Libvirt CPU Feature 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。