linux調度全景指南

 

 

 

 

| 導語 本文主要是講Linux的調度系統, 因爲所有內容太多,分三部分來說,調度能夠說是操做系統的靈魂,爲了讓CPU資源利用最大化,Linux設計了一套很是精細的調度系統,對大多數場景都進行了不少優化,系統擴展性強,咱們能夠根據業務模型和業務場景的特色,有針對性的去進行性能優化,在保證客戶網絡帶寬前提下,隔離客戶互相之間的干擾影響,提升CPU利用率,下降單位運算成本,提升市場競爭力。歡迎你們相互交流學習!linux

 

Alex 碼農的藝術,騰訊資深工程師,騰訊雲網絡核心成員,得到過2次五星員工(鵝廠優秀員工),面試經驗豐富,熱愛技術與運動,微信搜一搜【職場重生】關注我,面試

期待與你們一塊兒學習交流,認真回答每個問題;數據庫

                              目錄編程

 

 

 

                                                                  CPU緩存

 

CPU做爲計算資源,一直是雲計算廠商比拼的核心競爭力,咱們的目標是合理安排好計算任務,充分提升CPU的利用率,預留更多空間容錯,加強系統穩定性,讓任務更快執行,下降無效功耗,節約成本,從而提升市場競爭力。性能優化

 

                               CPU 實現的抽象邏輯圖bash

  1. 首先,咱們有一個自動計數器。這個自動計數器會隨着時鐘主頻不斷地自增,來做爲咱們的 PC 寄存器;服務器

  2. 在這個自動計數器的後面,咱們連上一個譯碼器。譯碼器還要同時連着咱們經過大量的 D 觸發器組成的內存。微信

  3. 自動計數器會隨着時鐘主頻不斷自增,從譯碼器當中,找到對應的計數器所表示的內存地址,而後讀取出裏面的 CPU 指令。網絡

  4. 讀取出來的 CPU 指令會經過CPU 時鐘的控制,寫入到一個由 D 觸發器組成的寄存器,也就是指令寄存器當中。

  5. 在指令寄存器後面,咱們能夠再跟一個譯碼器。這個譯碼器的做用再也不是用於尋址,而是把拿到的指令解析成opcode 和對應的操做數。

  6. 當咱們拿到對應的 opcode 和操做數,對應的輸出線路就要鏈接 ALU,開始進行各類算術和邏輯運算。對應的計算結果,則會再寫回到 D 觸發器組成的寄存器或者內存當中。

這裏整個過程就大概是CPU的一條指令的執行過程。爲了加快CPU指令的執行速度,CPU在發展過程當中作了不少優化,例如流水線,分支預測,超標量,Hyper-threading,SIMD,多級cache,NUMA架構等,  這裏主要關注Linux的調度系統。

 

CPU上下文

Linux 是一個多任務操做系統,它支持遠大於 CPU 數量的任務同時運行。固然,這些任務實際上並非真的在同時運行,而是由於系統在很短的時間內,將 CPU 輪流分配給它們,形成多任務同時運行的錯覺。

而在每一個任務運行前,CPU 都須要知道任務從哪裏加載、又從哪裏開始運行,也就是說,須要系統事先幫它設置好 CPU 寄存器和程序計數器(Program Counter,PC)。

CPU 寄存器,是 CPU 內置的容量小、但速度極快的內存。而程序計數器,則是用來存儲 CPU 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 CPU 在運行任何任務前,必須的依賴環境,所以也被叫作 CPU 上下文(執行環境):

 

 

                             

 

而這些保存下來的上下文,會存儲在系統內核中(堆棧),並在任務從新調度執行時再次加載進來。這樣就能保證任務原來的狀態不受影響,讓任務看起來仍是連續運行。

在Linux中,內核空間和用戶空間是兩種工做模式,操做系統運行在內核空間,而用戶態應用程序運行在用戶空間,它們表明不一樣的級別,而對系統資源具備不一樣的訪問權限。

這樣代碼(指令)執行存在不一樣的CPU上下文,而進行調度的時候,要進行相應的CPU上下文切換,Linux系統存在不一樣堆棧來保存CPU上下文,系統中每一個進程都會擁有屬於本身的內核棧,而系統中每一個CPU都將爲中斷處理準備了兩個獨立的中斷棧,分別是hardirq棧和softirq棧:

 

 

