通常情況下,,在SMP系統(tǒng)中,,Linux內(nèi)核的進程調(diào)度器根據(jù)自有的調(diào)度策略將系統(tǒng)中的一個進程調(diào)度到某個CPU上執(zhí)行。一個進程在前一個執(zhí)行時間是在cpuM(M為系統(tǒng)中的某CPU的ID)上運行,,而在后一個執(zhí)行時間是在cpuN(N為系統(tǒng)中另一CPU的ID)上運行,。這樣的情況在Linux中是很可能發(fā)生的,因為Linux對進程執(zhí)行的調(diào)度采用時間片法則(即進行用完自己的時間片即被暫停執(zhí)行),,而默認情況下,,一個普通進程或線程的處理器親和性是在所有可用的CPU上,有可能在它們之中的任何一個CPU(包括超線程)上執(zhí)行,。
進程的處理器親和性(Processor Affinity),,即是CPU的綁定設(shè)置,是指將進程綁定到特定的一個或多個CPU上去執(zhí)行,,而不允許調(diào)度到其他的CPU上,。Linux內(nèi)核對進程的調(diào)度算法也是遵守進程的處理器親和性設(shè)置的。設(shè)置進程的處理器親和性帶來的好處是可以減少進程在多個CPU之間交換運行帶來的緩存命中失效(cache missing),,從該進程運行的角度來看,,可能帶來一定程度上的性能提升。換個角度來看,,對進程親和性的設(shè)置也可能帶來一定的問題,,如破壞了原有SMP系統(tǒng)中各個CPU的負載均衡(load balance),這可能會導(dǎo)致整個系統(tǒng)的進程調(diào)度變得低效,。特別是在多處理器,、多核、多線程技術(shù)使用的情況下,,在NUMA(Non-Uniform Memory Access)[3]結(jié)構(gòu)的系統(tǒng)中,,如果不能基于對系統(tǒng)的CPU、內(nèi)存等有深入的了解,,對進程的處理器親和性進行設(shè)置是可能導(dǎo)致系統(tǒng)的整體性能的下降而非提升,。
每個vCPU都是宿主機中的一個普通的QEMU線程,可以使用taskset工具對其設(shè)置處理器親和性,,使其綁定到某一個或幾個固定的CPU上去調(diào)度,。盡管Linux內(nèi)核的進程調(diào)度算法已經(jīng)非常高效了,在多數(shù)情況下不需要對進程的調(diào)度進行干預(yù),,不過,,在虛擬化環(huán)境中有時卻有必要對客戶機的QEMU進程或線程綁定到固定的邏輯CPU上。下面舉一個云計算應(yīng)用中需要綁定vCPU的實例,。
作為IAAS(Infrastructure As A Service)類型的云計算提供商的A公司(如Amazon,、Google、阿里云,、盛大云等),,為客戶提供一個有2個邏輯CPU計算能力的一個客戶機。要求CPU資源獨立被占用,,不受宿主機中其他客戶機的負載水平的影響,。為了滿足這個需求,可以分為如下兩個步驟來實現(xiàn),。
第一步,,啟動宿主機時隔離出兩個邏輯CPU專門供一個客戶機使用。在Linux內(nèi)核啟動的命令行加上“isolcpus=”參數(shù),,可以實現(xiàn)CPU的隔離,,讓系統(tǒng)啟動后普通進程默認都不會調(diào)度到被隔離的CPU上執(zhí)行。例如,,隔離了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
系統(tǒng)啟動后,,在宿主機中檢查是否隔離成功,命令行如下:
[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個線程在運行,。而且,,根據(jù)輸出信息中cpu2和cpu3上運行的線程信息(也包括進程在內(nèi)),,分別有migration進程(用于進程在不同CPU間遷移)、兩個kworker進程(用于處理workqueues),、ksoftirqd進程(用于調(diào)度CPU軟中斷的進程),這些進程都是內(nèi)核對各個CPU的一些守護進程,,而沒有其他的普通進程在cup2和cpu3上運行,說明對其的隔離是生效的,。
另外,簡單解釋一下上面的一些命令行工具及其參數(shù)的意義,。ps命令顯示當前系統(tǒng)的進程信息的狀態(tài),,它的“-e”參數(shù)用于顯示所有的進程,“-L”參數(shù)用于將線程(LWP,light-weight process)也顯示出來,,“-o”參數(shù)表示以用戶自定義的格式輸出(其中“psr”這列表示當前分配給進程運行的處理器編號,,“l(fā)wp”列表示線程的ID,“ruser”表示運行進程的用戶,,“pid”表示進程的ID,,“ppid”表示父進程的ID,“args”表示運行的命令及其參數(shù)),。結(jié)合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
#(執(zhí)行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
#(執(zhí)行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線程分別被調(diào)度到cpu0和cpu1上。使用taskset命令將QEMU進程和第一個vCPU的線程綁定到cpu2,,將第二個vCPU線程綁定到cpu3上,。綁定之后,即可查看到綁定的結(jié)果是生效的,,代表兩個vCPU的QEMU線程分別運行在cpu2和cpu3上(即使再過一段時間后,,它們也不會被調(diào)度到其他CPU上去)。
對taskset命令解釋一下,,此處使用的語法是:taskset -p [mask] pid ,。其中,mask是一個代表了處理器親和性的掩碼數(shù)字,,轉(zhuǎn)化為二進制表示后,,它的值從最低位到最高位分別代表了第一個邏輯CPU到最后一個邏輯CPU,進程調(diào)度器可能將該進程調(diào)度到所有標為“1”的位代表的CPU上去運行,。根據(jù)上面的輸出,,taskset運行之前,QEMU線程的處理器親和性mask值是0×3(其二進制值為:0011),,可知其可能會被調(diào)度到cpu0和cpu1上運行,;而運行“taskset -p 0×4 3967”命令后,,提示新的mask值被設(shè)為0×4(其二進制值為:0100),所以該進程就只能被調(diào)度到cpu2上去運行,,即通過taskset工具實現(xiàn)了vCPU進程綁定到特定的CPU上,。
上面命令行中,根據(jù)ps命令可以看到QEMU的線程和進程的關(guān)系,,但如何查看vCPU與QEMU線程之間的關(guān)系呢,?可以切換(“Ctrl+Alt+2”快捷鍵)到QEMU monitor中進行查看,運行“info cpus”命令即可(還記得3.6節(jié)中運行過的“info kvm”命令吧),,其輸出結(jié)果如下:
(qemu) info cpus
* CPU #0: pc=0xffffffff810375ab thread_id=3967
CPU #1: pc=0xffffffff812b2594 thread_id=3968
從上面的輸出信息可知,,客戶機中的cpu0對應(yīng)的線程ID為3967,cpu1對應(yīng)的線程ID為3968,。另外,,“CPU #0”前面有一個星號(*),是標識cpu0是BSP(Boot Strap Processor,,系統(tǒng)最初啟動時在SMP生效前使用的CPU),。
總的來說,在KVM環(huán)境中,,一般并不推薦手動地人為設(shè)置QEMU進程的處理器親和性來綁定vCPU,,但是,在非常了解系統(tǒng)硬件架構(gòu)的基礎(chǔ)上,,根據(jù)實際應(yīng)用的需求,,是可以將其綁定到特定的CPU上去從而提高客戶機中的CPU執(zhí)行效率或者實現(xiàn)CPU資源獨享的隔離性。