三、Example1 和VmAllocationPolicySimple源碼分析
在接着上一篇博客分析之前,我們先來了解一些可能會需要注意的地方。
MIPS是虛擬機的cpu處理速度。假設同一機器下的所有PE具有相同的MIPS評級。Pe(處理單元)表示CPU單元,即可以理解爲CPU的核數,按照每秒百萬指令(MIPS)評級定義。
mips是虛擬機的cpu處理速度,cloudlet的length / 虛擬機mips = 任務執行所需時間,所有虛擬機的mips之和不能超過datacenter中定義的主機的物理cpu的mips之和,而虛擬cpu的mips的最大值也不能超過物理cpu的最大值,否則虛擬機將創建失敗。
CloudSimExample1
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import org.cloudbus.cloudsim.Cloudlet;
import org.cloudbus.cloudsim.CloudletSchedulerTimeShared;
import org.cloudbus.cloudsim.Datacenter;
import org.cloudbus.cloudsim.DatacenterBroker;
import org.cloudbus.cloudsim.DatacenterCharacteristics;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Pe;
import org.cloudbus.cloudsim.Storage;
import org.cloudbus.cloudsim.UtilizationModel;
import org.cloudbus.cloudsim.UtilizationModelFull;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicySimple;
import org.cloudbus.cloudsim.examples.VmAllocationPolicyFirstIn;
import org.cloudbus.cloudsim.examples.VmAllocationPolicy_BestFit;
import org.cloudbus.cloudsim.VmSchedulerTimeShared;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.provisioners.BwProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.PeProvisionerSimple;
import org.cloudbus.cloudsim.provisioners.RamProvisionerSimple;
/**
* A simple example showing how to create a datacenter with one host and run one
* cloudlet on it.
*/
public class CloudSimExample1 {
/** The cloudlet list. */
private static List<Cloudlet> cloudletList;
/** The vmlist. */
private static List<Vm> vmlist;
int algorithm_flag=0;
static List<Host> hostList = new ArrayList<Host>();
/**
* Creates main() to run this example.
*
* @param args the args
*/
@SuppressWarnings("unused")
public static void main(String[] args) {
Log.printLine("Starting CloudSimExample1...");
try {
// First step: Initialize the CloudSim package. It should be called
// before creating any entities.
int num_user = 1; // number of cloud users
Calendar calendar = Calendar.getInstance();
boolean trace_flag = false; // mean trace events
// Initialize the CloudSim library 初始化CloudSim包
CloudSim.init(num_user, calendar, trace_flag);
// Second step: Create Datacenters
// Datacenters are the resource providers in CloudSim. We need at
// list one of them to run a CloudSim simulation
Datacenter datacenter0 = createDatacenter("Datacenter_0");
// Third step: Create Broker 數據中心代理
//DatacenterBroker模擬SaaS提供商代理,根據QoS的需求協商資源和服務的分配策略
DatacenterBroker broker = createBroker();
int brokerId = broker.getId();
// Fourth step: Create one virtual machine
vmlist = new ArrayList<Vm>();
// VM description
int vmid = 0;
int mips = 3000;
long size = 10000; // image size (MB)
int ram = 1024; // vm memory (MB)
long bw = 1000;
int pesNumber1 = 2; // number of cpus
int pesNumber2 = 3; // number of cpus
int pesNumber3 = 3; // number of cpus
int pesNumber4 = 1; // number of cpus
String vmm = "Xen"; // VMM name
//create two VMs
Vm vm1 = new Vm(vmid, brokerId, mips, pesNumber1, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm2 = new Vm(vmid, brokerId, mips, pesNumber2, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm3 = new Vm(vmid, brokerId, mips, pesNumber3, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
vmid++;
Vm vm4 = new Vm(vmid, brokerId, mips, pesNumber4, ram, bw, size, vmm, new CloudletSchedulerTimeShared());
// add the VM to the vmList
vmlist.add(vm1);
vmlist.add(vm2);
vmlist.add(vm3);
vmlist.add(vm4);
// submit vm list to the broker 提交虛擬機列表
broker.submitVmList(vmlist);
// Fifth step: Create one Cloudlet 雲服務
cloudletList = new ArrayList<Cloudlet>();
// Cloudlet properties
int id = 0;
long length = 400000;
long fileSize = 300;
long outputSize = 300;
int pesNumber = 1; // number of cpus
UtilizationModel utilizationModel = new UtilizationModelFull(); //類:根據時間返回百分比利用率。
Cloudlet cloudlet = new Cloudlet(id, length, pesNumber, fileSize, outputSize, utilizationModel, utilizationModel, utilizationModel);
cloudlet.setUserId(brokerId);
cloudlet.setVmId(vmid);
// add the cloudlet to the list
cloudletList.add(cloudlet);
// submit cloudlet list to the broker 向代理提交任務列表
broker.submitCloudletList(cloudletList);
// Sixth step: Starts the simulation(模擬)
CloudSim.startSimulation();
CloudSim.stopSimulation();
//Final step: Print results when simulation is over
List<Cloudlet> newList = broker.getCloudletReceivedList();
printCloudletList(newList);
Log.printLine("CloudSimExample1 finished!");
} catch (Exception e) {
e.printStackTrace();
Log.printLine("Unwanted errors happen");
}
}
/**
* Creates the datacenter.
*
* @param name the name
*
* @return the datacenter
*/
private static Datacenter createDatacenter(String name) {
// Here are the steps needed to create a PowerDatacenter:
// 1. We need to create a list to store our machine
// 2. A Machine contains one or more PEs or CPUs/Cores.
// In this example, it will have only one core.
List<Pe> peList = new ArrayList<Pe>();
int mips = 5000;
// 3. Create PEs and add these into a list.
//創建處理器,並添加到Pe列表中:爲每個主機配置4核CPU 寫入Pe id 和 cpu處理速度
peList.add(new Pe(0, new PeProvisionerSimple(mips))); // need to store Pe id and MIPS Rating
peList.add(new Pe(1, new PeProvisionerSimple(mips)));
peList.add(new Pe(2, new PeProvisionerSimple(mips)));
peList.add(new Pe(3, new PeProvisionerSimple(mips)));
// 4. Create Host with its id and list of PEs and add them to the list
// of machines 創建主機,並將其添加至主機列表
int hostId = 0;
int ram = 2048; // host memory (MB)
long storage = 1000000; // host storage
int bw = 10000;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is our machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
//創建數據中心特徵,它表示了數據中心的資源的靜態屬性,
//比如:體系結構,操作系統,主機列表,分配策略,時間或空間共享,時區,價格
String arch = "x86"; // system architecture
String os = "Linux"; // operating system
String vmm = "Xen";
double time_zone = 10.0; // time zone this resource located
double cost = 3.0; // the cost of using processing in this resource
double costPerMem = 0.05; // the cost of using memory in this resource
double costPerStorage = 0.001; // the cost of using storage in this
// resource
double costPerBw = 0.0; // the cost of using bw in this resource
LinkedList<Storage> storageList = new LinkedList<Storage>(); // we are not adding SAN
// devices by now
DatacenterCharacteristics characteristics = new DatacenterCharacteristics(
arch, os, vmm, hostList, time_zone, cost, costPerMem,
costPerStorage, costPerBw);
// 6. Finally, we need to create a PowerDatacenter object.
Datacenter datacenter = null;
try {
//默認算法:VmAllocationPolicySimple 選擇最大剩餘PEs的主機分配
//首次適應算法:VmAllocationPolicyFirstIn 選擇最先適配的主機分配
datacenter = new Datacenter(name, characteristics, new VmAllocationPolicySimple(hostList), storageList, 0);
} catch (Exception e) {
e.printStackTrace();
}
return datacenter;
}
// We strongly encourage users to develop their own broker policies, to
// submit vms and cloudlets according
// to the specific rules of the simulated scenario
/**
* Creates the broker.
*
* @return the datacenter broker
*/
private static DatacenterBroker createBroker() {
DatacenterBroker broker = null;
try {
broker = new DatacenterBroker("Broker");
} catch (Exception e) {
e.printStackTrace();
return null;
}
return broker;
}
/**
* Prints the Cloudlet objects.
*
* @param list list of Cloudlets
*/
private static void printCloudletList(List<Cloudlet> list) {
int size = list.size();
Cloudlet cloudlet;
String indent = " ";
Log.printLine();
Log.printLine("========== OUTPUT ==========");
Log.printLine("Cloudlet ID" + indent + "STATUS" + indent
+ "Data center ID" + indent + "VM ID" + indent + "Time" + indent
+ "Start Time" + indent + "Finish Time");
DecimalFormat dft = new DecimalFormat("###.##");
for (int i = 0; i < size; i++) {
cloudlet = list.get(i);
Log.print(indent + cloudlet.getCloudletId() + indent + indent);
if (cloudlet.getCloudletStatus() == Cloudlet.SUCCESS) {
Log.print("SUCCESS");
Log.printLine(indent + indent + cloudlet.getResourceId()
+ indent + indent + indent + cloudlet.getVmId()
+ indent + indent
+ dft.format(cloudlet.getActualCPUTime()) + indent
+ indent + dft.format(cloudlet.getExecStartTime())
+ indent + indent
+ dft.format(cloudlet.getFinishTime()));
}
}
}
}
開始仿真模擬時,首先需要創建一個數據中心,然後再數據中心中創建CPU、內存等資源,此時只需要向代理中心註冊資源信息,用戶就可以使用數據中心的資源進行仿真模擬。在仿真資源分配試驗中,其步驟及其各個步驟中的代碼如下:
(1) 初始化Cloudsim包,代碼如下:
Int num_user= 1 ; //定義用戶數量
Calendar calendar=Calendar.getInstance();
boolean trace_flag=false;
CloudSim.init(num_user, calendar, trace_flag); //初始化CloudSim包
(2)創建數據中心(Datacenter),如果多次創建那就生成多個數據中心,代碼如下所示:
DataCenter datacenter()=createDatacenter("Datacenter_0");
(3) 創建數據中心代理(Broker),代碼如下所示。注:任務到虛擬機的映射是由DatacenterBroker類中的bindCloudletsToVms()函數實現。該函數根據不同的策略來實現任務的映射。
DatacenterBroker broker=createBroker();
Int brokerId=broker.get_id();
(4) 創建虛擬機,只有提交了虛擬機列表,broker纔會發揮作用。代碼如下所示:
vmlist=new VirtualMachineList(); //創建虛擬機列表
Vmvm=new Vm(vmid, brokerld, mips, PesNumber, ram, bw, size,
vmm,new CloudletSchedulerTimeShared()); //創建虛擬機
vmlist.add(vm); //加入虛擬機列表
broker.submitVMList(vmlist);//提交虛擬機列表
(5) 創建雲任務,代碼如下所示:
cloudletList = new CloudletList();//創建雲任務列表
Cloudlet cloudlet=new Cloudlet(id, length, file_size, output_size);
cloudlet.setUserlD(brokerld);
……
cloudletList.add(cloudlet); //將任務加入任務列表
……
broker.submitCloudletList(cloudletList);//向代理提交任務列表
(6) 執行資源調度算法,完成任務到虛擬機的映射,代碼如下所示:
broker. bindCloudletsToVms();
(7) 啓動仿真程序,代碼如下所示:
CloudSim.startSimulation();
(8) 打印仿真結果,代碼如下所示:
List<Cloudlet> newList = broker.getCloudletReceivedList();
CloudSim.stopSimulation();
printCloudletList(newList);
在createDatacenter函數中,我設定了4個主機,爲每個主機配置4核CPU。首先寫入PEs 的id 和 cpu處理速度。
List<Pe> peList = new ArrayList<Pe>();
int mips = 5000;
//爲每個主機配置4核CPU 寫入Pe id 和 cpu處理速度
peList.add(new Pe(0, new PeProvisionerSimple(mips)));
peList.add(new Pe(1, new PeProvisionerSimple(mips)));
peList.add(new Pe(2, new PeProvisionerSimple(mips)));
peList.add(new Pe(3, new PeProvisionerSimple(mips)));
隨後,創建四個主機,寫入id和PEs列表到主機中。
int hostId = 0;
int ram = 2048; // host memory (MB)
long storage = 1000000; // host storage
int bw = 10000;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is frist machine
hostId++;
hostList.add(
new Host(
hostId,
new RamProvisionerSimple(ram),
new BwProvisionerSimple(bw),
storage,
peList,
new VmSchedulerTimeShared(peList)
)
); // This is second machine
hostId++;
...
創建數據中心的的倉庫和屬性,這部分的源碼我沒有修改,具體參見源代碼。
VmAllocationPolicySimple
package org.cloudbus.cloudsim.examples;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cloudbus.cloudsim.Host;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.VmAllocationPolicy;
import org.cloudbus.cloudsim.core.CloudSim;
/**
* VmAllocationPolicySimple is an VmAllocationPolicy that chooses, as the host for a VM, the host
* with less PEs in use.
*/
//VmAllocationPolicy 抽象類 代表了數據中心主機到虛擬機的供應協議
public class VmAllocationPolicyFirstIn extends VmAllocationPolicy {
/** The vm table.記錄虛擬機被分配到哪臺主機 */
private Map<String, Host> vmTable;
/** The used pes.記錄虛擬機佔用了幾個處理器核心 */
private Map<String, Integer> usedPes;
/** The free pes.記錄每臺主機可用的處理器核心數 */
private List<Integer> freePes;
/**
* Creates the new VmAllocationPolicySimple object.
* @param list the list
* @pre $none
* @post $none
*/
public VmAllocationPolicyFirstIn(List<? extends Host> list) {
//初始化主機列表hostList(繼承自父類的成員)
super(list);
//初始化每臺主機可用的處理器核心數freePes
setFreePes(new ArrayList<Integer>());
for (Host host : getHostList()) {
getFreePes().add(host.getNumberOfPes());
}
//初始化vmTable和usedPes
setVmTable(new HashMap<String, Host>());
setUsedPes(new HashMap<String, Integer>());
}
/**
* Allocates a host for a given VM.
* @param vm VM specification
* @return $true if the host could be allocated; $false otherwise
* @pre $none
* @post $none
*/
@Override
public boolean allocateHostForVm(Vm vm) {
int requiredPes = vm.getNumberOfPes();//創建vm所需的處理器核心數
boolean result = false;
int tries = 0; //嘗試次數
List<Integer> freePesTmp = new ArrayList<Integer>();
for (Integer freePes : getFreePes()) {
freePesTmp.add(freePes);
}
//如果當前虛擬機還未創建
if (!getVmTable().containsKey(vm.getUid())) { // if this vm was not created
do {// we still trying until we find a host or until we try all of them
//嘗試創建虛擬機直到創建成功或所有的主機都已經嘗試過
int moreFree = Integer.MIN_VALUE;//當前最大可用核心數
int idx = -1; //當前最大可用核心數對應主機的下標
//默認算法;找到可用處理器核心數最大的第一臺主機
//TestData:VM所需核數分別爲 2 3 3 1
//正確結果: 剩餘資源:2 1 1 3 主機分配ID:0 1 2 3
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) > moreFree) {
moreFree = freePesTmp.get(i);
idx = i;
}
}
Host host = getHostList().get(idx);
result = host.vmCreate(vm);//嘗試創建虛擬機
if (result) { // if vm were succesfully created in the host
//更新映射關係及主機可用的處理器核心數
getVmTable().put(vm.getUid(), host);
getUsedPes().put(vm.getUid(), requiredPes);
getFreePes().set(idx, getFreePes().get(idx) - requiredPes);
result = true;
break;
} else {//如果創建失敗
//將當前主機的可用處理器核心數暫時設成最小值,從而排除該主機
freePesTmp.set(idx, Integer.MIN_VALUE);
}
tries++;
} while (!result && tries < getFreePes().size());
}
System.out.println("配置完成後PEs的剩餘情況");
System.out.println(getFreePes().get(0)+" "+getFreePes().get(1)+" "+getFreePes().get(2)+" "+getFreePes().get(3));
return result;
}
/**
* Releases the host used by a VM.
*
* @param vm the vm
* @pre $none
* @post none
*/
@Override
public void deallocateHostForVm(Vm vm) {
//刪除虛擬機相應的映射關係,通過主機銷燬虛擬機並更新可用的處理器核心數
Host host = getVmTable().remove(vm.getUid());
int idx = getHostList().indexOf(host);
int pes = getUsedPes().remove(vm.getUid());
if (host != null) {
host.vmDestroy(vm);
getFreePes().set(idx, getFreePes().get(idx) + pes);
}
}
...
//將虛擬機分配給指定的主機
@Override
public boolean allocateHostForVm(Vm vm, Host host) {
if (host.vmCreate(vm)) { //如果虛擬機創建成功,更新vmTable,並返回true
getVmTable().put(vm.getUid(), host);
int requiredPes = vm.getNumberOfPes();
int idx = getHostList().indexOf(host);
getUsedPes().put(vm.getUid(), requiredPes);
getFreePes().set(idx, getFreePes().get(idx) - requiredPes);
Log.formatLine(
"%.2f: VM #" + vm.getId() + " has been allocated to the host #" + host.getId(),
CloudSim.clock());
return true;
}
return false;
}
}
VmAllocationPolicy
類代表了數據中心主機到虛擬機的供應協議。在類剛開始便定義了vm table
(記錄虛擬機被分配到哪臺主機)、used pes
(記錄虛擬機佔用了幾個處理器核心)、free pes
(記錄每臺主機可用的處理器核心數),VmAllocationPolicy
就是通過PEs處理器核心的個數進行單資源的調度,所以在這裏我們只需關心這幾個參數。傳入Host列表,初始化這幾個參數之後,我們便可以開始調度。
判斷當前傳入的vm是否已經創建,在邏輯上就是判斷VmTable中有沒有寫入該vm的ID,未創建的話,進行資源分配,並創建虛擬機,以及更新映射關係及主機可用的處理器核心數。如果創建失敗,則將當前主機的可用處理器核心數暫時設成最小值,從而排除該主機。具體代碼以及詳細介紹見上。
四、虛擬機分配
虛擬機分配指的是,選擇滿足特定條件(內存、軟件環境配置等)的主機創建虛擬機的過程,這個過程由Datacenter對象負責。在雲數據中心,將特定應用的虛擬機分配給主機由虛擬機分配控制器VmAllocationPolicy
完成,Cloudsim在主機層和虛擬機層都實現了基於時間共享和空間共享的調度策略。用戶可以通過繼承該類實現自己的分配策略,CloudSim中,作者實現了一種簡單的分配策略——VmAllocationPolicySimple
。方法allocateHostForVm(Vm vm)
是該類的核心,它實現了從主機列表中選擇一臺主機,並在其上創建虛擬機vm。
主要實現過程的描述如下:
1. 記錄下所有主機可用的處理器核心數。
2. 從中選出可用處理器核心數最多的第一臺主機,並嘗試在其上創建虛擬機。
3. 如果 2 失敗了且還有主機沒有嘗試過,就排除當前選擇的這臺主機,重做 2。
4. 根據虛擬機是否創建成功,返回true或false。
我們自己寫相關算法,執行調度該如何修改代碼呢?下面接着敘述。
虛擬機的初始放置實際上是一個多維向量的裝箱問題,裝箱問題均是NP-Hard問題,目前針對裝箱問題的解法主要爲基於貪心算法的啓發式算法,例如首次適應算法(First Fit,FF),降序首次適應算法(First Fit Descending,FFD),最佳適應算法(Best Fit,BF)和降序最佳適應啓發式算法(Best Fit Descending,BFD)等。
- (1)首次適應算法:爲物品尋找目的箱子時,首先從第一個箱子尋找,如果不滿足條件(不能放在箱子),則繼續尋找直到找到合適的箱子。在目的箱子中爲物品分配所需空間,剩餘空間可以繼續放置新的物品。如果不能找到合適的箱子,則物品放置失敗。
- (2)降序首次適應算法:首先將箱子按照某種資源(如CPU等)的大小進行降序排序,然後按照首次適應算法查找合適的箱子。如果能找到合適的箱子,則放置物品,更新箱子大小,放置成功,否則放置失敗。
- (3)最佳適應算法:將物品裝入箱子時,在所有箱子中查找大於且最接近物品大小的箱子,然後再從該箱子中分配物品所需的空間,餘下的空間作爲新箱子繼續放置其他物品,此時物品放置成功。如果任何箱子都不能放置物品,則放置失敗。
- (4)降序最佳適應算法:在最佳適應算法的基礎上進行改進,首先將箱子按照某種資源(如CPU等)的大小進行降序排序,然後執行最佳適應算法查找箱子。若找到合適的箱子,則放置物品並更新箱子的大小,放置成功,否則失敗。
默認調度方法
int moreFree = Integer.MIN_VALUE;//當前最大可用核心數
int idx = -1; //當前最大可用核心數對應主機的下標
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) > moreFree) {
moreFree = freePesTmp.get(i);
idx = i;
}
}
上面是VmAllocationPolicySimple
中的默認算法。即找到可用處理器核心數最大的第一臺主機。
在這裏,測試數據爲:VM所需核數分別爲 2 3 3 1,按照在Example1中定義的VM和Host列表的資源規配置,那麼運行出來的正確結果應該是:
- 0-3號主機中剩餘PEs資源:2 1 1 3
0-3號虛擬機的主機分配ID:0 1 2 3
結果截圖:
首次適應算法
int moreFree = Integer.MIN_VALUE;//當前最大可用核心數
int idx = -1; //當前最大可用核心數對應主機的下標
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) >= requiredPes) {
idx = i;
break;
}
}
上面是首次適應算法的代碼。即按照次序找到符合條件的第一個主機。
在這裏,測試數據爲:VM所需核數分別爲 2 3 3 1,按照在Example1中定義的VM和Host列表的資源規配置,那麼運行出來的正確結果應該是:
- 0-3號主機中剩餘PEs資源:1 1 1 4
0-3號虛擬機的主機分配ID:0 1 2 0
結果截圖:
最佳適應算法
int resource_diff = Integer.MAX_VALUE;//所需資源與目前資源的差值
int idx = -1; //當前最大可用核心數對應主機的下標
for (int i = 0; i < freePesTmp.size(); i++) {
if (freePesTmp.get(i) >= requiredPes) {
int r = (freePesTmp.get(i) - requiredPes);//所需資源與目前資源的差值,找出最小的一個
if(r < resource_diff) {
resource_diff = r;
idx = i;
}
}
}
上面是最佳適應算法的代碼。即查找出大於且最接近所需PEs大小的主機。
在這裏,測試數據爲:VM所需核數分別爲 2 3 3 1,按照在Example1中定義的VM和Host列表的資源規配置,那麼運行出來的正確結果應該是:
- 0-3號主機中剩餘PEs資源:2 0 1 4
0-3號虛擬機的主機分配ID:0 1 2 1
結果截圖: