【Linux進程、線程、任務調度】三 CPU/IO消耗型進程 吞吐率/響應 SCHED_FIFO算法與SCHED_RR算法 SCHED_NORMAL算法和CFS算法 nice與renic chrt

  • 學習交流加(可免費幫忙下載CSDN資源):
  • 個人微信: liu1126137994
  • 學習交流資源分享qq羣1(已滿): 962535112
  • 學習交流資源分享qq羣2(已滿): 780902027
  • 學習交流資源分享qq羣3:893215882

上一篇文章(點擊鏈接:【Linux進程、線程、任務調度】二)講了:

  • fork vfork clone 的含義
  • 寫時拷貝技術
  • Linux線程的實現本質
  • 進程0 和 進程1
  • 進程的睡眠和等待隊列
  • 孤兒進程的託孤 ,SUBREAPER

本篇文章接着上一篇文章記錄以下學習內容:

  • CPU/IO消耗型進程
  • 吞吐率 vs. 響應
  • SCHED_FIFO算法 與 SCHED_RR算法
  • SCHED_NORMAL算法 和 CFS算法
  • nice與renice
  • chrt

本篇文章主要講解Linux系統調度器。對於調度器來說,我們需要考慮的有:吞吐與響應,CPU消耗型與IO消耗型進程。這四點都是對於調度算法的輸入來說的。

同時,調度器的單位是線程,但是線程,我們知道在第一篇文章中已經講解了,線程是一種輕量級的進程。所以本篇文章在說調度的對象時說的都是進程,但是我們要理解,線程纔是調度單位。這並不矛盾!!!

1、吞吐 vs. 響應

吞吐和響應之間的矛盾

  • 響應:最小化某個任務的響應時間,哪怕以犧牲其他任務爲代價
  • 吞吐:全局視野,整個系統的workload被最大化處理

首先我們在考慮調度器的時候,我們要理解操作系統的調度器設計目標追求兩點:吞吐率大和延遲低。

這兩點是相互矛盾的。因爲吞吐率大,勢必要把更多的時間放到真實有用功上而不是把時間浪費在進程上下文切換上而延遲低,勢必要求優先級高的進程可以隨時搶佔進來,打斷別人,強行插隊。但是上下文切換的時間,對吞吐率來講,本身是一個消耗。花了很多時間在上下文切換上,相當於做了很多無用功。這種消耗可以低到2us或者更低(這看起來沒什麼?),但是上下文切換更大的消耗不是切換本身,而是切換導致的cache miss。本身跑微博跑的好好的,現在要切換過去跑微信,CPU的cache,是很難命中微信的。

不搶佔,肯定響應差,搶了又會導致吞吐率下降。

Linux系統不是一個完全照顧吞吐的系統,也不是一個完全照顧相應的的系統。它作爲一個軟實時系統,實際上是想達到某種平衡(後面會講)。同時也提供給用戶一定的配置能力。在內核編譯的時候,Kernel Features —> Preemption Model選項實際上可以讓我們編譯內核的時候,是傾向於支持吞吐,還是支持響應:

越往上面選,吞吐越好,越好下面選,響應越好。服務器你一個月也難得用一次鼠標,而桌面則顯然要求一定的響應,這樣可以保證UI行爲的表現較好。但是Linux即便選擇的是最後一個選項“Preemptible Kernel (Low-Latency Desktop)”,它仍然不是硬實時的(下一篇文章講解爲何Linux系統不是一個硬實時系統)。

2、IO消耗型 vs. CPU消耗型

進程分爲:IO消耗型進程與CPU消耗型進程

  • IO消耗型(狂睡,等IO資源等):CPU利用率低,進程的運行效率主要受限於I/O速度
  • CPU消耗型(狂算):多數時間花在CPU上面(做運算)

