標籤:linux 效能調優 進程管理
本文為IBM RedBook的Linux Performanceand Tuning Guidelines的1.1節的翻譯
原文地址:http://www.redbooks.ibm.com/redpapers/pdfs/redp4285.pdf
原文Eduardo Ciliendo, Takechika Kunimasa, Byron Braswell
譯文如下:
1.1 Linux進程管理進程管理是作業系統的最重要的功能之一。有效率的進程管理能保證一個程式平穩而高效地運行。
Linux的進程管理與UNIX的進程管理相似。它包括進程調度、中斷處理、訊號、進程優先順序、環境切換、進程狀態、進度記憶體等。
在本節中,我們將描述Linux進程管理的基本原理的實現。它將更好地協助你理解Linux核心如何處理進程及其對系統效能的影響。
1.1.1 什麼是進程?
一個進程是一個運行在處理器的程式的一個執行個體。該進程使用Linux核心能夠處理的任何資源來完成它的任務。
所有運行在Linux作業系統中的進程都被task_struct結構管理,該結構同時被叫作進程描述。一個進程描述包含一個運行進程所有的必要資訊,例如進程標識、進程屬性和構建進程的資源。如果你瞭解該進程構造,你就能理解對於進程的運行和效能來說,什麼是重要的。圖1-2展示了進程結構相關的進程資訊概述。
圖1-2 task_struct結構體
1.1.2 進程的生命週期
每一個進程都有其生命週期,例如建立、運行、終止和消除。這些階段會在系統啟動和運行中重複無數次。因此,進程的生命週期對於其效能的分析是非常重要的。
圖1-3展示了經典的進程生命週期。
圖1-3 經典的進程生命週期
當一個進程建立一個新的進程,進程的建立進程(父進程)調用一個fork()系統調用。當fork()系統調用被調用,它得到該新建立進程(子進程)的進程描述並調用一個新的進程id。它複製該值到父進程進程描述到子進程中。此時整個的父進程的地址空間是沒有被複製的;父子進程共用相同的地址空間。
exec()系統調用複製新的程式到子進程的地址空間。因為父子進程共用地址空間,寫入一個新的程式的資料會引起一個分頁錯誤。在這種情況下,記憶體會分配新的實體記憶體頁給子進程。
這個延遲的操作叫作寫時複製。子進程通常運行他們自己的程式而不是與父進程運行相同的程式。這個操作避免了不必要的開銷,因為複製整個地址空間是一個非常緩慢和效率低下的操作,它需要使用大量的處理器時間和資源。
當程式已經執行完成,子進程通過調用exit()系統調用終止。exit()系統調用釋放進程大部分的資料並通過發送一個訊號通知其父進程。此時,子進程是一個被叫作殭屍進程的進程(參閱page 7的“Zombie processes”)。
子進程不會被完全移除直到其父進程知道其子進程的調用wait()系統調用而終止。當父進程被通知子進程終止,它移除子進程的所有資料結構並釋放它的進程描述。
1.1.3 線程
一個線程是一個單獨的進程產生的一個執行單元。它與其他的線程並行地運行在同一個進程中。各個線程可以共用進程的資源,例如記憶體、地址空間、開啟的檔案等等。它們能訪問相同的程式資料集。線程也被叫作輕量級的進程(Light Weight Process,LWP)。因為它們共用資源,所以每個線程不應該在同一時間改變它們共用的資源。互斥的實現、鎖、序列化等是使用者程式的責任。
從效能的角度來說,建立線程的開銷比建立進程少,因數建立一個線程時不需要複製資源。另一方面,進程和線程擁在調度演算法上有相似的特性。核心以相似的方式處理它們。
圖1-4 進程和線程
在現在的Linux實現中,線程支援UNIX的可移植作業系統介面(POSIX)標準庫。在Linux作業系統中有幾種可用的線程實現。以下是廣泛使用的線程庫:
LinuxThreads
LinuxThreads自從Linux核心2.0起就已經被作為預設的線程實現。LinuxThreads的一些實現並不符合POSIX標準。Native POSIX Thread Library(NPTL)正在取代LinuxThreads。LinuxThreads在將來的Linux企業發行版中將不被支援。
Native POSIX Thread Libary(NPTL)
NPTL最初是由紅帽公司開發的。NPTL與POSIX更加相容。通過Linux核心2.6的進階特性,例如,新的clone()系統調用、訊號處理的實現等等,它具有比LinuxThreads更高的效能和伸縮性。
NPTL與LinuxThreads有一些不相容。一個依賴於LinuxThreads的應用可能不能在NPTL實現中工作。
Next Generation POSIX Thread(NGPT)
NGPT是一個IBM開發的POSIX線程庫。現在處於維護階段並且在未來也沒有開發計劃。
使用LD_ASSUME_KERNEL環境變數,你可以選擇在應用中使用哪一個線程庫。
1.1.4 進程優先順序和nice值
進程優先順序是一個數值,它通過動態優先順序和靜態優先順序來決定進程被CPU處理的順序。一個擁有更高進程優先順序的進程擁有更大的機率得到處理器的處理。
核心根據進程的行為和特性使用試探演算法,動態地調整調高或調低動態優先順序。一個使用者進程可以通過使用進程的nice值間接改變靜態優先順序。一個擁有更高靜態優先順序的進程將會擁有更長的時間片(進程能在處理上運行多長時間)。
Linux支援從19(最低優先順序)到-20(最高優先順序)的nice值。預設值為0。把程式的nice值修改為負數(使進程的優先順序更高),需要以root身份登陸或使用su命令以root身份執行。
1.1.5 環境切換
在進程運行過程中,進程的運行資訊被儲存於處理器的寄存器和它的緩衝中。正在執行的進程載入到寄存器中的資料集被稱為上下文。為了切換進程,運行中進程的上下文將會被儲存,接下來的運行進程的上下文將被被恢複到寄存器中。進程描述和核心模式堆棧的地區將會用來儲存上下文。這個切換被稱為環境切換。過多的環境切換是不受歡迎的,因為處理器每次都必須清空重新整理寄存器和緩衝,為新的進程製造空間。它可能會引起效能問題。
圖1-5 說明了環境切換如何工作。
圖1-5 環境切換
1.1.6 中斷處理
中斷處理是優先順序最高的任務之一。中斷通常由I/O裝置產生,例如網路介面卡、鍵盤、磁碟控制卡、串列適配器等等。中斷處理器通過一個事件通知核心(例如,鍵盤輸入、乙太網路幀到達等等)。它讓核心中斷進程的執行,並儘可能快地執行中斷處理,因為一些裝置需要快速的響應。它是系統穩定的關鍵。當一個中斷訊號到達核心,核心必須切換當前的進程到一個新的中斷處理進程。這意味著中斷引起了環境切換,因此大量的中斷將會引起效能的下降。
在Linux的實現中,有兩種類型的中斷。硬中斷是由請求響應的裝置發出的(磁碟I/O中斷、網路介面卡中斷、鍵盤中斷、滑鼠中斷)。非強制中斷被用於處理可以延遲的任務(TCP/IP操作,SCSI協議操作等等)。你可以在/proc/interrupts檔案中查看硬中斷的相關資訊。
在多處理器的環境中,中斷被每一個處理器處理。綁定中斷到單個的物理處理中能提高系統的效能。更多的細節,請參閱4.4.2,“CPU的中斷處理親和力”。
1.1.7 進程狀態
每一個進程擁有自己的狀態,狀態表示了進程當前在發生什麼。
在進程的執行期間進程的狀態會發生改變。一些進程的狀態如下:
TASK_RUNNING
在此狀態下,表示進程正在CPU中運行或在隊列中等待運行(運行隊列)。
TASK_STOPPED
在此狀態下的進程被某些訊號(如SIGINT,SIGSTOP)暫停。進程正在等待通過一個訊號恢複運行,例如SIGCONT。
TASK_INTERRUPTIBLE
在此狀態下,進程被暫停並等待一個某些條件狀態的到達。如果一個進程處於TASK_INTERRUPTIBLE狀態並接收到一個停止的訊號,進程的狀態將會被改變並中斷操作。一個典型的TASK_INTERRUPTIBLE狀態的進程的例子是一個進程等待鍵盤中斷。
TASK_UNINTERRUPTIBLE
與TASK_INTERRUPTIBLE相似。當一個進程處於?TASK_UNINTERRUPTIBLE狀態可以被中斷,向處於TASK_UNINTERRUPTIBLE狀態的進程發送一個訊號不會發生任何操作。一個TASK_UNINTERRUPTIBLE進程的典型的例子是等待磁碟I/O操作。
TASK_ZOMBIE
當一個進程調用exit()系統調用退出後,它的父進程應該知道該進程的終止。處於TASK_ZOMBIE狀態的進程會等待其父進程通知其釋放所有的資料結構。
圖1-6 進程狀態
殭屍進程
當一個進程接收到一個訊號而終止,它在結束自己之前,通常需要一些時間來結束所有的任務(例如關閉開啟的檔案)。在這個通常非常短暫的時間內,該進程就是一個殭屍進程。
進程已經完成所有的關閉任務後,它會向父進程報告其即將終止。有些時候,一個殭屍進程不能把自己終止,這將會引導它的狀態顯示為z(zombie)。
使用kill命令來關閉這樣的一個進程是不可能的,因為該進程已經被認為已經死掉了。如果你不能清除殭屍進程,你可以結束其父進程,然後殭屍進程也隨之消失。但是,如果父進程為init進程,你不能結束它。init進程是一個非常重要的進程,因此可能需要重啟系統來清除殭屍進程。
1.1.8 進程記憶體段
進程使用其自身的記憶體地區來執行工作。工作的變化根據情況和進程的使用而決定。進程可以擁有不同的工作量特性和不同的資料大小需求。進程必須處理各種資料大小。為了滿足需求,Linux核心為每個進程使用動態申請記憶體的機制。進程記憶體配置的資料結構1-7所示。
圖1-7 進程地址空間
進程記憶體區由以下幾部分組成:
Text段
該地區用於儲存運行代碼。
Data段
資料區段包括三個地區。
- Data:該地區儲存已被初始化的資料,如靜態變數。
- BSS:該地區儲存初始化為0的資料。資料被初始化為0。
- Heap:該地區用於根據需求使用malloc()動態申請的記憶體。堆向高地址方向增長。
Stack段
該地區用於儲存局部變數、函數參數和返回函數的地址。棧向低地址方向增長。
使用者進程的地址空間記憶體分布可以使用pmap命令來查看。你可以使用ps命令來查看記憶體段的大小。可以參閱2.3.10的“pmap”,“ps和pstree”。
1.1.9 Linux CPU調度
任何的電腦的準系統都非常簡單,就是計算。為了能夠計算,它意味著必須管理計算資源或處理器和計算任務,也就是我們所知道的線程或進程。感謝Ingo Molnar的巨大貢獻,Linux核心使用一個O(1)的演算法代替以前的O(n)的CPU調度演算法。O(1)指的是一種靜態演算法,意味著選擇一個進程並執行所花費的時間是一個常數,不管進程的數量的大小。
新的調度演算法的擴充性非常好,不管進程的數量或者處理器的數量是多少,系統的開銷都是非常少的。該演算法使用兩個進程優先順序數組:
active(活動的)
expired(到期的)
調度器根據進程的優先順序和優先攔截率為進程分配時間片,然後進程以優先順序順序放置到active數組內。當進程時間片耗盡,進程申請一個新的時間片並放置到expired數組內。當active數組中的所有進程的時間片耗盡,這兩個數組進行切換,重新運行該演算法。對於一般的互動式進程(相對於即時進程),擁有高優先順序的進程通常比低優先順序的進程得到更長的時間片和更多的計算時間,但這並不表示低優先順序的進程會被完全忽略(餓死)。該演算法的優勢是為擁有大量線程和進程並擁有多處理器的企業級環境提升Linux核心的擴充性。該O(1)的新CPU調度器是為記憶體2.6設計的,但是現在已經移植到2.4系列中。圖1-8說明了Linux CPU如何調度工作。
圖1-8 Linux核心2.6 O(1)調度器
新調度器的另一個顯著改進是支援非一致性記憶體架構(NUMA)和對稱多執行緒器,例如Intel超執行緒技術。
改進後的NUMA支援確保只有某個節點過載時,Server Load Balancer才會跨越某個NUMA節點。這個機制確保了在NUMA系統相對比較緩慢的擴充連結流量的最小化。儘管每個調度節拍時Server Load Balancer會遍曆調度域群組中的處理器,但只有在節點過載並請求Server Load Balancer時,負載才會跨越調度域轉移。
圖1-9 O(1)CPU調度器結構
Linux效能及調優指南(翻譯)之Linux進程管理