(1)Linux效能調優之Linux程式管理

小蟲說發表於2018-12-22

一、背景

玩過一段時間Linux後,大家是否發現Linux也就是一個作業系統而已,和windows是類似的,只不過是windows擁有圖形化介面,而Linux大部分情況下只是個黑黑的視窗。windows多用於個人,而Linux因為出色的效能而多用於伺服器。

伺服器不是智慧的,Linux也不是智慧的,所以需要運維人員通過除錯,將伺服器的硬體和Linux的特性結合起來,從而達到效能最大輸出,這就是我理解的Linux效能調優。下面展示的是一張伺服器和Linux系統結合的概覽圖:

伺服器和Linux系統結合的概覽圖

二、基礎知識

調優不是說調就調的,我們要先了解Linux是如何處理任務以及與硬體資源進行互動的。效能調優需要在深刻理解硬體資源、作業系統和應用程式的基礎上進行。下面說一下在Linux中和效能關係最密切的幾個部分。

三、Linux程式管理

  • 3.1 什麼是程式?

程式是在處理器中執行的例項,Linux核心排程各類資源來滿足程式的需求。我的理解是,應用程式是儲存硬碟上的資料,通過程式入口將應用程式裝載到記憶體執行起來後就生成了一個程式(執行中的程式)。程式會使用各種資源,如CPU,鍵盤滑鼠,處理器,硬碟等,而這些工作由Linux核心排程,以滿足各個程式間的協同,競爭等。

Linux的程式管理方式類似於Unix的程式管理方式,包含程式排程、中斷處理、訊號、程式優先順序、程式切換、程式狀態、程式的記憶體等等

所有執行在Linux作業系統的程式都被task_struct這個結構體管理,task_struct也被稱為程式描述符。程式描述符包含一個程式執行所需的所有資訊,比如程式的id、程式的屬性以及構建程式的資源。

下圖展示了程式資訊相關結構的概覽:

(1)Linux效能調優之Linux程式管理

  • 3.2 程式的生命週期

每個程式都有自己生命週期,比如建立、執行、終止和刪除。在系統執行過程中,這些階段反覆執行成千上萬次。因此,從效能的角度來看,程式的生命週期十分重要。

下圖展示了一般程式的生命週期:

(1)Linux效能調優之Linux程式管理

當一個程式建立一個新的程式,建立程式的程式(父程式)使用名為fork()的系統呼叫。當fork()被呼叫的時候,它會為新建立的程式(子程式)獲得一個程式描述符,並且設定新的程式ID。複製父程式的程式描述符給子程式。這時候,不會複製父程式的地址空間,而是父子程式使用同樣的地址空間。

exec()系統呼叫把新程式複製到子程式的地址空間。由於共享同樣的地址空間,寫入新程式的資料會引發頁錯誤的異常。此時,核心給子程式分配新的物理頁。這個延遲的操作叫做Copy On Write。子程式和父程式執行的程式通常不一樣,它執行自己的程式。這個操作避免了不必要的開銷,因為,複製整個地址空間是很慢且低效率的,還會消耗很多的處理器時間和資源。

當程式執行完成,子程式使用exit()系統呼叫終止。exit()會釋放程式的大部分資料結構,並且把這個終止的訊息通知給父程式。這時候,子程式被稱為zombie process(殭屍程式)。直到父程式通過wait()系統呼叫知悉子程式終止之前,子程式都不會被完全的清除。一旦父程式知道子程式終止,它會清除子程式的所有資料結構和程式描述符。

  • 3.3 執行緒

說到程式不得不提的就是執行緒了。執行緒是單個程式中生成的執行單元。多個執行緒在同一個程式中併發執行。它們共享記憶體、地址空間、開啟檔案等等資源。還能訪問同樣的應用資料集。執行緒也被稱為輕量級程式(Light Weight Process)。由於執行緒間共享資源,執行緒不能同時改變它們共享的資源。互斥、鎖、序列化等等都是由使用者應用程式來實現。

從效能的角度看,建立執行緒比建立程式更加低消耗,因為建立執行緒不需要複製資源。另一方面,從程式和執行緒在排程上看,他們擁有相似的行為。核心用類似的方法來處理他們。

下圖是程式和執行緒的簡單對比:

(1)Linux效能調優之Linux程式管理

在當前的Linux實現中,執行緒由POSIX(Portable Operating System Interface for UNIX,可移 植作業系統介面)的相容庫(pthread)提供。Linux支援多執行緒。

  • 3.4 程式優先順序和nice級別

程式優先順序由動態優先順序和靜態優先順序決定,它是決定程式在CPU中執行順序的數字。優先順序越高的程式被處理器執行的機會越大。

根據程式的行為,核心使用啟發式演算法決定開啟或關閉動態優先順序。可以通過nice級別直接修改程式的靜態優先順序,擁有越高靜態優先順序的程式會獲得更長的時間片(時間片是程式在處理器中的執行時間)。

Linux支援的nice級別從19(最低優先順序)到-20(最高優先順序),預設只是0。只有root身份的使用者才能把程式的nice級別調整為負數(讓其具備較高優先順序)。

  • 3.5 切換上下文

在程式執行過程中,程式的資訊存放在處理器的暫存器和快取中。這部分執行中程式存放在暫存器中的資料就叫做context,上下文。在切換程式中,正在處理的程式上下文被儲存起來,把下一個要執行的程式的上下文恢復到暫存器。上下文通常儲存在程式描述符和核心態棧中。程式切換就叫做上下文切換(context switching)。因為處理器每次上下文切換都要為新程式重新整理暫存器和快取,可能引發效能上的問題,所以應該儘量避免太多的上下文切換。 下圖描述上下文切換是如何工作的:

