1. 程式人生 > >進程分析之CPU

進程分析之CPU

發送 能力 分別是 提升 constant 網卡中斷 pen sse 結果

進程分析之CPU

本文轉載自:https://github.com/ColZer/DigAndBuried/blob/master/system/cpu.md

在《進程分析之內存》文中,對系統/進程的內存使用情況進行分析了,本文將從cpu使用情況對進程進行分析;在這之前,先針對cpu比較相關幾個概念進行介紹

CPU INFO的閱讀以及對基本概念的了解;

cpu從硬件到系統層面有三個概念:物理CPU個數、物理核數、邏輯核個數;其中物理CPU的個數即硬件層面實實在在的CPU的個數;現在CPU都為多核,那麽整個系統物理核心=物理CPU個數 × 每個CPU的核心個數;而邏輯核主要用在“超線程”的環境下,將原本的一個物理核心虛擬成多個核心,從而實現單物理核心並行的調度多個線程,如果開啟超線程,那麽總邏輯CPU數 = 物理CPU個數 × 每顆物理CPU的核數 × 超線程數;

對於一些前端機器(webserver)為了提高響應qps,會打開超線程,比如設置超線程數為2,對整體系統性能提升較為明顯;根據內部”某某次”操作,超線程的打開,可以在不影響每個情況耗時的前提下,qps一樣可以提升idle一倍以上;

cpu物理/邏輯信息數據都可以從/proc/cpuinfo中可以獲取,下面將會具體分析;/proc/cpuinfo按照邏輯核為單位,描述相應邏輯核的相關屬性,其中每個虛擬核的屬性如下所示:

processor   : 0   //虛擬核編號,可用grep "processor"| wc -l獲取虛擬核個數
vendor_id : GenuineIntel //CPU廠商,這裏就表示因特爾

cpu family : 6 //廠商內部對CPU進行編號,因特爾廠商內部:“1”表示為8086和80186級芯片;“2”表示為286級芯片;“3”表示為386級芯片;“4”表示為486級芯片(SX、DX、:DX2、DX4);“5”表示為P5級芯片(經典奔騰和多能奔騰);“6”表示為P6級芯片(包括Celeron、PentiumII、PenfiumIII系列);“F”代表奔騰Ⅳ。
model : 45 //family下面更細粒度的標識
model name : Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz // CPU名稱
stepping : 7 //model下面更細粒度的更新版本號,那麽完整cpu就可以通過vendor_id + cpu_family + model + stepping來唯一標識;

//關於CPU型號可以詳細參見:http://blog.sina.com.cn/s/blog_605f5b4f010180st.html
//
cpu MHz : 2000.084
cache size : 15360 KB
physical id : 0 //物理CPU編號,可用grep "physical id"| sort| uniq| wc -l獲取物理CPU個數
siblings : 6 //當前物理CPU內部虛擬核心的個數,等於"超線程" × "cpu_cores"個數
core id : 0 //當前物理CPU( physical id)內部的核編號,
cpu cores : 6 //當前物理CPU內部核心個數
apicid : 0
initial apicid : 0
fpu : yes //是否具有浮點運算單元
fpu_exception : yes //是否支持浮點計算異常
cpuid level : 13
wp : yes //表明當前CPU是否在內核態支持對用戶空間的寫保護
//判斷CPU是否64位,檢查flags區段,看是否有lm標識,比如下面就為64位CPU
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 x2apic popcnt aes xsave avx lahf_lm arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid
bogomips : 4000.16 //測算CPU速度
clflush size : 64 //每次刷新緩存的大小單位
cache_alignment : 64 //緩存地址對齊單位
address sizes : 46 bits physical, 48 bits virtual //可訪問地址空間位數
power management:

相關命令有:

# 總核數 = 物理CPU個數 × 每顆物理CPU的核數
# 總邏輯CPU數 = 物理CPU個數 × 每顆物理CPU的核數 × 超線程數
# 查看物理CPU個數
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每個物理CPU中core的個數(即核數)
cat /proc/cpuinfo|
grep "cpu cores"| uniq
# 查看邏輯CPU的個數
cat /proc/cpuinfo| grep "processor"| wc -l
# 判斷每個核是否啟用超核
判斷每個物理核的siblings/cpu cores是否一致;如果不一致,判斷虛擬核的core id是否一致;

CPU親和力(CPU affinity)

“CPU affinity”中文喚作“CPU親和力”,是指在CMP架構(指在一個計算機上匯集了一組處理器,各CPU之間共享內存子系統以及總線結構)下,能夠將一個或多個進程綁定到一個或多個處理器上運行。而負責多CPU之間的均衡是由SMP來完成,它基於進程數來調整每個CPU使用比例:每個cpu都有一個可執行進程隊列,只有當其中一個CPU的可執行隊列裏進程數比其他CPU隊列進程數多25%時,才會將進程移動到另外空閑cpu上,這也是為什麽我們經常看到某個CPU0的負載要比其他CPU高很多,但是會在25%以內;

但是基於進程數的調度存在很大的問題,因為進程會因為功能的不同,會對CPU有不同的依賴;假設四種耗費cpu的進程都在一臺機器,(1)網卡中斷(2)1個處理網絡收發包進程(3)耗費cpu的n個worker進程(4)其他不太耗費cpu的進程;

此時(1)(2)大部分時間會出現在cpu0上,(3)的n個進程會隨著調度,平均到其他多個cpu上,(4)裏的進程也是隨著調度分配到各個cpu上;當發生網卡中斷的時候,cpu被打斷了,那麽分配到cpu0上的worker進程會因為中斷的處理而得不到正常運行(時間片被剝奪);最惡劣惡劣的情況是:(1)(2)(3)的進程全部分配到cpu0上,其他不太耗費cpu的進程數很多,全部分配到cpu1,cpu2,cpu3上,此時如果網卡發送一次,業務就掛了!

如果對系統的自動調度能力的不信任,我們可以手動的去調度,即設置進程的cpu-affinity,將進程的運行時間片綁定在特定的CPU上;

設置CPU-affinity有多種方法,其一就是通過設置taskset命令針對當前正在運行的pid進行設置,生效後在任務的下一次調度,將會將其進程調度到指定的CPU中

//實例
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ P COMMAND
20811 work 20 0 18.5g 1.7g 7048 S 8.0 3.6 3245:33 0 scribed-lighty
//當前P=0,表示使用的CPU-0
//taskset -cp 10,11 20811
20811 work 20 0 18.5g 1.7g 7048 S 11.3 3.6 3246:24 10 scribed-lighty
//設置pid在10,11兩個cpu上進行調度,此時p=10了

方法二是在進程代碼中,通過sched_setaffinity函數設置進程使用的cpu;

註意CPU-affinity的設置是可繼承的,即所有的子進程都擁有父進程的設置

內核態,用戶態,進程切換

在系統中,為了限制不同程序的訪問能力,防止非法獲取其他程序的數據或外部設備的數據,CPU將指令等級分為內核態和用戶態,其中內核態的指令可以訪問內存所有數據以及外圍設備如網卡;而用戶態的指令只能訪問自己的進程空間內存,且不允許直接訪問外圍設備,占用CPU的資源可以被剝削;用戶態和內核態的指令分別占用不同的CPU,即top命令上面的”%us和%sy”;

如果用戶的程序需要訪問內核的資源(寫文件,訪問網絡等),可以通過“系統調用”來完成一次用戶態與用戶態之間的切換,即執行一次陷阱指令,主要的工作流程如下:

  1. 用戶態程序將一些數據值放在寄存器中,或使用參數創建一個堆棧,以此表明需要系統提供的服務;
  2. 用戶態程序執行陷阱指令CPU切換到內核態,並跳到位於內存指定位置的指令,其中陷阱指令是系統的一部分, 他們具有內存保護,不可被用戶態程序訪問,他們會讀取程序放入內存的數據參數,並執行程序請求的服務;
  3. 系統調用完成後, 操作系統會重置CPU為用戶態並返回系統調用的結果

系統調用完成內核態與用戶態之間的切換,相比用戶態指令的執行,它是有一部的性能開銷(包括:寄存器值的設置,以及指令切換);但是相比CPU進程調度過程中的進程上下文切換,這裏系統調用的切換性能開銷還是要小很多,下面就來分析一下進程的上下文切換;

主流的CPU在同一時間內只能運行一個線程,當一個進程用完時間片或者被更高優先級的進程搶占後,它會備份到CPU的任務隊列中,同時調度其他待運行進程在CPU上運行。這裏有兩個概念:

  1. 任務隊列:每個CPU都會維持一個任務隊列,調度器會不停地根據進程的優先級從隊列中調度出新進程給CPU進行運行,並將當前正在運行的進程放到任務隊列中;任務隊列中任務的多少反映出現目前CPU的負載情況;
  2. 進程切換:目前整個進程切換過程是通過中斷技術來實現,即當CPU調度器獲得了待運行進程的控制塊後,立即用軟中斷指令來中止當前進程的運行,並保存當前進程的PC值和PSW值。其後使用壓棧指令把CPU其他寄存器的值壓入進程私有堆棧。然後再從待運行進程的進程控制塊中取出私有堆棧指針的值並存入處理器的寄存器SP,至此SP就指向了待運行進程的私有堆棧,於是下面就自待運行進程的私有堆棧中彈出上下文進人處理器。最後,利用中斷返回指令來實現自待運行進程的私有堆棧中彈出PSW值和PC值,從而完成整個切換。

線程以及輕量進程的實現

“進程是資源的管理單位,線程是計算的調度單位”這句話是常識,但是在linux中,線程這個概念卻是依賴LWD(輕量進程)來實現;

線程從類型與實現來說,可以分為三種類型:

  1. 內核線程:只運行在內核態,不受用戶態上下文的拖累。
  2. 用戶線程:完全建立在用戶空間的線程庫,用戶線程的創建、調度、同步和銷毀全部都是在用戶空間完成,不需要內核的參與。這種線程是極其低消耗和高效的。但是這種多個線程在內核中只對應一個調度對象(內核線程),此時如果一個線程的堵塞在系統調度上,勢必會導致整個進程的堵塞,這也是用戶線程的缺點;
  3. 輕量級進程:是目前linux線程的實現方式;它建立在內核之上並由內核支持,每一個輕量級進程都與一個調度對象(內核線程)關聯(因此linux創建一個線程(輕量級進程),需要經過一次系統調用,在內核中創建相對應的內核線程,相比用戶線程,開銷較大,同時受限內核線程數目,整個系統的線程數也是有限制的);輕量級進程由clone()系統調用創建,即與父進程是共享進程地址空間和系統資源,即它的創建只需要一個最小的執行上下文,相比創建普通進程,開銷還是要小很多很多!

實踐:在linux中獲取指定進程的線程

方法一:ls /proc/PID/task | wc -l

//task目錄下面有該PID進程下所有的線程號或輕量進程號的目錄,每個linux進程默認有一個線程,所以該目錄下必然有一個PID目錄,每個LWP目錄下面有該輕量進程的相關數據,由於多個輕量進程之間共享了進程地址空間和系統資源,所以LWD目錄下面的數據大部分是一樣的;

方法二:ps -efL

ps的-f參數解析:does full-format listing. When used with -L, the NLWP (number of threads) and LWP (thread ID) columns will be added.
-f參數將會完整的顯示進程信息,比如PPID(父進程ID),C(CPU使用率)等,如果-L配合,將會線程每個PID的輕量進程數目和每個輕量進程號

    ps -efL |grep scribed-lighty
UID PID PPID LWP C NLWP STIME TTY TIME CMD
work 20811 1 20811 5 86 Nov13 ? 1-01:37:21 ./scribed-lighty
work 20811 1 21074 0 86 Nov13 ? 00:00:00 ./scribed-lighty
work 20811 1 21075 0 86 Nov13 ? 00:00:00 ./scribed-lighty
work 20811 1 21076 0 86 Nov13 ? 00:52:47 ./scribed-lighty

我們看到主進程20811,它的LWP=20811,CPU總計開銷C=5%,輕量進程數目NLWP=86個;而LWP=21074,它的歸屬的進程PID=20811;

方法三:pstree -p PID
會將線程以進程樹的方式展現出來

進程優先級PR,NICE值,nice%比例

在系統中通過top等命令可以看到每個進程的優先級PR,NICE值,nice%;它們都是和進程的優先級有關;

其中PR是進程的優先級,值越小進程的優先級越高。NICE值表示優先級的修正值,值大小位於[-20,+19]值,正值表示低優先級,負值表示高優先級,值為零則表示不調整該進程PR,即PR(new)=PR(old)+nice,系統默認PR=20,那麽最終的PR位於[0,39]之間,進程切換時候根據進程之間的PR值來調度CPU的資源。但是註意:PR值只會影響用戶態的CPU時間片,內核態不受PR值影響

假設一次調度中,有2個runnable的進程A和B,假設nice都為0時,內核會依次給每個進程分配1k個CPU時間片用於計算用戶態指令。如果此時nice_A=0,nice_B=-10,此時CPU可能分別給A和B分配1k和1.5k的時間片用於計算用戶態指令,即本次調度過程中,因為改變過優先級的進程多占用了0.5k個時間片。

此時nice%,就是來度量這種因為改變優先級的進程的占用CPU用戶態的比例,即0.5k/總的調度時間;而user%為1k/總的調度時間;那麽進程A在一次調度過程占用的用戶態時間片比例為(0.5k+ 1k)/總的調度時間;

nice值為負數,優先級高了,可以多獲取nice%的時間片;如果nice值為0或者為正值,進程被調度的優先級降低,此時nice%=0,但是在在總的調度時間不變的情況,優先級高的進程會多占時間皮,那麽低優先級進程的user%就會減少;所以整個系統用於用戶態的CPU比例應該是所有進程的user%+nice%

進程的nice值是可以被修改的,修改命令分別是nice和renice。

  1. nice命令:nice –n adjustment command,如果這裏不指定adjustment,則默認為10。
  2. renice命令:動態修改一個pid的nice值。

系統以及進程的CPU耗時的了解

CPU是資源,進程或者系統的CPU的耗時是指CPU這個資源被占用的時間,如果拿這個時間與統計時間進行相除法,就可以得到CPU這段時間的占用比例;

CPU的使用耗時根據用處不同,分為:

  1. user:正常優先級下用戶態的耗時,用於進程的用戶態的指令計算
  2. nice:因為改變優先級而多獲取的用戶態CPU的耗時;
  3. sys:內核態的計算占用的CPU耗時
  4. iowait:內核態CPU等待IO完成的CPU耗時
  5. steal:管理程序維護另一個虛擬處理器時,虛擬CPU的無意識等待時間百分比
  6. irq:硬中斷造成的CPU耗時
  7. softirq:軟中斷造成的CPU耗時
  8. idle:CPU空閑時間百分比。
  9. guest:忽略

在一個調度過程中,CPU總時間=user+nice+sys+iowait+idle+steal+irq+softirq+guest;對於一個進程來說只有user+nice+sys是這段時間內真正被用於CPU計算,TOP等程序也是利用這個值來計算進程占用的CPU比例;

關於IOwait,“內核態CPU等待IO完成的CPU耗時”這句話對iowait的理解還是很字面,後面分析IO會詳細分析,這裏簡單字面進行分析;

