Linux调度系统全景指南(下篇)
點(diǎn)擊上方藍(lán)字關(guān)注公眾號(hào),更多經(jīng)典內(nèi)容等著你
| 導(dǎo)語(yǔ)本文主要是講Linux的調(diào)度系統(tǒng), 由于全部?jī)?nèi)容太多,分三部分來(lái)講,本篇是下篇(主要線程和進(jìn)程),上篇請(qǐng)看(CPU和中斷):Linux調(diào)度系統(tǒng)全景指南(上篇),調(diào)度可以說是操作系統(tǒng)的靈魂,為了讓CPU資源利用最大化,Linux設(shè)計(jì)了一套非常精細(xì)的調(diào)度系統(tǒng),對(duì)大多數(shù)場(chǎng)景都進(jìn)行了很多優(yōu)化,系統(tǒng)擴(kuò)展性強(qiáng),我們可以根據(jù)業(yè)務(wù)模型和業(yè)務(wù)場(chǎng)景的特點(diǎn),有針對(duì)性的去進(jìn)行性能優(yōu)化,在保證客戶網(wǎng)絡(luò)帶寬前提下,隔離客戶互相之間的干擾影響,提高CPU利用率,降低單位運(yùn)算成本,提高市場(chǎng)競(jìng)爭(zhēng)力。歡迎大家相互交流學(xué)習(xí)!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 目錄
上篇請(qǐng)看(CPU和中斷):Linux調(diào)度系統(tǒng)全景指南(上篇)
中篇請(qǐng)看(搶占和時(shí)鐘):Linux調(diào)度系統(tǒng)全景指南(中篇)? ?
? ? ?? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?進(jìn)程
?一般定義是操作系統(tǒng)對(duì)一個(gè)正在運(yùn)行的程序的一種抽象,?是運(yùn)行資源的管理單位(虛擬內(nèi)存空間,文件句柄,全局變量,信號(hào)等運(yùn)行資源),是操作系統(tǒng)資源分配的最小單位。在linux系統(tǒng)下,無(wú)論是進(jìn)程,還是線程,到了內(nèi)核里面,我們統(tǒng)一都叫任務(wù)(Task),由一個(gè)統(tǒng)一的結(jié)構(gòu) task_struct 進(jìn)行管理:
詳細(xì)結(jié)構(gòu):
大體分為下面幾類:
進(jìn)程運(yùn)行空間
Linux 按照特權(quán)等級(jí),把進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間,分別對(duì)應(yīng)著下圖中, CPU 特權(quán)等級(jí)的 Ring 0 和 Ring 3。
內(nèi)核空間(Ring 0)具有最高權(quán)限,可以直接訪問所有資源;
用戶空間(Ring 3)只能訪問受限資源,不能直接訪問內(nèi)存等硬件設(shè)備,必須通過系統(tǒng)調(diào)用陷入到內(nèi)核中,才能訪問這些特權(quán)資源;
進(jìn)程既可以在用戶空間運(yùn)行,又可以在內(nèi)核空間中運(yùn)行。進(jìn)程在用戶空間運(yùn)行時(shí),被稱為進(jìn)程的用戶態(tài),而陷入內(nèi)核空間的時(shí)候,被稱為進(jìn)程的內(nèi)核態(tài)。
進(jìn)程內(nèi)存空間(x86_64)
各個(gè)分區(qū)的意義:
內(nèi)核空間:
在32位系統(tǒng)中,Linux會(huì)留1G空間給內(nèi)核,用戶進(jìn)程是無(wú)法訪問的,用來(lái)存放進(jìn)程相關(guān)數(shù)據(jù)和內(nèi)存數(shù)據(jù),內(nèi)核代碼等;在64位系統(tǒng)里面,Linux會(huì)采用最低48位來(lái)表示虛擬內(nèi)存,這可通過 /proc/cpuinfo 來(lái)查看address sizes :
address sizes :
36 bits physical, 48 bits virtual,總的虛擬地址空間為256TB( 2^48 ),在這256TB的虛擬內(nèi)存空間中, 0000000000000000 - 00007fffffffffff(128TB)為用戶空間,ffff800000000000 - ffffffffffffffff(128TB)為內(nèi)核空間, 剩下的是用戶內(nèi)存空間:
stack棧區(qū):
專門用來(lái)實(shí)現(xiàn)函數(shù)調(diào)用-棧結(jié)構(gòu)的內(nèi)存塊。相對(duì)空間下(可以設(shè)置大小,Linux 一般默認(rèn)是8M,可通過 ulimit –s 查看),系統(tǒng)自動(dòng)管理,從高地址往低地址,向下生長(zhǎng)。
內(nèi)存映射區(qū):
包括文件映射和匿名內(nèi)存映射, 應(yīng)用程序的所依賴的動(dòng)態(tài)庫(kù),會(huì)在程序執(zhí)行時(shí)候,加載到內(nèi)存這個(gè)區(qū)域,一般包括數(shù)據(jù)(data)和代碼(text);通過mmap系統(tǒng)調(diào)用,可以把特定的文件映射到內(nèi)存中,然后在相應(yīng)的內(nèi)存區(qū)域中操作字節(jié)來(lái)訪問文件內(nèi)容,實(shí)現(xiàn)更高效的IO操作;匿名映射,在glibc中malloc分配大內(nèi)存的時(shí)候會(huì)用到匿名映射。這里所謂的“大”表示是超過了MMAP_THRESHOLD 設(shè)置的字節(jié)數(shù),它的缺省值是 128 kB,可以通過 mallopt() 去調(diào)整這個(gè)設(shè)置值。還可以用于進(jìn)程間通信IPC(共享內(nèi)存)。
heap堆區(qū):
主要用于用戶動(dòng)態(tài)內(nèi)存分配,空間大,使用靈活,但需要用戶自己管理,通過brk系統(tǒng)調(diào)用控制堆的生長(zhǎng),向高地址生長(zhǎng)。
BBS段和DATA段:
用于存放程序全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù),一般未初始化的放在BSS段(統(tǒng)一初始化為0,不占程序文件的空間),初始化的放在data段,只讀數(shù)據(jù)放在rodata段(常量存儲(chǔ)區(qū))。
text段:
主要存放程序二進(jìn)制代碼。
?
進(jìn)程調(diào)度
進(jìn)程狀態(tài)機(jī)
進(jìn)程是一個(gè)動(dòng)態(tài)的概念,是應(yīng)用程序當(dāng)前正在運(yùn)行的一個(gè)實(shí)例, 在進(jìn)程的整個(gè)生命周期中,它會(huì)處于不同的狀態(tài),并且在不同狀態(tài)之間轉(zhuǎn)化:
R (TASK_RUNNING)--執(zhí)行狀態(tài)
只有在該狀態(tài)的進(jìn)程才可能在CPU上運(yùn)行。而同一時(shí)刻可能有多個(gè)進(jìn)程處于可執(zhí)行狀態(tài),這些進(jìn)程的task_struct結(jié)構(gòu)(進(jìn)程控制塊)被放入對(duì) 應(yīng)CPU的可執(zhí)行隊(duì)列中(一個(gè)task最多只能出現(xiàn)在一個(gè)CPU的可執(zhí)行隊(duì)列中)。進(jìn)程調(diào)度器的任務(wù)就是從各個(gè)CPU的可執(zhí)行隊(duì)列中分別選擇一個(gè)task在該 CPU上運(yùn)行。很多操作系統(tǒng)教科書將正在CPU上執(zhí)行的進(jìn)程定義為RUNNING狀態(tài),而將可執(zhí)行但是尚未被調(diào)度執(zhí)行的進(jìn)程定義為READY狀態(tài),這兩種狀態(tài)在linux下統(tǒng)一為 TASK_RUNNING狀態(tài)。
S (TASK_INTERRUPTIBLE)--可中斷的睡眠狀態(tài)
處于這個(gè)狀態(tài)的進(jìn)程因?yàn)榈却硞€(gè)事件的發(fā)生(比如等待socket連接、等待信號(hào)量,等待鎖),而被掛起。這些進(jìn)程的task_struct結(jié)構(gòu)被放入對(duì)應(yīng)事件的等待隊(duì)列中。當(dāng)這些事件發(fā)生時(shí)(由外部中斷觸發(fā)、或由其他進(jìn)程觸發(fā)),對(duì)應(yīng)的等待隊(duì)列中的一個(gè)或多個(gè)進(jìn)程將被喚醒。通過ps命令我們會(huì)看到,一般情況下,進(jìn)程列表中的絕大多數(shù)進(jìn)程都處于TASK_INTERRUPTIBLE狀態(tài)(除非機(jī)器的負(fù)載很高)。畢竟CPU就一兩個(gè),進(jìn)程動(dòng)輒幾十上百個(gè),如果不是絕大多數(shù)進(jìn)程都在睡眠,CPU又怎么響應(yīng)得過來(lái)。
T (TASK_STOPPED or TASK_TRACED)--暫停狀態(tài)或跟蹤狀態(tài)
向進(jìn)程發(fā)送一個(gè)SIGSTOP信號(hào),它就會(huì)因響應(yīng)該信號(hào)而進(jìn)入TASK_STOPPED狀態(tài)(除非該進(jìn)程本身處于TASK_UNINTERRUPTIBLE狀態(tài)而不響應(yīng)信號(hào))。(SIGSTOP與SIGKILL信號(hào)一樣,是非常強(qiáng)制的。不允許用戶進(jìn)程通過signal系列的系統(tǒng)調(diào)用重新設(shè)置對(duì)應(yīng)的信號(hào)處理函數(shù)。)向進(jìn)程發(fā)送一個(gè)SIGCONT信號(hào),可以讓其從TASK_STOPPED狀態(tài)恢復(fù)到TASK_RUNNING狀態(tài)。
Z (TASK_DEAD - EXIT_ZOMBIE)--退出狀態(tài)
進(jìn)程成為僵尸進(jìn)程。當(dāng)父進(jìn)程遺漏了用wait()函數(shù)等待已終止的子進(jìn)程時(shí),子進(jìn)程就會(huì)進(jìn)入一種無(wú)父進(jìn)程的狀態(tài),此時(shí)子進(jìn)程就是僵尸進(jìn)程。
D (TASK_UNINTERRUPTIBLE)--不可中斷的睡眠狀態(tài)
TASK_INTERRUPTIBLE狀態(tài)類似,進(jìn)程處于睡眠狀態(tài),但是此刻進(jìn)程是不可中斷的。不可中斷,指的并不是CPU不響應(yīng)外部硬件的中斷,而是指進(jìn)程不響應(yīng)異步信號(hào)。絕大多數(shù)情況下,進(jìn)程處在睡眠狀態(tài)時(shí),總是應(yīng)該能夠響應(yīng)異步信號(hào)的。否則你將驚奇的發(fā)現(xiàn),kill -9竟然殺不死一個(gè)正在睡眠的進(jìn)程了!于是我們也很好理解,為什么ps命令看到的進(jìn)程幾乎不會(huì)出現(xiàn)TASK_UNINTERRUPTIBLE狀態(tài),而總是 TASK_INTERRUPTIBLE狀態(tài)。而TASK_UNINTERRUPTIBLE狀態(tài)存在的意義就在于,內(nèi)核的某些處理流程是不能被打斷的。如果響應(yīng)異步信號(hào),程序的執(zhí)行流程中就會(huì)被插入一段用于處理異步信號(hào)的流程(這個(gè)插入的流程可能只存在于內(nèi)核態(tài),也可能延伸到用戶態(tài)),于是原有的流程就被中斷了。
進(jìn)程上下文切換
上下文切換(有時(shí)也稱做進(jìn)程切換或任務(wù)切換):是指CPU從一個(gè)進(jìn)程或線程切換到另一個(gè)進(jìn)程或線程。簡(jiǎn)潔描述一下,上下文切換可以認(rèn)為是內(nèi)核(操作系統(tǒng)的核心)在 CPU 上對(duì)于進(jìn)程(包括線程)進(jìn)行以下的活動(dòng):
掛起一個(gè)進(jìn)程,將這個(gè)進(jìn)程在CPU 中的狀態(tài)(上下文)存儲(chǔ)于內(nèi)存中的某處;
在內(nèi)存中檢索下一個(gè)進(jìn)程的上下文并將其在CPU 的寄存器中恢復(fù);
跳轉(zhuǎn)到程序計(jì)數(shù)器所指向的位置(即跳轉(zhuǎn)到進(jìn)程被中斷時(shí)的代碼行),以恢復(fù)該進(jìn)程。
因此上下文是指某一時(shí)間點(diǎn)CPU寄存器和程序計(jì)數(shù)器的內(nèi)容,廣義上還包括內(nèi)存中進(jìn)程的虛擬地址映射信息。上下文切換只能發(fā)生在內(nèi)核態(tài)中,上下文切換通常是計(jì)算密集型的。也就是說,它需要相當(dāng)可觀的處理器時(shí)間,在每秒幾十上百次的切換中,每次切換都需要納秒量級(jí)的時(shí)間。所以,上下文切換對(duì)系統(tǒng)來(lái)說意味著消耗大量的CPU時(shí)間,事實(shí)上,可能是操作系統(tǒng)中時(shí)間消耗最大的操作。Linux相比與其他操作系統(tǒng)(包括其他類Unix系統(tǒng))有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。
進(jìn)程優(yōu)先級(jí)
進(jìn)程的優(yōu)先級(jí)有動(dòng)態(tài)優(yōu)先級(jí)和靜態(tài)優(yōu)先級(jí)決定;
它是決定進(jìn)程在CPU的執(zhí)行順序的數(shù)字;
優(yōu)先級(jí)越高被CPU執(zhí)行的概率越大;
內(nèi)核采用啟發(fā)式算法決定是開啟或者關(guān)閉動(dòng)態(tài)優(yōu)先級(jí),可以通過修改nice級(jí)別直接修改進(jìn)程的靜態(tài)優(yōu)先級(jí),而獲取更多CPU執(zhí)行時(shí)間;
Linux支持的nice級(jí)別從19(最低優(yōu)先級(jí))到-20(最高優(yōu)先級(jí)),默認(rèn)只是0。只有root身份的用戶才能把進(jìn)程的nice級(jí)別調(diào)整為負(fù)數(shù)(讓其具備較高優(yōu)先級(jí))。
時(shí)間片
時(shí)間片(timeslice)又稱為“量子(quantum)”或“處理器片(processor slice)”是分時(shí)操作系統(tǒng)分配給每個(gè)正在運(yùn)行的進(jìn)程微觀上的一段CPU時(shí)間(在搶占內(nèi)核中是:從進(jìn)程開始運(yùn)行直到被搶占的時(shí)間);
時(shí)間片由操作系統(tǒng)內(nèi)核的調(diào)度程序分配給每個(gè)進(jìn)程。首先,內(nèi)核會(huì)給每個(gè)進(jìn)程分配相等的初始時(shí)間片,然后每個(gè)進(jìn)程輪番地執(zhí)行相應(yīng)的時(shí)間,當(dāng)所有進(jìn)程都處于時(shí)間片耗盡的狀態(tài)時(shí),內(nèi)核會(huì)重新為每個(gè)進(jìn)程計(jì)算并分配時(shí)間片,如此往復(fù);
時(shí)間片設(shè)得太短會(huì)導(dǎo)致過多的進(jìn)程切換,降低了CPU效率;而設(shè)得太長(zhǎng)又可能引起對(duì)短的交互請(qǐng)求的響應(yīng)變差,不同調(diào)度算法,對(duì)時(shí)間片管理不一樣;
通常狀況下,一個(gè)系統(tǒng)中所有的進(jìn)程被分配到的時(shí)間片長(zhǎng)短并不是相等的,盡管初始時(shí)間片基本相等(在Linux系統(tǒng)中,初始時(shí)間片也不相等,而是各自父進(jìn)程的一半),系統(tǒng)通過測(cè)量進(jìn)程處于“睡眠”和“正在運(yùn)行”狀態(tài)的時(shí)間長(zhǎng)短來(lái)計(jì)算每個(gè)進(jìn)程的交互性,交互性和每個(gè)進(jìn)程預(yù)設(shè)的靜態(tài)優(yōu)先級(jí)(Nice值)的疊加即是動(dòng)態(tài)優(yōu)先級(jí),動(dòng)態(tài)優(yōu)先級(jí)按比例縮放就是要分配給那個(gè)進(jìn)程時(shí)間片的長(zhǎng)短。一般地,為了獲得較快的響應(yīng)速度,交互性強(qiáng)的進(jìn)程(即趨向于IO消耗型)被分配到的時(shí)間片要長(zhǎng)于交互性弱的(趨向于處理器消耗型)進(jìn)程。
調(diào)度框架
實(shí)際上進(jìn)程是資源管理的單位,線程才是調(diào)度的單位,內(nèi)核統(tǒng)稱為任務(wù)調(diào)度。操作系統(tǒng)最重要的任務(wù)就是把系統(tǒng)中的task調(diào)度到各個(gè)CPU上去執(zhí)行, 不同的任務(wù)有不同的需求,因此我們需要對(duì)任務(wù)進(jìn)行分類:一種是普通進(jìn)程,另外一種是實(shí)時(shí)進(jìn)程。對(duì)于實(shí)時(shí)進(jìn)程,毫無(wú)疑問快速響應(yīng)的需求是最重要的,而對(duì)于普通進(jìn)程,我們需要兼顧前三點(diǎn)的需求。相信你也發(fā)現(xiàn)了,這些需求是互相沖突的,對(duì)于這些time-sharing的普通進(jìn)程如何平衡設(shè)計(jì)呢?這里需要進(jìn)一步將普通進(jìn)程細(xì)分為交互式進(jìn)程(interactive processs)和批處理進(jìn)程(batch process)。交互式進(jìn)程需要和用戶進(jìn)行交流,因此對(duì)調(diào)度延遲比較敏感,而批處理進(jìn)程屬于那種在后臺(tái)默默干活的,因此它更注重throughput的需求。當(dāng)然,無(wú)論如何,分享時(shí)間片的普通進(jìn)程還是需要兼顧公平,不能有人大魚大肉,有人連湯都喝不上。為了達(dá)到這些設(shè)計(jì)目標(biāo),調(diào)度器必須要考慮某些調(diào)度因素,比如說“優(yōu)先級(jí)”、“時(shí)間片”等。在Linux內(nèi)核中,優(yōu)先級(jí)就是實(shí)時(shí)進(jìn)程調(diào)度的主要考慮因素。而對(duì)于普通進(jìn)程,如何細(xì)分時(shí)間片則是調(diào)度器的核心思考點(diǎn)。過大的時(shí)間片會(huì)嚴(yán)重?fù)p傷系統(tǒng)的響應(yīng)延遲,讓用戶明顯能夠感知到延遲,卡頓,從而影響用戶體驗(yàn)。較小的時(shí)間片雖然有助于減少調(diào)度延遲,但是頻繁的切換對(duì)系統(tǒng)的throughput會(huì)造成嚴(yán)重的影響。因?yàn)檫@時(shí)候大部分的CPU時(shí)間用于進(jìn)程切換,而忘記了它本來(lái)的功能其實(shí)就是推動(dòng)任務(wù)的執(zhí)行。由于Linux是一個(gè)通用操作系統(tǒng),它的目標(biāo)是星辰大海,既能運(yùn)行在嵌入式平臺(tái)上,又能在服務(wù)器領(lǐng)域中獲得很好的性能表現(xiàn),此外在桌面應(yīng)用場(chǎng)景中,也不能讓用戶有較差的用戶體驗(yàn)。Linux任務(wù)調(diào)度算法核心就是解決調(diào)度優(yōu)化問題:
(1)公平:對(duì)于time-sharing的進(jìn)程,保證每個(gè)進(jìn)程得到合理的CPU時(shí)間。
(2)高效:使CPU保持忙碌狀態(tài),即總是有進(jìn)程在CPU上運(yùn)行。
(3)響應(yīng)時(shí)間:使交互用戶的響應(yīng)時(shí)間盡可能短。
(4)周轉(zhuǎn)時(shí)間:使批處理用戶等待輸出的時(shí)間盡可能短。
(5)吞吐量:使單位時(shí)間內(nèi)處理的進(jìn)程數(shù)量盡可能多。
Linux調(diào)度器采用了模塊化設(shè)計(jì)的思想,從而把進(jìn)程調(diào)度的軟件分成了兩個(gè)層次,一個(gè)是core scheduler layer,另外一個(gè)是specific scheduler layer:
? ? ? ? ? ? ? ? ? ? ? ??
從功能層面上看,進(jìn)程調(diào)度仍然分成兩個(gè)部分,第一個(gè)部分是通過負(fù)載均衡模塊將各個(gè)runnable task根據(jù)負(fù)載情況平均分配到各個(gè)CPU runqueue上去。第二部分的功能是在各個(gè)CPU的Main scheduler和Tick scheduler的驅(qū)動(dòng)下進(jìn)行單個(gè)CPU上的調(diào)度。調(diào)度器處理的task各不相同,有RT task,有normal task,有Deal line task,但是無(wú)論哪一種task,它們都有共同的邏輯,這部分被抽象成Core scheduler layer,同時(shí)各種特定類型的調(diào)度器定義自己的sched_class,并以鏈表的形式加入到系統(tǒng)中。這樣的模塊化設(shè)計(jì)可以方便用戶根據(jù)自己的場(chǎng)景定義specific scheduler,而不需要改動(dòng)Core scheduler layer的邏輯。
2個(gè)調(diào)度器
可以用兩種方法來(lái)激活調(diào)度:
主調(diào)度器 :一種是直接的, 比如進(jìn)程打算睡眠或出于其他原因放棄CPU;
周期性調(diào)度器:通過周期性的機(jī)制, 以固定的頻率運(yùn)行, 不時(shí)的檢測(cè)是否有必要。
調(diào)度策略
linux內(nèi)核目前實(shí)現(xiàn)了6種調(diào)度策略(即調(diào)度算法),用于對(duì)不同類型的進(jìn)程進(jìn)行調(diào)度,?或者支持某些特殊的功能:
SCHED_NORMAL和SCHED_BATCH調(diào)度普通的非實(shí)時(shí)進(jìn)程;
SCHED_FIFO和SCHED_RR和SCHED_DEADLINE則采用不同的調(diào)度策略調(diào)度實(shí)時(shí)進(jìn)程;
SCHED_IDLE則在系統(tǒng)空閑時(shí)調(diào)用idle進(jìn)程;
stop任務(wù)是系統(tǒng)中優(yōu)先級(jí)最高的任務(wù),它可以搶占所有的進(jìn)程并且不會(huì)被任何進(jìn)程搶占,其專屬調(diào)度器類即stop-task;
idle-task調(diào)度器類與CFS里要處理的SCHED_IDLE沒有關(guān)系;
idle任務(wù)會(huì)被任意進(jìn)程搶占,其專屬調(diào)度器類為idle-task;
idle-task和stop-task沒有對(duì)應(yīng)的調(diào)度策略;
采用SCHED_IDLE調(diào)度策略的任務(wù)其調(diào)度器類為 CFS。
調(diào)度器類
CFS (Completely_Fair_Scheduler)
Real-Time Scheduler
stop-task (sched_class_highest) Scheduler
Deadline Scheduler: Earliest Deadline First (EDF) + Constant Bandwidth Server (CBS)
Idle-task Scheduler
調(diào)度類順序
優(yōu)先級(jí)順序:stop-task --> deadline --> real-time --> fair --> idle
在各調(diào)度器類定義的時(shí)候通過next指針定義好了下一級(jí)調(diào)度器類;
stop-task是通過宏#define sched_class_highest (&stop_sched_class)指定的;
編譯時(shí)期就已決定,不能動(dòng)態(tài)擴(kuò)展。
調(diào)度實(shí)體
這種一般性要求調(diào)度器不直接操作進(jìn)程,而是處理可調(diào)度實(shí)體,?因此需要一個(gè)通用的數(shù)據(jù)結(jié)構(gòu)描述這個(gè)調(diào)度實(shí)體,即seched_entity結(jié)構(gòu),?其實(shí)際上就代表了一個(gè)調(diào)度對(duì)象,可以是一個(gè)進(jìn)程,也可以是一個(gè)進(jìn)程組,linux中針對(duì)當(dāng)前可調(diào)度的實(shí)時(shí)和非實(shí)時(shí)進(jìn)程,定義了類型為seched_entity的3個(gè)調(diào)度實(shí)體:
sched_dl_entity 采用EDF算法調(diào)度的實(shí)時(shí)調(diào)度實(shí)體;
sched_rt_entity 采用Roound-Robin或者FIFO算法調(diào)度的實(shí)時(shí)調(diào)度實(shí)體;
sched_entity 采用CFS算法調(diào)度的普通非實(shí)時(shí)進(jìn)程的調(diào)度實(shí)體。
調(diào)度類算法
CFS(Completely Fair Scheduler)算法(完全公平調(diào)度器,對(duì)于普通進(jìn)程)
設(shè)定一個(gè)調(diào)度周期(sched_latency_ns),目標(biāo)是讓每個(gè)進(jìn)程在這個(gè)周期內(nèi)至少有機(jī)會(huì)運(yùn)行一次。就是每個(gè)進(jìn)程等待CPU的時(shí)間最長(zhǎng)不超過這個(gè)調(diào)度周期;
根據(jù)進(jìn)程的數(shù)量,大家平分這個(gè)調(diào)度周期內(nèi)的CPU使用權(quán),由于進(jìn)程的優(yōu)先級(jí)即nice值不同,分割調(diào)度周期的時(shí)候要加權(quán);
每個(gè)進(jìn)程的經(jīng)過加權(quán)后的累計(jì)運(yùn)行時(shí)間保存在自己的vruntime字段里;
哪個(gè)進(jìn)程的vruntime最小(紅黑樹pick_next)就獲得本輪運(yùn)行的權(quán)利。
Realtime Scheduler(實(shí)時(shí))
實(shí)時(shí)系統(tǒng)是這樣的一種計(jì)算系統(tǒng):當(dāng)事件發(fā)生后,它必須在確定的時(shí)間范圍內(nèi)做出響應(yīng)。在實(shí)時(shí)系統(tǒng)中,產(chǎn)生正確的結(jié)果不僅依賴于系統(tǒng)正確的邏輯動(dòng)作,而且依賴于邏輯動(dòng)作的時(shí)序。換句話說,當(dāng)系統(tǒng)收到某個(gè)請(qǐng)求,會(huì)做出相應(yīng)的動(dòng)作以響應(yīng)該請(qǐng)求,想要保證正確地響應(yīng)該請(qǐng)求,一方面邏輯結(jié)果要正確,更重要的是需要在最后期限(deadline)內(nèi)作出響應(yīng)。如果系統(tǒng)未能在最后期限內(nèi)進(jìn)行響應(yīng),那么該系統(tǒng)就會(huì)產(chǎn)生錯(cuò)誤或者缺陷。在多任務(wù)操作系統(tǒng)中(如Linux),實(shí)時(shí)調(diào)度器(realtime scheduler)負(fù)責(zé)協(xié)調(diào)實(shí)時(shí)任務(wù)對(duì)CPU的訪問,以確保系統(tǒng)中所有的實(shí)時(shí)任務(wù)在其deadline內(nèi)完成,為了滿足實(shí)時(shí)任務(wù)的調(diào)度需求,Linux提供了兩種實(shí)時(shí)調(diào)度器:POSIX realtime scheduler(后文簡(jiǎn)稱RT調(diào)度)和deadline scheduler(后文簡(jiǎn)稱DL調(diào)度器)。
Linux 支持SCHED_RR和SCHED_FIFO兩種實(shí)時(shí)調(diào)度策略。
先進(jìn)先出(SCHED_FIFO): 沒有時(shí)間片,被調(diào)度器選擇后只要不被搶占,阻塞,或者自愿放棄處理器,可以運(yùn)行任意長(zhǎng)的時(shí)間。
輪轉(zhuǎn)調(diào)度(SCHED_RR): 有時(shí)間片,其值在進(jìn)程運(yùn)行時(shí)會(huì)減少。時(shí)間片用完后,該值重置,進(jìn)程置于隊(duì)列末尾。
兩種調(diào)度策略都是靜態(tài)優(yōu)先級(jí),內(nèi)核不為這兩種實(shí)時(shí)進(jìn)程計(jì)算動(dòng)態(tài)優(yōu)先級(jí)。
這兩種實(shí)現(xiàn)都屬于 軟實(shí)時(shí)。
實(shí)時(shí)優(yōu)先級(jí)的范圍:0 ~ MAX_RT_PRIO-1
MAX_RT_PRIO默認(rèn)值為 100
故默認(rèn)實(shí)時(shí)優(yōu)先級(jí)范圍:0 ~ 99。
實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)范圍[0~99]都高于普通進(jìn)程[100~139],始終優(yōu)先于普通進(jìn)程得到運(yùn)行,為了防止普通進(jìn)程饑餓,Linux kernel有一個(gè)RealTime Throttling機(jī)制,就是為了防止CPU消耗型的實(shí)時(shí)進(jìn)程霸占所有的CPU資源而造成整個(gè)系統(tǒng)失去控制。它的原理很簡(jiǎn)單,就是保證無(wú)論如何普通進(jìn)程都能得到一定比例(默認(rèn)5%)的CPU時(shí)間,可以通過兩個(gè)內(nèi)核參數(shù)來(lái)控制:
/proc/sys/kernel/sched_rt_period_us
缺省值是1,000,000 μs (1秒),表示實(shí)時(shí)進(jìn)程的運(yùn)行粒度為1秒。(注:修改這個(gè)參數(shù)請(qǐng)謹(jǐn)慎,太大或太小都可能帶來(lái)問題)。/proc/sys/kernel/sched_rt_runtime_us
缺省值是 950,000 μs (0.95秒),表示在1秒的運(yùn)行周期里所有的實(shí)時(shí)進(jìn)程一起最多可以占用0.95秒的CPU時(shí)間。
如果sched_rt_runtime_us=-1,表示取消限制,意味著實(shí)時(shí)進(jìn)程可以占用100%的CPU時(shí)間(慎用,有可能使系統(tǒng)失去控制)。
Deadline Task Scheduling
DL調(diào)度器是根據(jù)任務(wù)的deadline來(lái)確定調(diào)度的優(yōu)先順序的:deadline最早到來(lái)的那個(gè)任務(wù)最先調(diào)度執(zhí)行。對(duì)于有M個(gè)處理器的系統(tǒng),優(yōu)先級(jí)最高的前M個(gè)deadline任務(wù)(即deadline最早到來(lái)的前M個(gè)任務(wù))將被選擇在對(duì)應(yīng)M個(gè)處理器上運(yùn)行。
Linux DL調(diào)度器還實(shí)現(xiàn)了constant bandwidth server(CBS)算法,該算法是一種CPU資源預(yù)留協(xié)議。CBS可以保證每個(gè)任務(wù)在每個(gè)period內(nèi)都能收到完整的runtime時(shí)間。在一個(gè)周期內(nèi),DL進(jìn)程的“活”來(lái)的時(shí)候,CBS會(huì)重新補(bǔ)充該任務(wù)的運(yùn)行時(shí)間。在處理“活”的時(shí)候,runtime時(shí)間會(huì)不斷的消耗;如果runtime使用完畢,該任務(wù)會(huì)被DL調(diào)度器調(diào)度出局。在這種情況下,該任務(wù)無(wú)法再次占有CPU資源,只能等到下一次周期到來(lái)的時(shí)候,runtime重新補(bǔ)充之后才能運(yùn)行。因此,CBS一方面可以用來(lái)保證每個(gè)任務(wù)的CPU時(shí)間按照其定義的runtime參數(shù)來(lái)分配,另外一方面,CBS也保證任務(wù)不會(huì)占有超過其runtime的CPU資源,從而防止了DL任務(wù)之間的互相影響。
為了避免DL任務(wù)造成系統(tǒng)超負(fù)荷運(yùn)行,DL調(diào)度器有一個(gè)準(zhǔn)入機(jī)制,在任務(wù)配置好了period、runtime和deadline參數(shù)之后并準(zhǔn)備加入到系統(tǒng)的時(shí)候,DL調(diào)度器會(huì)對(duì)該任務(wù)進(jìn)行評(píng)估。這個(gè)準(zhǔn)入機(jī)制保證了DL任務(wù)將不會(huì)使用超過系統(tǒng)的CPU時(shí)間的最大值。這個(gè)最大值在sched_rt_runtime_us和kernel.sched_rt_period_us sysctl參數(shù)中指定。默認(rèn)值是950000和1000000,表示在1s的周期內(nèi),CPU用于執(zhí)行實(shí)時(shí)任務(wù)(DL任務(wù)和RT任務(wù))的最大時(shí)間值是950000μs。對(duì)于單個(gè)核心系統(tǒng),這個(gè)測(cè)試既是必要的,也是充分的。這意味著:既然接受了該DL任務(wù),那么CPU有信心可以保證其在截止日期之前能夠分配給它需要的runtime長(zhǎng)度的CPU時(shí)間。
deadline調(diào)度器是僅僅根據(jù)實(shí)時(shí)任務(wù)的時(shí)序約束進(jìn)行調(diào)度的,從而保證實(shí)時(shí)任務(wù)正確的邏輯行為。雖然在多核系統(tǒng)中,全局deadline調(diào)度器會(huì)面臨Dhall效應(yīng)(把若干個(gè)任務(wù)分配給若干個(gè)處理器執(zhí)行其實(shí)是一個(gè)NP-hard問題(本質(zhì)上是一個(gè)裝箱問題),由于各種異常場(chǎng)景,很難說一個(gè)調(diào)度算法會(huì)優(yōu)于任何其他的算法),不過我們?nèi)匀豢梢詫?duì)系統(tǒng)進(jìn)行分區(qū)來(lái)解決這個(gè)問題。具體的做法是采用cpusets的方法把CPU利用率高的任務(wù)放置到指定的cpuset上。開發(fā)人員也可以受益于deadline調(diào)度器:他們可以通過設(shè)計(jì)其應(yīng)用程序與DL調(diào)度器交互,從而簡(jiǎn)化任務(wù)的時(shí)序控制行為。
在linux中,DL任務(wù)比實(shí)時(shí)任務(wù)(RR和FIFO)具有更高的優(yōu)先級(jí)。這意味著即使是最高優(yōu)先級(jí)的實(shí)時(shí)任務(wù)也會(huì)被DL任務(wù)延遲執(zhí)行。因此,DL任務(wù)不需要考慮來(lái)自實(shí)時(shí)任務(wù)的干擾,但實(shí)時(shí)任務(wù)必須考慮DL任務(wù)的干擾。
Stop_Sched_Class
stop_sched_class用于停止CPU, 一般在SMP系統(tǒng)上使用, 用以實(shí)現(xiàn)負(fù)載平衡和CPU熱插拔. 這個(gè)類有最高的調(diào)度優(yōu)先級(jí),stop調(diào)度器類實(shí)現(xiàn)了Unix的stop_machine 特性, stop_machine 是一個(gè)通信信號(hào) : 在SMP的情況下相當(dāng)于暫時(shí)停止其他的CPU的運(yùn)行, 它讓一個(gè) CPU 繼續(xù)運(yùn)行,而讓所有其他CPU空閑。在單CPU的情況下這個(gè)東西就相當(dāng)于關(guān)中斷。一般來(lái)說,內(nèi)核會(huì)在如下情況下使用stop_machine技術(shù):
Idle_Sched_Class
idle任務(wù)會(huì)被任意進(jìn)程搶占,其專屬調(diào)度器類為idle-task,當(dāng)CPU沒有任務(wù)空閑時(shí),默認(rèn)的idle實(shí)現(xiàn)是hlt指令,hlt指令使CPU處于暫停狀態(tài),等待硬件中斷發(fā)生的時(shí)候恢復(fù),從而達(dá)到節(jié)能的目的。即從處理器C0態(tài)變到 C1態(tài)(見 ACPI標(biāo)準(zhǔn)),讓CPU置為WFI(Wait for interrupt)低功耗狀態(tài),以節(jié)省功耗,多cpu系統(tǒng)中每個(gè)cpu一個(gè)idle進(jìn)程。
組調(diào)度
linux內(nèi)核實(shí)現(xiàn)了control group功能(cgroup,since linux 2.6.24),可以支持將進(jìn)程分組,然后按組來(lái)劃分各種資源。比如:group-1擁有30%的CPU和50%的磁盤IO、group-2擁有10%的CPU和20%的磁盤IO、等等。cgroup支持很多種資源的劃分,CPU資源就是其中之一,這就引出了組調(diào)度,
linux內(nèi)核中,傳統(tǒng)的調(diào)度程序是基于進(jìn)程來(lái)調(diào)度的, 以進(jìn)程為單位來(lái)瓜分CPU資源,如果我們想把進(jìn)程進(jìn)行分組,以進(jìn)程組進(jìn)行瓜分CPU資源,Linux實(shí)現(xiàn)了組調(diào)度架構(gòu)來(lái)實(shí)現(xiàn)這個(gè)需求:
? ? ? ? ? ? ? ? ? ? ? ? ? Linux組調(diào)度實(shí)現(xiàn)架構(gòu)
在linux內(nèi)核中,使用task_group結(jié)構(gòu)來(lái)管理組調(diào)度的組。所有存在的task_group組成一個(gè)樹型結(jié)構(gòu)(與cgroup的目錄結(jié)構(gòu)相對(duì)應(yīng)),task_group可以包含具有任意調(diào)度類別的進(jìn)程(具體來(lái)說是實(shí)時(shí)進(jìn)程和普通進(jìn)程兩種類別),于是task_group需要為每一種調(diào)度策略提供一組調(diào)度結(jié)構(gòu)。這里所說的一組調(diào)度結(jié)構(gòu)主要包括兩個(gè)部分,調(diào)度實(shí)體和運(yùn)行隊(duì)列(兩者都是每CPU一份的)。調(diào)度實(shí)體會(huì)被添加到運(yùn)行隊(duì)列中,對(duì)于一個(gè)task_group,它的調(diào)度實(shí)體會(huì)被添加到其父task_group的運(yùn)行隊(duì)列,因?yàn)楸徽{(diào)度的對(duì)象有task_group和task兩種,所以需要一個(gè)抽象的結(jié)構(gòu)來(lái)代表它們。如果調(diào)度實(shí)體代表task_group,則它的my_q字段指向這個(gè)調(diào)度組對(duì)應(yīng)的運(yùn)行隊(duì)列;否則my_q字段為NULL,調(diào)度實(shí)體代表task。在調(diào)度實(shí)體中與my_q相對(duì)的是X_rq(具體是針對(duì)普通進(jìn)程的cfs_rq和針對(duì)實(shí)時(shí)進(jìn)程的rt_rq),前者指向這個(gè)組自己的運(yùn)行隊(duì)列,里面會(huì)放入它的子節(jié)點(diǎn);后者指向這個(gè)組的父節(jié)點(diǎn)的運(yùn)行隊(duì)列,也就是這個(gè)調(diào)度實(shí)體應(yīng)該被放入的運(yùn)行隊(duì)列;
調(diào)度發(fā)生的時(shí)候,調(diào)度程序從根task_group的運(yùn)行隊(duì)列中選擇一個(gè)調(diào)度實(shí)體。如果這個(gè)調(diào)度實(shí)體代表一個(gè)task_group,則調(diào)度程序需要從這個(gè)組對(duì)應(yīng)的運(yùn)行隊(duì)列繼續(xù)選擇一個(gè)調(diào)度實(shí)體。如此遞歸下去,直到選中一個(gè)進(jìn)程。除非根task_group的運(yùn)行隊(duì)列為空,否則遞歸下去一定能找到一個(gè)進(jìn)程。因?yàn)槿绻粋€(gè)task_group對(duì)應(yīng)的運(yùn)行隊(duì)列為空,它對(duì)應(yīng)的調(diào)度實(shí)體就不會(huì)被添加到其父節(jié)點(diǎn)對(duì)應(yīng)的運(yùn)行隊(duì)列中;
組的調(diào)度策略
組調(diào)度的主要針對(duì)rt(實(shí)時(shí)調(diào)度)和cfs(完全公平調(diào)度)兩種類別:
實(shí)時(shí)進(jìn)程的組調(diào)度
實(shí)時(shí)進(jìn)程是對(duì)CPU有著實(shí)時(shí)性要求的進(jìn)程,它的優(yōu)先級(jí)是跟具體任務(wù)相關(guān)的,完全由用戶來(lái)定義的。調(diào)度器總是會(huì)選擇優(yōu)先級(jí)最高的實(shí)時(shí)進(jìn)程來(lái)運(yùn)行,發(fā)展到組調(diào)度,組的優(yōu)先級(jí)就被定義為“組內(nèi)最高優(yōu)先級(jí)的進(jìn)程所擁有的優(yōu)先級(jí)。
普通進(jìn)程的組調(diào)度支持(Fair Group Scheduling)
2.6.23 引入的 CFS 調(diào)度器對(duì)所有進(jìn)程完全公平對(duì)待。但是依然有個(gè)問題:設(shè)想當(dāng)前機(jī)器有2個(gè)用戶,有一個(gè)用戶跑著9個(gè)進(jìn)程,且都是CPU 密集型進(jìn)程;另一個(gè)用戶只跑著一個(gè) X 進(jìn)程,是交互性進(jìn)程。從 CFS 的角度看,它將平等對(duì)待這 10 個(gè)進(jìn)程,結(jié)果導(dǎo)致的是跑 X 進(jìn)程的用戶受到不公平對(duì)待,他只能得到約 10% 的 CPU 時(shí)間,讓他的體驗(yàn)相當(dāng)差。基于此,組調(diào)度的概念被引入[6]。CFS 處理的不再是一個(gè)進(jìn)程的概念,而是調(diào)度實(shí)體(sched entity),一個(gè)調(diào)度實(shí)體可以只包含一個(gè)進(jìn)程,也可以包含多個(gè)進(jìn)程。因此,上述例子的困境可以這么解決:分別為每個(gè)用戶建立一個(gè)組,組里放該用戶所有進(jìn)程,從而保證用戶間的公平性。該功能是基于控制組(control group, cgroup)的概念,需要內(nèi)核開啟 CGROUP 的支持才可使用。
信號(hào)處理
信號(hào)機(jī)制是進(jìn)程之間相互傳遞消息的一種方法,信號(hào)全稱為軟中斷信號(hào),也有人稱作軟中斷。從它的命名可以看出,它的實(shí)質(zhì)和使用很像中斷。所以,信號(hào)可以說是進(jìn)程控制的一部分:
軟中斷信號(hào)(signal,又簡(jiǎn)稱為信號(hào))用來(lái)通知進(jìn)程發(fā)生了異步事件。進(jìn)程之間可以互相通過系統(tǒng)調(diào)用kill發(fā)送軟中斷信號(hào)。內(nèi)核也可以因?yàn)閮?nèi)部事件而給進(jìn)程發(fā)送信號(hào),通知進(jìn)程發(fā)生了某個(gè)事件。注意,信號(hào)只是用來(lái)通知某進(jìn)程發(fā)生了什么事件,并不給該進(jìn)程傳遞任何數(shù)據(jù);
當(dāng)信號(hào)發(fā)送到某個(gè)進(jìn)程中時(shí),操作系統(tǒng)會(huì)中斷該進(jìn)程的正常流程,并進(jìn)入相應(yīng)的信號(hào)處理函數(shù)執(zhí)行操作,完成后再回到中斷的地方繼續(xù)執(zhí)行;
信號(hào)分類處理, ?第一種是類似中斷的處理程序,對(duì)于需要處理的信號(hào),進(jìn)程可以指定處理函數(shù),由該函數(shù)來(lái)處 理。第二種方法是,忽略某個(gè)信號(hào),對(duì)該信號(hào)不做任何處理,就象未發(fā)生過一樣。第三種方法是,對(duì)該信號(hào)的處理保留系統(tǒng)的默認(rèn)值,這種缺省操作,對(duì)大部分的信 號(hào)的缺省操作是使得進(jìn)程終止。進(jìn)程通過系統(tǒng)調(diào)用signal來(lái)指定進(jìn)程對(duì)某個(gè)信號(hào)的處理行為;
在進(jìn)程表的表項(xiàng)中有一個(gè)軟中斷信號(hào)域,該域中每一位對(duì)應(yīng)一個(gè)信號(hào),當(dāng)有信號(hào)發(fā)送給進(jìn)程時(shí),對(duì)應(yīng)位置位。由此可以看出,進(jìn)程對(duì)不同的信號(hào)可以同時(shí)保留,但對(duì)于同一個(gè)信號(hào),進(jìn)程并不知道在處理之前來(lái)過多少個(gè);
信號(hào)分類:
(1) 與進(jìn)程終止相關(guān)的信號(hào)。當(dāng)進(jìn)程退出,或者子進(jìn)程終止時(shí),發(fā)出這類信號(hào)。?
(2) 與進(jìn)程例外事件相關(guān)的信號(hào)。如進(jìn)程越界,或企圖寫一個(gè)只讀的內(nèi)存區(qū)域(如程序正文區(qū)),或執(zhí)行一個(gè)特權(quán)指令及其他各種硬件錯(cuò)誤。?
(3) 與在系統(tǒng)調(diào)用期間遇到不可恢復(fù)條件相關(guān)的信號(hào)。如執(zhí)行系統(tǒng)調(diào)用exec時(shí),原有資源已經(jīng)釋放,而目前系統(tǒng)資源又已經(jīng)耗盡。?
(4) 與執(zhí)行系統(tǒng)調(diào)用時(shí)遇到非預(yù)測(cè)錯(cuò)誤條件相關(guān)的信號(hào)。如執(zhí)行一個(gè)并不存在的系統(tǒng)調(diào)用。?
(5) 在用戶態(tài)下的進(jìn)程發(fā)出的信號(hào)。如進(jìn)程調(diào)用系統(tǒng)調(diào)用kill向其他進(jìn)程發(fā)送信號(hào)。?
(6) 與終端交互相關(guān)的信號(hào)。如用戶關(guān)閉一個(gè)終端,或按下break鍵等情況。?
(7) 跟蹤進(jìn)程執(zhí)行的信號(hào)。?
多線程信號(hào)處理:
不要在線程的信號(hào)掩碼中阻塞不能被忽略處理的兩個(gè)信號(hào) SIGSTOP 和 SIGKILL。
不要在線程的信號(hào)掩碼中阻塞 SIGFPE、SIGILL、SIGSEGV、SIGBUS。
確保 sigwait() 等待的信號(hào)集已經(jīng)被進(jìn)程中所有的線程阻塞。
在主線程或其它工作線程產(chǎn)生信號(hào)時(shí),必須調(diào)用 kill() 將信號(hào)發(fā)給整個(gè)進(jìn)程,而不能使用 pthread_kill() 發(fā)送某個(gè)特定的工作線程,否則信號(hào)處理線程無(wú)法接收到此信號(hào)。
因?yàn)?sigwait()使用了串行的方式處理信號(hào)的到來(lái),為避免信號(hào)的處理存在滯后,或是非實(shí)時(shí)信號(hào)被丟失的情況,處理每個(gè)信號(hào)的代碼應(yīng)盡量簡(jiǎn)潔、快速,避免調(diào)用會(huì)產(chǎn)生阻塞的庫(kù)函數(shù)。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ?? 線程
? ??
線程(英語(yǔ):thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。大部分情況下,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。在Unix System V及SunOS中也被稱為輕量進(jìn)程(lightweight processes),但輕量進(jìn)程更多指內(nèi)核線程(kernel thread),而把用戶線程(user thread)稱為線程。一個(gè)進(jìn)程的組成實(shí)體可以分為兩大部分:線程集和資源集。進(jìn)程中的線程是動(dòng)態(tài)的對(duì)象;代表了進(jìn)程指令的執(zhí)行。資源,包括地址空間、打開的文件、用戶信息等等,由進(jìn)程內(nèi)的線程共享。
????????????????????????????? 線程和進(jìn)程的關(guān)系
根據(jù)操作系統(tǒng)內(nèi)核是否對(duì)線程可感知,可以把線程分為內(nèi)核線程和用戶線程
分類的標(biāo)準(zhǔn)主要是線程的調(diào)度者在核內(nèi)還是在核外。前者更利于并發(fā)使用多處理器的資源,而后者則更多考慮的是上下文切換開銷。Linux內(nèi)核只提供了輕量進(jìn)程的支持,限制了更高效的線程模型的實(shí)現(xiàn),但Linux著重優(yōu)化了進(jìn)程的調(diào)度開銷,一定程度上也彌補(bǔ)了這一缺陷。目前最流行的線程機(jī)制LinuxThreads所采用的就是“線程-進(jìn)程”一對(duì)一模型(還存在多對(duì)一,多對(duì)多模型)用戶級(jí)實(shí)現(xiàn)一個(gè)包括信號(hào)處理在內(nèi)的線程管理機(jī)制。
? ? ? ? Linux 線程實(shí)現(xiàn)采用內(nèi)核級(jí)線程”一對(duì)一”模型
在linux系統(tǒng)下,無(wú)論是進(jìn)程,還是線程,到了內(nèi)核里面,我們統(tǒng)一都叫任務(wù)(Task),由一個(gè)統(tǒng)一的結(jié)構(gòu) task_struct 進(jìn)行管理。一個(gè)進(jìn)程由于其運(yùn)行空間的不同,從而有內(nèi)核線程和用戶進(jìn)程的區(qū)分。內(nèi)核線程運(yùn)行在內(nèi)核空間,之所以稱之為線程是因?yàn)樗鼪]有虛擬地址空間,只能訪問內(nèi)核的代碼和數(shù)據(jù);?而用戶進(jìn)程則運(yùn)行在用戶空間, 不能直接訪問內(nèi)核的數(shù)據(jù)但是可以通過中斷,系統(tǒng)調(diào)用等方式從用戶態(tài)陷入內(nèi)核態(tài),但是內(nèi)核態(tài)只是進(jìn)程的一種狀態(tài), 與內(nèi)核線程有本質(zhì)區(qū)別,用戶進(jìn)程運(yùn)行在用戶空間上,而一些通過共享資源實(shí)現(xiàn)的一組進(jìn)程我們稱之為線程組。Linux下內(nèi)核其實(shí)本質(zhì)上沒有線程的概念,Linux下線程其實(shí)上是與其他進(jìn)程共享某些資源的進(jìn)程而已。但是我們習(xí)慣上還是稱它們?yōu)榫€程或者輕量級(jí)進(jìn)程。
內(nèi)核線程
內(nèi)核線程是直接由內(nèi)核本身啟動(dòng)的進(jìn)程。內(nèi)核線程實(shí)際上是將內(nèi)核函數(shù)委托給獨(dú)立的進(jìn)程,它與內(nèi)核中的其他進(jìn)程”并行”執(zhí)行。內(nèi)核線程經(jīng)常被稱之為內(nèi)核守護(hù)進(jìn)程,他們執(zhí)行下列任務(wù):
周期性地將修改的內(nèi)存頁(yè)與頁(yè)來(lái)源塊設(shè)備同步;
如果內(nèi)存頁(yè)很少使用,則寫入交換區(qū);
管理延時(shí)動(dòng)作, 如2號(hào)進(jìn)程接手內(nèi)核進(jìn)程的創(chuàng)建;
實(shí)現(xiàn)文件系統(tǒng)的事務(wù)日志;
…
內(nèi)核線程主要有兩種類型:
線程啟動(dòng)后一直等待,直至內(nèi)核請(qǐng)求線程執(zhí)行某一特定操作。
線程啟動(dòng)后按周期性間隔運(yùn)行,檢測(cè)特定資源的使用,在用量超出或低于預(yù)置的限制時(shí)采取行動(dòng)。
內(nèi)核線程由內(nèi)核自身生成,其特點(diǎn)在于:
它們?cè)贑PU的內(nèi)核態(tài)執(zhí)行,而不是用戶態(tài);
它們只可以訪問虛擬地址空間的內(nèi)核部分(高于TASK_SIZE的所有地址),但不能訪問用戶空間。
Linux在內(nèi)核線程架構(gòu)設(shè)計(jì)中,內(nèi)核線程建立和銷毀都是由操作系統(tǒng)負(fù)責(zé)、通過系統(tǒng)調(diào)用完成的。在內(nèi)核的支持下運(yùn)行,無(wú)論是用戶進(jìn)程的線程,或者是系統(tǒng)進(jìn)程的線程,他們的創(chuàng)建、撤銷、切換都是依靠?jī)?nèi)核實(shí)現(xiàn)的。線程管理的所有工作由內(nèi)核完成,應(yīng)用程序沒有進(jìn)行線程管理的代碼,只有一個(gè)到內(nèi)核級(jí)線程的編程接口. 內(nèi)核為進(jìn)程及其內(nèi)部的每個(gè)線程維護(hù)上下文信息,調(diào)度也是在內(nèi)核基于線程架構(gòu)的基礎(chǔ)上完成。內(nèi)核線程駐留在內(nèi)核空間,它們是內(nèi)核對(duì)象。
內(nèi)核線程就是內(nèi)核的分身,一個(gè)分身可以處理一件特定事情。Linux內(nèi)核使用內(nèi)核線程來(lái)將內(nèi)核分成幾個(gè)功能模塊,像kworker, ?kswapd, ksoftirqd, ?migration , rcu_bh,rcu_schd, watchdog等(內(nèi)核線程都用[] 括起來(lái))。這在處理異步事件如異步IO,阻塞任務(wù),延后任務(wù)處理時(shí)特別有用。內(nèi)核線程的使用是廉價(jià)的,唯一使用的資源就是內(nèi)核棧和上下文切換時(shí)保存寄存器的空間,內(nèi)核線程只運(yùn)行在內(nèi)核態(tài),不受用戶態(tài)上下文的拖累,在多核系統(tǒng)中,很多內(nèi)核線程都是per cpu運(yùn)行粒度。
用戶線程
Linux內(nèi)核只提供了輕量級(jí)進(jìn)程(LWP)的方式支持用戶線程,限制了更高效的線程模型的實(shí)現(xiàn),但Linux著重優(yōu)化了進(jìn)程的調(diào)度開銷,一定程度上也彌補(bǔ)了這一缺陷。目前最流行的線程機(jī)制LinuxThreads所采用的就是線程-進(jìn)程”一對(duì)一”模型,調(diào)度交給核心,而在用戶級(jí)實(shí)現(xiàn)一個(gè)包括信號(hào)處理在內(nèi)的線程管理機(jī)制。輕量級(jí)進(jìn)程由clone()系統(tǒng)調(diào)用創(chuàng)建,參數(shù)是CLONE_VM,即與父進(jìn)程是共享進(jìn)程地址空間和系統(tǒng)資源。
LinuxThreads是用戶空間的線程庫(kù),所采用的是“線程-進(jìn)程”1對(duì)1模型(即一個(gè)用戶線程對(duì)應(yīng)一個(gè)輕量級(jí)進(jìn)程,而一個(gè)輕量級(jí)進(jìn)程對(duì)應(yīng)一個(gè)特定的內(nèi)核線程),將線程的調(diào)度等同于進(jìn)程的調(diào)度,調(diào)度交由內(nèi)核完成, 有了內(nèi)核線程,每個(gè)用戶線程被映射或綁定到一個(gè)內(nèi)核線程。用戶線程在其生命期內(nèi)都會(huì)綁定到該內(nèi)核線程。調(diào)度器管理、調(diào)度并分派這些線程。運(yùn)行時(shí)庫(kù)為每個(gè)用戶級(jí)線程請(qǐng)求一個(gè)內(nèi)核級(jí)線程。而線程的創(chuàng)建、同步、銷毀由核外線程庫(kù)完成(LinuxThtreads已綁定到 GLIBC中發(fā)行)。??在LinuxThreads中,由專門的一個(gè)管理線程處理所有的線程管理工作。當(dāng)進(jìn)程第一次調(diào)用pthread_create()創(chuàng)建線程時(shí)就會(huì)先 創(chuàng)建(clone())并啟動(dòng)管理線程。后續(xù)進(jìn)程pthread_create()創(chuàng)建線程時(shí),都是管理線程作為pthread_create()的調(diào)用者的子線程,通過調(diào)用clone()來(lái)創(chuàng)建用戶線程,并記錄輕量級(jí)進(jìn)程號(hào)和線程id的映射關(guān)系,因此用戶線程其實(shí)是管理線程的子線程。LinuxThreads只支持調(diào)度范圍為PTHREAD_SCOPE_SYSTEM的調(diào)度,默認(rèn)的調(diào)度策略是SCHED_OTHER。?用戶線程調(diào)度策略也可修改成SCHED_FIFO或SCHED_RR方式,這兩種方式支持優(yōu)先級(jí)為0-99,而SCHED_OTHER只支持0。??
? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? Linux?輕量級(jí)進(jìn)程實(shí)現(xiàn)
LinuxThtreads的設(shè)計(jì)存在一些局限性,導(dǎo)致后面Linux實(shí)現(xiàn)了新的線程庫(kù)NPTL,或稱為 Native POSIX Thread Library,是 Linux 線程的一個(gè)新實(shí)現(xiàn),它克服了 LinuxThreads 的缺點(diǎn),同時(shí)也符合 POSIX 的需求。與 LinuxThreads 相比,它在性能和穩(wěn)定性方面都提供了重大的改進(jìn)。與 LinuxThreads 一樣,NPTL 也實(shí)現(xiàn)了一對(duì)一的模型。
輕量級(jí)進(jìn)程具有局限性:
首先,大多數(shù)LWP的操作,如建立、析構(gòu)以及同步,都需要進(jìn)行系統(tǒng)調(diào)用。系統(tǒng)調(diào)用的代價(jià)相對(duì)較高:需要在user mode和kernel mode中切換。
其次,每個(gè)LWP都需要有一個(gè)內(nèi)核線程支持,因此LWP要消耗內(nèi)核資源(內(nèi)核線程的棧空間)。因此一個(gè)系統(tǒng)不能支持大量的LWP。
優(yōu)點(diǎn):
(1)運(yùn)行代價(jià):LWP只有一個(gè)最小的執(zhí)行上下文和調(diào)度程序所需的統(tǒng)計(jì)信息。
(2)處理器競(jìng)爭(zhēng):因與特定內(nèi)核線程關(guān)聯(lián),因此可以在全系統(tǒng)范圍內(nèi)競(jìng)爭(zhēng)處理器資源。
(3)使用資源:與父進(jìn)程共享進(jìn)程地址空間。
(4)調(diào)度:像普通進(jìn)程一樣調(diào)度。
協(xié)程
以上描述的不管是中斷,進(jìn)程,線程(內(nèi)核線程,用戶線程(輕量級(jí)進(jìn)程實(shí)現(xiàn)))的調(diào)度都是由內(nèi)核掌控,用戶并不能直接干預(yù),要在用戶態(tài)實(shí)現(xiàn)對(duì)邏輯調(diào)度控制,需要實(shí)現(xiàn)類似用戶級(jí)線程,用戶級(jí)線程是完全建立在用戶空間的線程庫(kù),用戶線程的創(chuàng)建、調(diào)度、同步和銷毀全部庫(kù)函數(shù)在用戶空間完成,不需要內(nèi)核的幫助。因此這種線程是極其低消耗和高效的。協(xié)程本質(zhì)上也是一種用戶級(jí)線程實(shí)現(xiàn),在一個(gè)線程(內(nèi)核執(zhí)行單元)內(nèi),協(xié)程通過主動(dòng)放棄時(shí)間片交由其他協(xié)程執(zhí)行來(lái)協(xié)作,故名協(xié)程。協(xié)程的一些關(guān)鍵點(diǎn):
任何代碼執(zhí)行都需要上下文(CPU),協(xié)程也有自己的上下文,協(xié)程切換只涉及基本的CPU上下文切換, 完全在用戶空間進(jìn)行,沒有模式切換,所以比線程切換要小,開源libco 的協(xié)程切換的匯編代碼,也就是二十來(lái)?xiàng)l匯編指令,一般切換代價(jià):?協(xié)程 < 線程 < 系統(tǒng)調(diào)用 < 進(jìn)程 ;
在多核多線程系統(tǒng)中,線程切換代價(jià)比較高(cache miss等),為了減少線程切換,希望在同一個(gè)線程內(nèi)進(jìn)行不同邏輯的偽并行(實(shí)際上還是串行),這樣降低了代碼邏輯切換代價(jià),但這樣并沒有擁有真正并發(fā)帶來(lái)的高性能,選擇合適的使用場(chǎng)景;
協(xié)程存在兼容性問題,協(xié)程分為有棧協(xié)程和無(wú)棧協(xié)程實(shí)現(xiàn),對(duì)不同系統(tǒng)和處理器要進(jìn)行兼容,但不同系統(tǒng)對(duì)上下文實(shí)現(xiàn),異常等不一樣,這樣就可能產(chǎn)生不兼容情況,一般用戶態(tài)的代碼都不關(guān)心底層差別,而使用協(xié)程后的代碼兼容性變差。
?
本期結(jié)束,我們下期再見!
想要獲取linux調(diào)度全景指南精簡(jiǎn)版,關(guān)注公眾號(hào)回復(fù)“調(diào)度”即可獲取。回復(fù)其他消息,獲取更多內(nèi)容;
? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ??
往期推薦
Linux調(diào)度系統(tǒng)全景指南(上篇)
Linux調(diào)度系統(tǒng)全景指南(中篇)
如何成為一名大廠的優(yōu)秀員工?
C++模版的本質(zhì)
C++內(nèi)存管理全景指南
云網(wǎng)絡(luò)丟包故障定位全景指南
? ? ? ? ? ?
掃碼二維碼
獲取更多精彩內(nèi)容
總結(jié)
以上是生活随笔為你收集整理的Linux调度系统全景指南(下篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux调度系统全景指南(中篇)
- 下一篇: Linux调度系统全景指南(终结篇)