Linux系統調用CPU上下文切換堆棧結構:

        

 

  • 中斷上下文:中斷代碼運行於內核空間,中斷上下文即運行中斷代碼所須要的CPU上下文環境,須要硬件傳遞過來的這些參數,內核須要保存的一些其餘環境(主要是當前被打斷執行的進程或其餘中斷環境),這些通常都保存在中斷棧中(x86是獨立的,其餘可能和內核棧共享,這和具體處理架構密切相關),在中斷結束後,進程仍然能夠從原來的狀態恢復運行。

  • 進程上下文:進程是由內核來管理和調度的,進程的切換髮生在內核態,進程的上下文不只包括了虛擬內存、棧、全局變量等用戶空間的資源,還包括了內核堆棧、寄存器等內核空間的狀態。

  • 系統調用上下文:進程能夠在內核空間和用戶空間運行,分別稱爲進程的用戶態和進程的內核態, 從用戶態到內核態的轉變須要經過系統調用來完成,須要進行CPU上下文切換,在執行系統調用時候,須要保存用戶態的CPU上下文(用戶態堆棧)到內核堆棧,而後加載內核態的CPU上下文。

  • CPU處理器總處於如下狀態中的一種:
    1、內核態,運行於進程上下文,內核表明進程運行於內核空間;
    2、內核態,運行於中斷上下文,內核表明硬件運行於內核空間;
    3、用戶態,運行於用戶空間。

 

 

                             

                                 中斷

 

 

中斷是由硬件設備產生的,而它們從物理上說就是電信號,以後,它們經過中斷控制器發送給CPU,接着CPU判斷收到的中斷來自於哪一個硬件設備(這定義在內核中),最後,由CPU發送給內核,內核來處理中斷。

 

硬中斷簡單處理流程:

硬中斷實現:中斷控制器+中斷服務程序

中斷框架設計(x86):

X86計算機的 CPU 爲中斷只提供了兩條外接引腳:NMI 和 INTR。其中 NMI 是不可屏蔽中斷,它一般用於電源掉電和物理存儲器奇偶校驗;INTR是可屏蔽中斷,能夠經過設置中斷屏蔽位來進行中斷屏蔽,它主要用於接受外部硬件的中斷信號,這些信號由中斷控制器傳遞給 CPU。當前x86 SMP架構主流都是採用多級I/O APIC(高級可編程中斷控制器)中斷系統。

Local APIC:主要負責傳遞中斷信號到指定的處理器;

I/O APIC:主要是收集來自 I/O 裝置的 Interrupt 信號且在當那些裝置須要中斷時發送信號到本地 APIC;

 

中斷分類

中斷可分爲同步(synchronous)中斷和異步(asynchronous)中斷:

  • 同步中斷是當指令執行時由 CPU 控制單元主動產生,之因此稱爲同步,是由於只有在一條指令執行完畢後 CPU 纔會發出中斷,而不是發生在代碼指令執行期間,好比系統調用,根據 Intel 官方資料,同步中斷稱爲異常(exception),異常可分爲故障(fault)、陷阱(trap)、終止(abort)三類。

  • 異步中斷是指由其餘硬件設備依照 CPU 時鐘信號隨機產生,即意味着中斷可以在指令之間發生,例如鍵盤中斷,異步中斷被稱爲中斷(interrupt),中斷可分爲可屏蔽中斷(Maskable interrupt)和非屏蔽中斷(Nomaskable interrupt)。

  1. 非屏蔽中斷(Non-maskable interrupts,即NMI):就像這種中斷類型的字面意思同樣,這種中斷是不可能被CPU忽略或取消的。NMI是在單獨的中斷線路上進行發送的,它一般被用於關鍵性硬件發生的錯誤,如內存錯誤,風扇故障,溫度傳感器故障等。

  2. 可屏蔽中斷(Maskable interrupts):這些中斷是能夠被CPU忽略或延遲處理的。當緩存控制器的外部針腳被觸發的時候就會產生這種類型的中斷,而中斷屏蔽寄存器就會將這樣的中斷屏蔽掉。咱們能夠將一個比特位設置爲0,來禁用在此針腳觸發的中斷。

處理流程:

區別:

相同點:

1.最後都是由CPU發送給內核,由內核去處理;

2.處理程序的流程設計上是類似的。

不一樣點:

1.產生源不相同,陷阱、異常是由CPU產生的,而中斷是由硬件設備產生的;

2.內核須要根據是異常,陷阱,仍是中斷調用不一樣的處理程序;

3.中斷不是時鐘同步的,這意味着中斷可能隨時到來;陷阱、異常是CPU產生的,因此,它是時鐘同步的;

4.當處理中斷時,處於中斷上下文中;處理陷阱、異常時,處於進程上下文中。

 

