轉載自:http://smilejay.com/2012/08/kvm-vcpu-binding/
通常情況下,在SMP系統中,Linux核心的程式排程器根據自有的排程策略將系統中的一個程式排程到某個CPU上執行。一個程式在前一個執行時間 是在cpuM(M為系統中的某CPU的ID)上執行,而在後一個執行時間是在cpuN(N為系統中另一CPU的ID)上執行。這樣的情況在Linux中是 很可能發生的,因為Linux對程式執行的排程採用時間片法則(即進行用完自己的時間片即被暫停執行),而預設情況下,一個普通程式或執行緒的處理器親和性 是在所有可用的CPU上,有可能在它們之中的任何一個CPU(包括超執行緒)上執行。
程式的處理器親和性(Processor Affinity),即是CPU的繫結設定,是指將程式繫結到特定的一個或多個CPU上去執行,而不允許排程到其他的CPU上。Linux核心對程式的調 度演算法也是遵守程式的處理器親和性設定的。設定程式的處理器親和性帶來的好處是可以減少程式在多個CPU之間交換執行帶來的快取命中失效(cache missing),從該程式執行的角度來看,可能帶來一定程度上的效能提升。換個角度來看,對程式親和性的設定也可能帶來一定的問題,如破壞了原有SMP 系統中各個CPU的負載均衡(load balance),這可能會導致整個系統的程式排程變得低效。特別是在多處理器、多核、多執行緒技術使用的情況下,在NUMA(Non-Uniform Memory Access)[3]結構的系統中,如果不能基於對系統的CPU、記憶體等有深入的瞭解,對程式的處理器親和性進行設定是可能導致系統的整體效能的下降而非提升。
每個vCPU都是宿主機中的一個普通的QEMU執行緒,可以使用taskset工具對其設定處理器親和性,使其繫結到某一個或幾個固定的CPU上去調 度。儘管Linux核心的程式排程演算法已經非常高效了,在多數情況下不需要對程式的排程進行干預,不過,在虛擬化環境中有時卻有必要對客戶機的QEMU進 程或執行緒繫結到固定的邏輯CPU上。下面舉一個雲端計算應用中需要繫結vCPU的例項。
作為IAAS(Infrastructure As A Service)型別的雲端計算提供商的A公司(如Amazon、Google、阿里雲、盛大雲等),為客戶提供一個有2個邏輯CPU計算能力的一個客戶 機。要求CPU資源獨立被佔用,不受宿主機中其他客戶機的負載水平的影響。為了滿足這個需求,可以分為如下兩個步驟來實現。
第一步,啟動宿主機時隔離出兩個邏輯CPU專門供一個客戶機使用。在Linux核心啟動的命令列加上“isolcpus=”引數,可以實現CPU的 隔離,讓系統啟動後普通程式預設都不會排程到被隔離的CPU上執行。例如,隔離了cpu2和cpu3的grub的配置檔案如下:
title Red Hat Enterprise Linux Server (3.5.0)
root (hd0,0)
kernel /boot/vmlinuz-3.5.0 ro root=UUID=1a65b4bb-cd9b-4bbf-97ff-7e1f7698d3db isolcpus=2,3
initrd /boot/initramfs-3.5.0.img
系統啟動後,在宿主機中檢查是否隔離成功,命令列如下:
[root@jay-linux ~]# ps -eLo psr | grep 0 | wc -l
106
[root@jay-linux ~]# ps -eLo psr | grep 1 | wc -l
107
[root@jay-linux ~]# ps -eLo psr | grep 2 | wc -l
4
[root@jay-linux ~]# ps -eLo psr | grep 3 | wc -l
4
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’
root 10 2 10 2 [migration/2]
root 11 2 11 2 [kworker/2:0]
root 12 2 12 2 [ksoftirqd/2]
root 245 2 245 2 [kworker/2:1]
[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’
root 13 2 13 3 [migration/3]
root 14 2 14 3 [kworker/3:0]
root 15 2 15 3 [ksoftirqd/3]
root 246 2 246 3 [kworker/3:1]
從上面的命令列輸出資訊可知,cpu0和cpu1上分別有106和107個執行緒在執行,而cpu2和cpu3上都分別只有4個執行緒在執行。而且,根 據輸出資訊中cpu2和cpu3上執行的執行緒資訊(也包括程式在內),分別有migration程式(用於程式在不同CPU間遷移)、兩個kworker 程式(用於處理workqueues)、ksoftirqd程式(用於排程CPU軟中斷的程式),這些程式都是核心對各個CPU的一些守護程式,而沒有其 他的普通程式在cup2和cpu3上執行,說明對其的隔離是生效的。
另外,簡單解釋一下上面的一些命令列工具及其引數的意義。ps命令顯示當前系統的程式資訊的狀態,它的“-e”引數用於顯示所有的程式,“-L”參 數用於將執行緒(LWP,light-weight process)也顯示出來,“-o”參數列示以使用者自定義的格式輸出(其中“psr”這列表示當前分配給程式執行的處理器編號,“lwp”列表示執行緒的 ID,“ruser”表示執行程式的使用者,“pid”表示程式的ID,“ppid”表示父程式的ID,“args”表示執行的命令及其引數)。結合ps和 awk工具的使用,是為了分別將在處理器cpu2和cpu3上執行的程式列印出來。
第二步,啟動一個擁有2個vCPU的客戶機並將其vCPU繫結到宿主機中兩個CPU上。此操作過程的命令列如下:
#(啟動一個客戶機)
[root@jay-linux kvm_demo]# qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
VNC server running on ‘::1:5900’
#(檢視代表vCPU的QEMU執行緒)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep
root 3963 1 3963 0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3968 1 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize
#(繫結代表整個客戶機的QEMU程式,使其執行在cpu2上)
[root@jay-linux ~]# taskset -p 0×4 3963
pid 3963′s current affinity mask: 3
pid 3963′s new affinity mask: 4
#(繫結第一個vCPU的執行緒,使其執行在cpu2上)
[root@jay-linux ~]# taskset -p 0×4 3967
pid 3967′s current affinity mask: 3
pid 3967′s new affinity mask: 4
#(繫結第二個vCPU的執行緒,使其執行在cpu3上)
[root@jay-linux ~]# taskset -p 0×8 3968
pid 3968′s current affinity mask: 4
pid 3968′s new affinity mask: 8
#(檢視QEMU執行緒的繫結是否生效,如下的第5列為處理器親和性)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep
root 3963 1 3963 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3968 3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize
#(執行vCPU的繫結後,檢視在cpu2上執行的執行緒)
[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’
root 10 2 10 2 [migration/2]
root 11 2 11 2 [kworker/2:0]
root 12 2 12 2 [ksoftirqd/2]
root 245 2 245 2 [kworker/2:1]
root 3963 1 3963 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
root 3963 1 3967 2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
#(執行vCPU的繫結後,檢視在cpu3上執行的執行緒)
[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’
root 13 2 13 3 [migration/3]
root 14 2 14 3 [kworker/3:0]
root 15 2 15 3 [ksoftirqd/3]
root 246 2 246 3 [kworker/3:1]
root 3963 1 3968 3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
由上面的命令列及其輸出資訊可知,CPU繫結之前,代表這個客戶機的QEMU程式和代表各個vCPU的QEMU執行緒分別被排程到cpu0和cpu1 上。使用taskset命令將QEMU程式和第一個vCPU的執行緒繫結到cpu2,將第二個vCPU執行緒繫結到cpu3上。繫結之後,即可檢視到繫結的結 果是生效的,代表兩個vCPU的QEMU執行緒分別執行在cpu2和cpu3上(即使再過一段時間後,它們也不會被排程到其他CPU上去)。
對taskset命令解釋一下,此處使用的語法是:taskset -p [mask] pid 。其中,mask是一個代表了處理器親和性的掩碼數字,轉化為二進位制表示後,它的值從最低位到最高位分別代表了第一個邏輯CPU到最後一個邏輯CPU,進 程排程器可能將該程式排程到所有標為“1”的位代表的CPU上去執行。根據上面的輸出,taskset執行之前,QEMU執行緒的處理器親和性mask值是 0×3(其二進位制值為:0011),可知其可能會被排程到cpu0和cpu1上執行;而執行“taskset -p 0×4 3967”命令後,提示新的mask值被設為0×4(其二進位制值為:0100),所以該程式就只能被排程到cpu2上去執行,即通過taskset工具實 現了vCPU程式繫結到特定的CPU上。
上面命令列中,根據ps命令可以看到QEMU的執行緒和程式的關係,但如何檢視vCPU與QEMU執行緒之間的關係呢?可以切換 (“Ctrl+Alt+2”快捷鍵)到QEMU monitor中進行檢視,執行“info cpus”命令即可(還記得3.6節中執行過的“info kvm”命令吧),其輸出結果如下:
(qemu) info cpus
* CPU #0: pc=0xffffffff810375ab thread_id=3967
CPU #1: pc=0xffffffff812b2594 thread_id=3968
從上面的輸出資訊可知,客戶機中的cpu0對應的執行緒ID為3967,cpu1對應的執行緒ID為3968。另外,“CPU #0”前面有一個星號(*),是標識cpu0是BSP(Boot Strap Processor,系統最初啟動時在SMP生效前使用的CPU)。
總的來說,在KVM環境中,一般並不推薦手動地人為設定QEMU程式的處理器親和性來繫結vCPU,但是,在非常瞭解系統硬體架構的基礎上,根據實際應用的需求,是可以將其繫結到特定的CPU上去從而提高客戶機中的CPU執行效率或者實現CPU資源獨享的隔離性。