一般而言,IO消耗型任務對延遲比較敏感,應該被優先調度。它雖然時間都花在IO上,不關心CPU的性能,但是它關心的是是否能夠及時的拿到CPU的使用權。也就是是否可以及時的被CPU調度。當自己完成了IO操作,但是一直不被CPU調度,那肯定也是不行的。比如,你正在瘋狂編譯安卓系統,而等鼠標行爲的用戶界面老不工作(正在狂睡),但是鼠標一點,我們應該優先打斷正在編譯的進程,而去響應鼠標這個I/O,這樣電腦的用戶體驗才符合人性。

3、實時進程調度

在早期2.6內核時,調度器使用的是優先級數組和Bitmaps

  • 優先級號一共有0-139,其中0-99的是RT(實時)進程,100-139的是非實時進程。
  • 某個優先級有TASK_RUNNING進程,響應bit設置爲1.
  • 調度第一個bitmap設置爲1的進程。

對於Linux的RT進程,按照SCHED_FIFO和SCHED_RR的策略。

  • SCHED_FIFO:不同優先級按照優先級高的先跑到睡眠,優先級低的再跑。同等優先級的先進先出,先ready的跑到睡,後ready的接着跑。
  • SCHED_RR:不同優先級按照優先級高的先跑到睡眠,優先級低的再跑。同等優先級的進行時間片輪轉。

比如Linux存在如下4個進程,T1~T4(內核裏面優先級數字越低,優先級越高):

那麼它們在Linux的跑法就是:
在這裏插入圖片描述

當然,Linux中大多數的進程都不是RT的,而是非RT的普通進程。並且,就算有RT的進程一直存在,CPU也不會一直被RT進程霸佔,必須給普通進程留有一定的時間片。在Linux中存在一個RT門限。

在sched_rt_period_us時間內,RT進程最多跑sched_rt_runtime_us時間,剩下的時間必須留給非RT進程使用。

在Linux系統中上述兩個時間在如下位置:
/proc/sys/kernel/sched_rt_period_us
/proc/sys/kernel/sched_rt_runtime_us

4、非實時進程的調度

4.1 早期2.6內核:

  • 在不同的優先級進行時間片輪轉
  • -20 ~ 19的nice值
  • 根據睡眠情況,動態獎勵和懲罰

普通進程調度,進程不會像RT進程那樣一致霸佔CPU,而是所有的進程都輪轉獲得CPU,只是當優先級高的話,可獲得更多的時間片,醒來後可以搶佔優先級低的進程。

但是,當進程的CPU佔有率高,或者一開始的優先級高的話,後面內核會降低它的優先級,這樣可以讓IO消耗型的進程能夠競爭過CPU消耗型的進程。
從而保障IO消耗型的進程能夠及時獲得CPU使用權。

4.2 CFS完全公平調度策略

新內核採用的調度策略就不是那麼簡單了,爲了很好的統一CPU消耗型與IO消耗型進程的調度,與優先級(nice值)的協調,新內核採用了一種策略:完全公平調度策略。

注意:完全公平調度策略是針對普通進程而言的。

完全公平調度策略,內部的實現使用的是紅黑樹。左邊節點的值小於右邊節點的值。

紅黑樹節點的值爲vruntime,進程的虛擬運行時間。

	vruntime =  pruntime * NICE_0_LOAD/ weight
  • pruntime:進程的物理運行時間,即實際的運行時間
  • weight:權重
  • NICE_0_LOAD:參數,等於1024,也是nice值爲0的權重

nice值與weight值的對應關係:

CFS調度策略:

當RT進程都睡眠了(或者RT進程已經跑了超過了sched_rt_runtime_us時間值),那麼就該普通進程被調度了。Linux最先調度vruntime最小的進程,也就是位於紅黑樹最左邊的進程。假設最先調度的進程是p1,那麼隨着p1的運行,p1的pruntime就會變大,就會導致p1的vruntime就會變大,那麼p1在紅黑樹中的位置就會往右移動。下一次,就會調度最新的vruntime最小的進程(最新的紅黑樹最左邊的進程)。

那麼什麼樣的線程最容易被調度呢?由上述公式知,當pruntime小,weight值大的時候,vruntime小,最容易被調度到。而pruntime小意味着是IO消耗型進程,weight值大的意味着是nice值小(優先級高)的進程。