中斷親和:

  • 在 SMP 體系結構中,咱們能夠經過系統調用和一組相關的宏來設置 CPU 親和力(CPU affinity),將一個或多個進程綁定到一個或多個處理器上運行。中斷在這方面也絕不示弱,也具備相同的特性。中斷親和力是指將一個或多箇中斷源綁定到特定的 CPU 上運行;

  • 在 /proc/irq 目錄中,對於已經註冊中斷處理程序的硬件設備,都會在該目錄下存在一個以該中斷號命名的目錄 IRQ# ,IRQ# 目錄下有一個 smp_affinity 文件(SMP 體系結構纔有該文件),它是一個 CPU 的位掩碼,能夠用來設置該中斷的親和力, 默認值爲 0xffffffff,代表把中斷髮送到全部的 CPU 上去處理。若是中斷控制器不支持 IRQ affinity,不能改變此默認值,同時也不能關閉全部的 CPU 位掩碼,即不能設置成 0x0;

  • 中斷親和好處是,在大量硬件中斷場景,對於文件服務器、高流量 Web 服務器這樣的應用來講,把不一樣的網卡 IRQ 均衡綁定到不一樣的 CPU 上將會減輕某個 CPU 的負擔,提升多個 CPU 總體處理中斷的能力;對於數據庫服務器這樣的應用來講,把磁盤控制器綁到一個 CPU、把網卡綁定到另外一個 CPU 將會提升數據庫的響應時間,優化性能。合理的根據本身的生產環境和應用的特色來平衡 IRQ 中斷有助於提升系統的總體吞吐能力和性能;

 

Linux系統常見中斷分類

時鐘中斷

時鐘芯片產生,主要工做是處理和時間有關的全部信息,決定是否執行調度程序以及處理下半部分。和時間有關的全部信息包括系統時間、進程的時間片、延時、使用CPU的時間、各類定時器,進程更新後的時間片爲進程調度提供依據,而後在時鐘中斷返回時決定是否要執行調度程序。下半部分處理程序是Linux提供的一種機制,它使一部分工做推遲執行。時鐘中斷要絕對保證維持系統時間的準確性,「時鐘中斷」是整個操做系統的脈搏。

NMI中斷

外部硬件經過CPU的 NMI Pin 去觸發(硬件觸發),或者軟件向CPU系統總線上投遞一個NMI類型中斷(軟件觸發),NMI中斷的主要用途有兩個:

  • 用來告知操做系統有硬件錯誤(Hardware Failure),如內存錯誤,風扇故障,溫度傳感器故障等;

  • 用來作看門狗定時器,檢測CPU死鎖等;

硬件IO中斷

大多數硬件外設IO中斷,好比網卡,鍵盤,硬盤,鼠標,USB,串口等;

虛擬中斷

KVM裏面一些中斷退出和中斷注入等,軟件模擬中斷;

查看方式:cat /proc/interrupts

Linux系統中斷處理

因爲中斷會打斷內核中進程的正常調度運行,因此要求中斷服務程序儘量的短小精悍;可是在實際系統中,當中斷到來時,要完成工做每每須要進行大量的耗時處理。所以指望讓中斷處理程序運行得快,並想讓它完成的工做量多,這兩個目標相互制約,誕生頂/底半部機制。

中斷上半部分:

中斷處理程序是頂半部——接受中斷,它就當即開始執行,但只有作嚴格時限的工做。可以被容許稍後完成的工做會推遲到底半部去,此後,在合適的時機,底半部會被開終端執行。頂半部簡單快速,執行時禁止部分或者所有中斷。

中斷下半部分:

底半部稍後執行,並且執行期間能夠響應全部的中斷。這種設計可使系統處於中斷屏蔽狀態的時間儘量的短,以此來提升系統的響應能力。頂半部只有中斷處理程序機制,而底半部的實現有軟中斷,tasklet和工做隊列等實現方式;

 

軟中斷

軟中斷做爲下半部機制的表明,是隨着SMP(share memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了必定的機制)。軟中斷通常是「可延遲函數」的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是由於要知足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,並且能夠在多個CPU上並行執行,使得總的系統效率能夠更高。它的特性包括:產生後並非立刻能夠執行,必需要等待內核的調度才能執行。軟中斷不能被本身打斷(即單個cpu上軟中斷不能嵌套執行),只能被硬件中斷打斷(上半部), 能夠併發運行在多個CPU上(即便同一類型的也能夠)。因此軟中斷必須設計爲可重入的函數(容許多個CPU同時操做),所以也須要使用自旋鎖來保護其數據結構。