(1)Linux效能調優之Linux程式管理

  • 3.6 中斷處理

中斷處理是最高優先順序別的任務之一。中斷通常由I/O裝置產生,譬如網路介面、鍵盤、磁碟控制器。中斷處理器把鍵盤輸入、網路幀到達這類事件通知給核心,它告訴核心儘快中斷程式執行,因為某些裝置需要快速的迴應。這對系統穩定性是一個挑戰。當中斷訊號到達核心,核心必須切換當前執行中的程式到新的程式,處理中斷。這就意味著會發生上下文切換,同時也意味著大量的的中斷會導致系統效能下降。

在Linux中有兩類中斷,硬中斷是由裝置產生的需要做出響應的中斷,例如磁碟I/O中斷,網路卡中斷,鍵盤和滑鼠中斷。軟中斷用於任務處理,可以推遲,例如TCP/IP操作、SCSI協議操作。可以在/proc/interrupts中看到相關的硬中斷資訊。

  • 3.7 程式狀態

TASK_RUNNING 在這個狀態中,程式正在CPU中執行,或者在執行佇列(run queue)中等待執行。

TASK_STOPPED 程式由於特定的訊號(如SIGINT、SIGSTOP)而掛起就會處於這個狀態,等待恢復訊號,比如SINCONT。

TASK_INTERRUPTIBLE 在此狀態中,程式掛起並且等待一個特定的條件。假如程式處於TASK_INTERRUPTIBLE狀態並且收到一個停止訊號,程式狀態會發生改變,操作會中斷。TASK_INTERRUPTIBLE的典型例子是等待鍵盤中斷。

TASK_UNINTERRUPTIBLE 類似於TASK_INTERRUPTIBLE。當程式處於TASK_INTERRUPTIBLE狀態可以被中斷,傳送一個訊號給TASK_UNINTERRUPTIBLE卻不會有任何反應。TASK_UNINTERRUPTIBLE最典型的例子是程式等待磁碟I/O操作。

TASK_ZOMBIE 程式在使用exit()系統呼叫退出以後,父程式應該知道程式終結。在TASK_ZOMBIE狀態中,程式在等待父程式收到通知並釋放所有的資料結構。

它們之間的關係如下圖:

(1)Linux效能調優之Linux程式管理

說下殭屍程式

當程式已經收到訊號而終止,正常情況下,完全結束之前,它有一些時間來完成所有的任務(例如關閉開啟的檔案)。在這個很短的的正常的時間片裡,這個程式是殭屍。

當程式完成了所有的關閉操作, 它向父程式報告它即將終結。有時候,殭屍程式不能夠結束它自己,這個狀態下,它就顯示狀態為Z(zombie)。

因為它已經死了,所以不可能使用kill命令殺死這種程式。如果無法擺脫殭屍程式,可以殺死殭屍程式的父程式,這樣殭屍程式也會消失。然後,如果殭屍程式的父程式是init,你就別這麼做了,init是非常重要的程式,你可能要重啟才能擺脫殭屍程式了。

  • 3.8 程式記憶體段

程式使用自己的記憶體區域處理任務,任務種類由場景和程式用途決定。程式有不同的工作特性和不同資料大小要求,程式必須處理各種大小的資料。為滿足這一要求,Linux核心給各個程式使用動態記憶體分配機制。程式記憶體分配結構如下圖:

(1)Linux效能調優之Linux程式管理

程式記憶體區域包含如下段:

文字 :儲存可執行程式碼的區域
資料 : 資料段由如下三個區域構成
    Data:儲存初始化資料,比如靜態變數
    BSS:儲存初始化0資料,資料初始化為0
    Heap(堆):根據需要使用malloc()分配動態記憶體。堆向高地址空間增長。
棧: 該區域儲存區域性變數、函式引數和函式的返回地址。棧向低地址空間增長。
複製程式碼

使用者程式的地址空間分配可以使用pmap命令顯示出來。你可以使用ps命令顯示總的段大小。

  • 3.9 Linux的CPU排程

計算機的最基本功能就是計算,為了實現計算功能,必須要有辦法管理計算資源、處理器、計算任務,也就是常說的程式和執行緒。

下面談下Linux中CPU呼叫程式的演算法,這個演算法使用兩個程式優先順序陣列:

1.active
2.expired
複製程式碼

由於排程器根據程式的優先順序和先前阻塞率來位分配時間片,程式的優先順序被放在一個active陣列中。當時間片到期,它們被重新分配新的時間片,並且放置到expired陣列上。當所有active陣列上的程式都到期,active和expired陣列發生對換,重啟演算法。對於一般的互動程式(對應實時程式),高優先順序的程式通常會比低優先順序程式分配更多的時間片,但是並不意味著完全不給低優先順序程式機會。

如下展示和Linux的CPU排程器是如何工作的:

(1)Linux效能調優之Linux程式管理

新排程器的另外一個大有有點是支援非均勻記憶體架構(Non-Uniform Memory Architecture, NUMA)和對稱多執行緒處理器,例如Intel的超執行緒技術。支援NUMA保證了正常情況下不會出現負載均衡的情況,除非一個節點負擔過重。這個機制保證了在NUMA系統中,比較緩慢的鏈路負載較小。儘管在一個組中的處理器排程的每一個處理,會被負載均衡,但是排程器的組只會在節點負載過高和要求負載均衡的時候產生。

(1)Linux效能調優之Linux程式管理

四、下一節是???

談完Linux的程式管理,下一節談一下 Linux的記憶體體系

相關文章