這樣的話,我們就可以看到,紅黑樹很神奇的同時照顧了普通進程的CPU/IO消耗型與優先級(nice值)的情況。

比如有4個普通進程,如下表,目前顯然T1的vruntime最小(這是它喜歡睡的結果),然後T1被調度到。

pruntime Weight vruntime
T1 8 1024(nice=0) 8*1024/1024=8
T2 10 526 (nice=3) 10*1024/526=19
T3 20 1024(nice=0) 20*1024/1024=20
T4 20 820 (nice=1) 20*1024/820=24

然後,我們假設T1被調度再執行12個pruntime,它的vruntime將增大delta*1024/weight(這裏delta是12,weight是1024),於是T1的vruntime成爲20,那麼這個時候vruntime最小的反而是T2(爲19),此後,Linux將傾向於調度T2(儘管T2的nice值大於T1,優先級低於T1,但是它的vruntime現在只有19)。

所以普通進程的調度,是一個綜合考慮IO/CPU消耗型與優先級的,通俗點說就是考慮你喜歡睡還是喜歡幹活,以及你的nice值是多少(優先級高不高)。所以,Linux中進程的調度時間是不確定的,它具有隨機性,無法判斷一個進程的調度的延遲,更無法判斷一個進程什麼時候會被調度到,它是需要看看當前系統中是否還有其他進程在跑,以及被喚醒的進程的nice值以及它之前喜不喜歡睡覺!!!

還有一點要注意:普通進程在跑,如果突然有一個RT進程過來了,那麼RT進程就是無敵的,它會被調度,直到它運行結束或者睡眠。

5、工具chrt和renice

chrt工具可以設置進程的調度策略與優先級,nice和renice可設置進程的nice值。renice是程序已經跑起來了你可以去設置nice值。

看一下程序:
two-loops.c

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>

void *thread_fun(void *param)
{
	printf("thread pid:%d, tid:%lu\n", getpid(), pthread_self());
	while (1) ;
	return NULL;
}

int main(void)
{
	pthread_t tid1, tid2;
	int ret;

	printf("main pid:%d, tid:%lu\n", getpid(), pthread_self());

	ret = pthread_create(&tid1, NULL, thread_fun, NULL);
	if (ret == -1) {
		perror("cannot create new thread");
		return 1;
	}

	ret = pthread_create(&tid2, NULL, thread_fun, NULL);
	if (ret == -1) {
		perror("cannot create new thread");
		return 1;
	}

	if (pthread_join(tid1, NULL) != 0) {
		perror("call pthread_join function fail");
		return 1;
	}

	if (pthread_join(tid2, NULL) != 0) {
		perror("call pthread_join function fail");
		return 1;
	}

	return 0;
}

  • 編譯並在後天運行兩份:

    $ gcc two-loops.c -pthread

在這裏插入圖片描述

top命令查看CPU利用率:
在這裏插入圖片描述

可以看到,運行的兩個程序的CPU利用率都接近百分之百。

renice其中之一,再觀察CPU利用率

可以很明顯的看到,renice修改了進程的優先級,導致進程的CPU佔有率變了。

  • 編譯並在後臺運行一份

在這裏插入圖片描述

top查看器CPU利用率接近百分之200:
在這裏插入圖片描述

把它所有的線程設置爲SCHED_FIFO調度策略,優先級爲50:

$ chrt -f  -p 50 3110
  • -f代表FIFO,-p代表進程pid 50爲設置的優先級
    然後會發現進程的CPU利用率會降低一些。

當然設置調度策略(SCHED_FIFO)和RT優先級,除了可以用chrt工具,還可以直接在代碼裏寫:
在這裏插入圖片描述

6、總結

掌握以下內容:

  • CPU消耗型與IO消耗型
  • 吞吐與響應的關係
  • SCHED_FIFO 與SCHED_RR調度策略
  • SCHED_NORMAL和CFS完全公平調度策略
  • nice和renice
  • chrt工具
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章