軟中斷的調度時機:

  1. do_irq完成I/O中斷時調用irq_exit。

  2. 系統使用I/O APIC,在處理完本地時鐘中斷時。

  3. local_bh_enable,即開啓本地軟中斷時。

  4. SMP系統中,cpu處理完被CALL_FUNCTION_VECTOR處理器間中斷所觸發的函數時。

  5. ksoftirqd/n線程被喚醒時。

     

     

軟中斷內核線程

在 Linux 中,中斷具備最高的優先級。不論在任什麼時候刻,只要產生中斷事件,內核將當即執行相應的中斷處理程序,等到全部掛起的中斷和軟中斷處理完畢後才能執行正常的任務,所以有可能形成實時任務得不到及時的處理。中斷線程化以後,中斷將做爲內核線程運行並且被賦予不一樣的實時優先級,實時任務能夠有比中斷線程更高的優先級。這樣,具備最高優先級的實時任務就能獲得優先處理,即便在嚴重負載下仍有實時性保證。可是,並非全部的中斷均可以被線程化,好比時鐘中斷,主要用來維護系統時間以及定時器等,其中定時器是操做系統的脈搏,一旦被線程化,就有可能被掛起,後果將不堪設想,因此不該當被線程化。

軟中斷優先在 irq_exit() 中執行,若是超過期間等條件轉爲 softirqd 線程中執行。知足如下任一條件軟中斷在 softirqd 線程中執行:

在 irq_exit()->__do_softirq() 中運行,時間超過 2ms。

在 irq_exit()->__do_softirq() 中運行,輪詢軟中斷超過 10 次。

在 irq_exit()->__do_softirq() 中運行,本線程須要被調度。

注:調用 raise_softirq() 喚醒軟中斷時,不在中斷環境中。

 

TASKLET

因爲軟中斷必須使用可重入函數,這就致使設計上的複雜度變高,做爲設備驅動程序的開發者來講,增長了負擔。而若是某種應用並不須要在多個CPU上並行執行,那麼軟中斷實際上是沒有必要的。所以誕生了彌補以上兩個要求的tasklet。它具備如下特性:

a)一種特定類型的tasklet只能運行在一個CPU上,不能並行,只能串行執行。

b)多個不一樣類型的tasklet能夠並行在多個CPU上。

c)軟中斷是靜態分配的,在內核編譯好以後,就不能改變。但tasklet就靈活許多,能夠在運行時改變(好比添加模塊時)。

tasklet是在兩種軟中斷類型的基礎上實現的,所以若是不須要軟中斷的並行特性,tasklet就是最好的選擇。也就是說tasklet是軟中斷的一種特殊用法,即延遲狀況下的串行執行。

 

tasklet有兩種,tasklet 和 hi-tasklet:

前者對應softirq_vec[TASKLET_SOFTIRQ];

後者對應softirq_vec[HI_SOFTIRQ]。只是後者排在softirq_vec[]的第一個,因此更早被執行;

 

/proc/softirqs 提供了軟中斷的運行狀況

# cat /proc/softirqs CPU0 HI: 1 //高優先級TASKLET軟中斷 TIMER: 12571001 //定時器軟中斷 NET_TX: 826165 //網卡發送軟中斷 NET_RX: 6263015 //網卡接收軟中斷 BLOCK: 1403226 //塊設備處理軟中斷 BLOCK_IOPOLL: 0 //塊設備處理軟中斷 TASKLET: 3752 //普通TASKLET軟中斷 SCHED: 0 //調度軟中斷 HRTIMER: 0 //當前已經沒有使用 RCU: 9729155 //RCU處理軟中斷,主要是callback函數處理

 

 

工做隊列

 

 

工做隊列(work queue)是Linux kernel中將工做推後執行的一種機制。軟中斷運行在中斷上下文中,所以不能阻塞和睡眠,而tasklet使用軟中斷實現,固然也不能阻塞和睡眠,工做隊列能夠把工做推後,交由一個內核線程去執行—這個下半部分老是會在進程上下文執行,所以工做隊列的優點就在於它容許從新調度甚至睡眠。

 

 

workqueue 中幾個角色關係:

 

  • work :工做/任務。

  • workqueue :工做的集合。workqueue 和 work 是一對多的關係。

  • worker :工人。在代碼中 worker 對應一個work_thread() 內核線程。

  • worker_pool:工人的集合。worker_pool 和 worker 是一對多的關係。

  • pwq(pool_workqueue):中間人 / 中介,負責創建起 workqueue 和 worker_pool 之間的關係。workqueue 和 pwq 是一對多的關係,pwq 和 worker_pool 是一對一的關係。

     