磁盤作為外部設備,它是有一定的IOPS和帶寬/吞吐量的限制;如果在有大量的磁盤讀寫的時候,IO就出現的瓶頸,IO服務時間(包括IO等待)變長,此時如果系統空閑idle資源沒有其他可運行進程進行調度,那麽就相當於有一部分CPU資源在“等待”這部分IO進程,換句話說(iowait+idle的時間都是CPU空閑的時間,只是idle是真的空閑,iowait是有進程在等待io而浪費的CPU空閑時間);

IO壓力和IOwait其實沒有必然關系,因為它有一個條件就是當前“沒有其他可運行進程進行調度”,在IO壓力很大的時候,IOwait可能很小,對IO的分析參考下一節,比如通過iostat進行分析;

關於Irix/Solaris mode

使用TOP等命令來查看每個進程的CPU耗時情況有兩種模式Irix或Solaris;其中Irix on下,它統計在單個CPU的時間片(A)下,進程的user+sys的耗時比例:(use+sys)/A;一個進程同時可能有多個線程在這個時間片下占用多個CPU,那麽(use+sys)/A 是可以大於100%的;而Solaris模式下(Irix off),A=A×CPU個數,從而獲取進程占整個系統的CPU資源比例

TOP命令看到的%CPU默認就是Irix模式(Irix on)下的值,而ps -ef看到的%C就是Solaris模式下的值

/proc/stat的閱讀

結合上面的知識,基本可以讀懂/proc/stat這個文件

bash-3.00$ cat /proc/stat
//每個CPU和總CPU用戶每一中計算的的時間片(Jiffies)
//Jiffies代表時間。它的單位隨硬件平臺的不同而不同。系統裏定義了一個常數HZ,代表每秒種最小時間間隔的數目。這樣jiffies的單位就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間間隔了。每個CPU時間片,Jiffies都要加1。CPU的利用率就是用執行用戶態+系統態的Jiffies除以總的Jifffies來表示。
//CPU user nice sys idel iowait irq softirq stealstolen guest
cpu 3579507973 923662884 1182115010 24742042424 1505324647 0 62267429 0 0
cpu0 533687044 21915510 297115205 1366731979 388976289 0 57810083 0 0
cpu1 309042325 31702797 115875683 1954446024 254394961 0 776714 0 0
cpu2 176634066 33605831 74702888 2197816335 183274070 0 223664 0 0
cpu3 112741411 30599346 43952679 2368466561 110427395 0 74039 0 0
cpu4 82092567 29779396 27776205 2459019904 67567418 0 31883 0 0
cpu5 66798645 29049156 20332706 2506608334 43466448 0 16243 0 0
cpu6 662220781 120553155 194181592 1423472844 263172456 0 2635018 0 0
cpu7 481430180 120766223 125531895 1845550767 92661358 0 289551 0 0
cpu8 369107111 121663571 86258341 2037860980 51238138 0 102709 0 0
cpu9 308882461 123089686 68038799 2139948304 26220899 0 51682 0 0
cpu10 236282379 135555206 52318720 2227790503 14142008 0 141788 0 0
cpu11 240588998 125383002 76030291 2214329883 9783202 0 114051 0 0
//一堆,每一個代表一個CPU每個中斷號出現的次數
intr 55570678587 186 0 0 0 128 0 0 0 0 0 88903 0 0 0 0 0 2136397299 0 0 0 0
ctxt 1021520446354 // 進程上下文切換的次數
btime 1422384167 //系統總共的啟動時間
processes 6951204073 // 系統總共運行的進程數
procs_running 14 // 當前運行隊列的任務的數目。
procs_blocked 0 // 前被阻塞的任務的數目。
//每一個軟中斷出現次數
softirq 72347376064 0 2169065483 5827202 202608370 1870099913 0

進程分析之CPU