源地址:
======================================================
在談這東西之前,就一定要先聊聊,Hypervisor這東西
不論是VMWare vSphere,或是Hyper-V,都一定會有這東西
這是Guest OS存取Hardware必定經過的一層,另一個名稱叫做Virtual Machine Monitor
首先,先來看看有啟用Hyper-V跟沒有啟用Hyper-V的開機差異
(左圖是未啟用Hyper-V、右圖是啟用Hyper-V)
當Hypervisor被啟用之後,將會使用%Systemroot%\System32\Drivers底下的hvboot.sys先載入Hypervisor
接著在透過實際的CPU來決定使用哪一種VT技術,%Systemroot%\System32底下的hvax64.exe或者hvix64.exe
hvax64.exe是AMD-V,hvix.64.exe是Intel-VT
啟用了Hyper-V之後,將會使用Hypervisor去存取Hardware的CPU與Memory
接著,才會取啟動Host Server(在Hyper-V中稱為Parent Partition),又稱為父分割區
在啟用Hyper-V之後,Host將會處於下圖VM1中的位置,後續所建立的Guest OS,將會在VM2與VM3的位置
又稱為Child Partition,子分割區
從上圖可以看到,Child Partition都會需要安裝Driver才能存取像是Ethemet、Hard Drive、Time Sync…等等的一些裝置
然而會有很多VMWare ESX的使用者會說,在VMWare ESX裡的Guest OS就不需要安裝就能夠使用了
這是因為這兩者的Hypervisor的結構本身並不相同,VMWare ESX使用的是感知型的Hypervisor Drivers
所以會需要硬體支援(硬體廠商配合VMWare開發),但是Hyper-V則是Host有支援即可(有驅動就可以)
(下圖為VMWare ESX的Hypervisor)
雖然VMWare可以在未安裝VM Tools的情況下運作,但是還是安裝後才能得到更多進階的功能
因為此篇主要是講解Hyper-V,在此就不對VMWare的部份有過多的說明囉~
下面這張圖,將說明啟用前跟啟用後的差異
下圖,則是Parent跟Child之間的關係,在VMBus可溝通的情況下,將會使用Hypercall對Hypervisor進行I/O
Virtual Machine Worker Process(VMWP)就相當於是Guest OS的主機板
VMWP會使用VMBUS、VID去使用Hypervisor API去對Hypervisor存取,取得Guest OS該使用的資源
Virtualization infrastructure Driver其實就是%Systemroot%\System32\Drivers底下的winhv.sys
當Guest OS在存取VHD時,則會透過VSC,經過VMBUS連接父分割區的VMBUS,由父分割區的VSP進行DISK I/O
這是比較詳細的I/O運作邏輯
這是Driver透過VMBUS的對應
在此引用這個網站的圖片,來做一個簡單的圖解
微軟的MSDN中,有提供Hyper-V WMI Provider的文件可以參考
在啟用Hyper-V後,會新增root\virtualization這個命名空間,我們可以透過PowerShell去查詢該命名空間底下所有的Object
指令是Get-Wmiobject –Namespace “root\virtualization” –Computer Hostname –list
下圖這個範例,示範了如何透過WMI去查詢Guest OS的狀態
Get-WmiObject -Class Msvm_ComputerSystem -Namespace "root\virtualization" -ComputerName Hostname
其中的EnabledState就是回傳的狀態值,2代表正常
這邊看似沒甚麼了不起,GUI介面就可以得知的東西
呃…確實是如此,因為GUI介面就已經幫你寫好查詢的方法跟回傳,這裡只是大概講解Hyper-V Manager
是如何去取得Guest OS的狀態之類
但是如果是GUI介面所沒有的呢:P
或者你要用Job的方式從Host去控制Guest OS,而不是透過Guest OS的Job去控制呢?
這裡的命令,其實都是透過WMI去控制的
接著來示範一個很奇特的範例,來驗證第一張圖,WMI的架構
我們先透過一個VBScript去使用Windows Script Host去呼叫CIMOM
然後,對Guest OS做鍵盤輸入的動作,首先先將下列VBScript存成PressKey.vbs
如對此VBScript不放心,您可以直接至Microsoft MSDN中複製
001 | option explicit |
002 |
003 | dim objWMIService |
004 | dim fileSystem |
005 | const wmiSuccessful = 0 |
006 |
007 | Main() |
008 |
009 | '----------------------------------------------------------------- |
010 | ' Main routine |
011 | '----------------------------------------------------------------- |
012 | Sub Main() |
013 | |
014 | dim computer, objArgs, vmName, computerSystem, keycode, keyboard |
015 | |
016 | set fileSystem = Wscript.CreateObject( "Scripting.FileSystemObject" ) |
017 |
018 | computer = "." |
019 | set objWMIService = GetObject( "winmgmts:\\" & computer & "\root\virtualization" ) |
020 |
021 | set objArgs = WScript.Arguments |
022 | if WScript.Arguments.Count = 2 then |
023 | vmName= objArgs.Unnamed.Item(0) |
024 | keycode = objArgs.Unnamed.Item(1) |
025 | else |
026 | WScript.Echo "usage: cscript PressKey.vbs vmName keycode" |
027 | WScript.Quit |
028 | end if |
029 | |
030 | set computerSystem = GetComputerSystem(vmName) |
031 | set keyboard = GetComputerKeyboard(computerSystem) |
032 |
033 | if PressKey(keyboard, keycode) then |
034 |
035 | WriteLog "Done" |
036 | WScript.Quit(0) |
037 | else |
038 | WriteLog "PressKey failed" |
039 | WScript.Quit(1) |
040 | end if |
041 |
042 | End Sub |
043 |
044 | '----------------------------------------------------------------- |
045 | ' Retrieve Msvm_VirtualComputerSystem from base on its ElementName |
046 | ' |
047 | '----------------------------------------------------------------- |
048 | Function GetComputerSystem(vmElementName) |
049 | dim query |
050 | On Error Resume Next |
051 | query = Format1( "select * from Msvm_ComputerSystem where ElementName = '{0}'" , vmElementName) |
052 | set GetComputerSystem = objWMIService.ExecQuery(query).ItemIndex(0) |
053 | if (Err.Number <> 0) then |
054 | WriteLog Format1( "Err.Number: {0}" , Err.Number) |
055 | WriteLog Format1( "Err.Description:{0}" ,Err.Description) |
056 | WScript.Quit(1) |
057 | end if |
058 | End Function |
059 |
060 |
061 | '----------------------------------------------------------------- |
062 | ' Retrieve Msvm_Keyboard from given computer system |
063 | ' |
064 | '----------------------------------------------------------------- |
065 | Function GetComputerKeyboard(computerSystem) |
066 | dim query |
067 | On Error Resume Next |
068 | query = Format1( "ASSOCIATORS OF { {0}} WHERE resultClass = Msvm_Keyboard" , computerSystem.Path_.Path) |
069 | set GetComputerKeyboard = objWMIService.ExecQuery(query).ItemIndex(0) |
070 | if (Err.Number <> 0) then |
071 | WriteLog Format1( "Err.Number: {0}" , Err.Number) |
072 | WriteLog Format1( "Err.Description:{0}" ,Err.Description) |
073 | WScript.Quit(1) |
074 | end if |
075 | End Function |
076 |
077 | '----------------------------------------------------------------- |
078 | ' Press the key with the given key code on the given keyboard |
079 | '----------------------------------------------------------------- |
080 | Function PressKey(keyboard, keyCode) |
081 | WriteLog Format2( "PressKey({0}, {1})" , keyboard.ElementName, keyCode) |
082 | |
083 | dim objInParam, objOutParams |
084 | |
085 | PressKey = false |
086 | set objInParam = keyboard.Methods_( "PressKey" ).InParameters.SpawnInstance_() |
087 | objInParam.keyCode = keyCode |
088 |
089 | set objOutParams = keyboard.ExecMethod_( "PressKey" , objInParam) |
090 |
091 | if objOutParams.ReturnValue = wmiSuccessful then |
092 | WriteLog Format2( "The key with code '{0}' is typed on {1}." , keyCode, keyboard.ElementName) |
093 | PressKey = true |
094 | end if |
095 |
096 | End Function |
097 |
098 | '----------------------------------------------------------------- |
099 | ' Create the console log files. |
100 | '----------------------------------------------------------------- |
101 | Sub WriteLog(line) |
102 | dim fileStream |
103 | set fileStream = fileSystem.OpenTextFile( ".\PressKey.log" , 8, true) |
104 | WScript.Echo line |
105 | fileStream.WriteLine line |
106 | fileStream.Close |
107 |
108 | End Sub |
109 |
110 |
111 | '------------------------------------------------------------------------------ |
112 | ' The string formatting functions to avoid string concatenation. |
113 | '------------------------------------------------------------------------------ |
114 | Function Format2(myString, arg0, arg1) |
115 | Format2 = Format1(myString, arg0) |
116 | Format2 = Replace(Format2, "{1}" , arg1) |
117 | End Function |
118 |
119 | '------------------------------------------------------------------------------ |
120 | ' The string formatting functions to avoid string concatenation. |
121 | '------------------------------------------------------------------------------ |
122 | Function Format1(myString, arg0) |
123 | Format1 = Replace(myString, "{0}" , arg0) |
124 | End Function |
接著我們會需要這張表去對應Virtial Key相對應的Uint32的值
這張表的值是16進位,將其換算成十進位後,加上256,會可以輸入該值到Guest OS中
像範例中,我會Daniel-AD這部Guest OS輸入鍵盤324,d這個字母在表列中為0x44
也就是256+68=324,接著他就會在Guest OS中輸入d這個字母
你也可以透過Powershell去轉換這個數值
PowerShell的語法function To-UInt32{[UInt32]("0x{0:x}" -f $args[0])+256}
這可以新增一個To UInt32的轉換方法
接著可以透過To-UInt32 0x44去取得相對應的值