一般,在工做隊列和軟中斷/tasklet中做出選擇,可以使用如下規則:

 

  • 若是推後執行的任務須要睡眠,那麼只能選擇工做隊列。

  • 若是推後執行的任務須要延時指定的時間再觸發,那麼使用工做隊列,由於其能夠利用timer延時(內核定時器實現)。

  • 若是推後執行的任務須要在一個tick以內處理,則使用軟中斷或tasklet,由於其能夠搶佔普通進程和內核線程,同時不可睡眠。

  • 若是推後執行的任務對延遲的時間沒有任何要求,則使用工做隊列,此時一般爲可有可無的任務。

 

實際上,工做隊列的本質就是將工做交給內核線程處理,所以其能夠用內核線程替換。可是內核線程的建立和銷燬對編程者的要求較高,而工做隊列實現了內核線程的封裝,不易出錯,推薦使用工做隊列。

 

中斷上下文

中斷代碼運行於內核空間,中斷上下文即運行中斷代碼所須要CPU上下文環境,須要硬件傳遞過來的這些參數,內核須要保存的一些其餘環境(主要是當前被打斷執行的進程或其餘中斷環境),這些通常都保存在中斷棧中(x86是獨立的,其餘可能和內核棧共享,這和具體處理架構密切相關),在中斷結束後,進程仍然能夠從原來的狀態恢復運行。

 

是否處於中斷中,在Linux中是經過preempt_count來判斷的,具體以下:

#define in_irq()     (hardirq_count()) //在處理硬中斷中

#define in_softirq()     (softirq_count()) //在處理軟中斷中

#define in_interrupt()   (irq_count()) //在處理硬中斷或軟中斷中

#define in_atomic()     ((preempt_count() & ~PREEMPT_ACTIVE) != 0) //包含以上全部狀況

 

總結和注意的點:

 

1.Linux kernel的設計者制定了規則:

  • 中斷上下文不是調度實體,task纔是【進程(主線程)或者線程】;

  • 優先級順序:硬中斷上下文 > 軟中斷上下文 > 進程上下文 ;

中斷上下文(hardirq和softirq context)並不參與調度(暫不考慮中斷線程化),它們是異步事件的處理機制,目標就是儘快完成處理,返回現場。所以,全部中斷上下文的優先級都是高於進程上下文的。也就是說,對於用戶進程(不管內核態仍是用戶態)或者內核線程,除非disable了CPU的本地中斷,不然一旦中斷髮生,它們是沒有任何能力阻擋中斷上下文搶佔當前進程上下文的執行的。

 

2.Linux 將中斷處理過程分紅了兩個階段,也就是上半部和下半部:

  • 上半部用來快速處理中斷,它在中斷禁止模式下運行,主要處理跟硬件緊密相關的或時間敏感的工做,須要快速執行;

  • 下半部用來延遲處理上半部未完成的工做,一般以軟中斷方式運行,能夠延遲執行。

     

3. 硬中斷和軟中斷(只要是中斷上下文)執行的時候都不容許內核搶佔(本文後續章節會講內核搶佔)。由於在中斷上下文中,惟一能打斷當前中斷handler的只有更高優先級的中斷,它不會被進程打斷(這點對於softirq,tasklet也同樣,所以這些bottom half也不能睡眠);若是在中斷上下文中睡眠,則沒有辦法喚醒它,由於全部的wake_up_xxx都是針對某個進程而言的,而在中斷上下文中,沒有進程的概念,沒有相應task_struct(這點對於softirq和tasklet同樣),所以真的睡眠了,好比調用了會致使阻塞的例程,內核幾乎會掛。

 

4.硬中斷能夠被另外一個優先級比本身高的硬中斷「中斷」,不能被同級(同一種硬中斷)或低級的硬中斷「中斷」,更不能被軟中斷「中斷」。軟中斷能夠被硬中斷「中斷」,可是不會被另外一個軟中斷「中斷」。在一個CPU上,軟中斷老是串行執行。因此在單處理器上,對軟中斷的數據結構進行訪問不須要加任何同步原語。

 

5.關中斷不會丟失中斷,可是對於期間到來的多個相同的中斷會合併成一個,即只處理一次;時鐘中斷中須要更新jieffis計數值,若是多箇中斷合成一個,爲了減小影響jieffis值準確性,須要其餘硬件時鐘來矯正。

 

 

關注公衆號,獲取所有內容:

想要獲取linux調度全景指南精簡版,關注公衆號回覆「調度」便可獲取。回覆其餘消息,獲取更多內容;

                                       

往期推薦

           

掃碼二維碼

獲取更多精彩內容

 

相關文章
相關標籤/搜索