1. 程式人生 > >我就是認真:Linux的程序優先順序 NI 和 PR 有什麼區別?

我就是認真:Linux的程序優先順序 NI 和 PR 有什麼區別?

請及時關注“高效運維(微信ID:greatops)”公眾號,並置頂公眾號,以免錯過各種乾貨滿滿的原創文章。

作者簡介

鄒立巍

Linux系統技術專家。目前在騰訊SNG社交網路運營部 計算資源平臺組,負責內部私有云平臺的建設和架構規劃設計。

曾任新浪動態應用平臺系統架構師,負責微博、新浪部落格等重點業務的內部私有云平臺架構設計和運維管理工作。

為什麼要有程序優先順序?

這似乎不用過多的解釋,畢竟自從多工作業系統誕生以來,程序執行佔用cpu的能力就是一個必須要可以人為控制的事情。因為有的程序相對重要,而有的程序則沒那麼重要。

程序優先順序起作用的方式從發明以來基本沒有什麼變化,無論是隻有一個cpu的時代,還是多核cpu時代,都是通過控制程序佔用cpu時間的長短來實現的。

就是說在同一個排程週期中,優先順序高的程序佔用的時間長些,而優先順序低的程序佔用的短些。

請大家真的不要混淆了系統中的這兩個概念:nice(NI)和priority(PR),他們有著千絲萬縷的關係,但對於當前的Linux系統來說,它們並不是同一個概念。

我們看這個命令:

linux

大家是否真的明白其中PRI列NI列的具體含義有什麼區別?

同樣的,如果是top命令:

linux

大家是否搞清楚了這其中PR值NI值的差別?如果沒有,那麼我們可以首先搞清楚什麼是nice值。

什麼是NICE值?

NICE值應該是熟悉Linux/UNIX的人很瞭解的概念了,它是反應一個程序“優先順序”狀態的值,其取值範圍是-20至19,一共40個級別。

這個值越小,表示程序”優先順序”越高,而值越大“優先順序”越低。

例如,我們可以通過NICE命令來對一個將要執行的bash命令進行NICE值設定,方法是:

[[email protected] zorro]# nice -n 10 bash

這樣我就又打開了一個bash,並且其nice值設定為10,而預設情況下,程序的優先順序應該是從父程序繼承來的,這個值一般是0。

我們可以通過nice命令直接檢視到當前shell的nice值:

[[email protected] zorro]# nice 10

對比一下正常情況:

[[email protected] zorro]# exit

退出當前nice值為10的bash,開啟一個正常的bash,我們檢視下其 Nice值:

[[email protected] zorro]# bash

[[email protected] zorro]# nice 0

另外,使用renice命令可以對一個正在執行的程序進行nice值的調整,我們也可以使用比如top、ps等命令檢視程序的nice值,具體方法我就不多說了,大家可以參閱相關man page。

需要大家注意的是,我在這裡都在使用nice值這一稱謂,而非優先順序(priority)這個說法。

nice值雖然不是priority,但是它確實可以影響程序的優先順序

在英語中,如果我們形容一個人nice,那一般說明這個人的人緣比較好。什麼樣的人人緣好?往往是謙讓、有禮貌的人。

比如,你跟一個nice的人一起去吃午飯,點了兩個一樣的飯,先上了一份後,nice的那位一般都會說:“你先吃你先吃!”,這就是人緣好,這人nice!但是如果另一份上的很晚,那麼這位nice的人就要餓著了。

這說明什麼?

越nice的人搶佔資源的能力就越差,而越不nice的人搶佔能力就越強。這就是nice值大小的含義,nice值越低,說明程序越不nice,搶佔cpu的能力就越強,優先順序就越高(作者這個解釋太形象了,小編忍不住要手動點贊!!)。

在原來使用O1排程的Linux上,我們還會把nice值叫做靜態優先順序,這也基本符合nice值的特點,就是當nice值設定好了之後,除非我們用renice去改它,否則它是不變的。

而priority的值在之前核心的O1排程器上表現是會變化的,所以也叫做動態優先順序。

什麼是優先順序和實時程序?

我們再來看看什麼是priority值,就是ps命令中看到的PRI值或者top命令中看到的PR值。

本文為了區分這些概念,以後:

統一用nice值表示NI值,或者叫做靜態優先順序,也就是用nice和renice命令來調整的優先順序;

而實用priority值表示PRI和PR值,或者叫動態優先順序。

我們也統一將“優先順序”這個詞的概念規定為表示priority值的意思。

在核心中,程序優先順序的取值範圍是通過一個巨集定義的,這個巨集的名稱是MAX_PRIO,它的值為140。

而這個值又是由另外兩個值相加組成的,一個是代表nice值取值範圍的NICE_WIDTH巨集,另一個是代表實時程序(realtime)優先順序範圍的MAX_RT_PRIO巨集。

說白了就是,Linux實際上實現了140個優先順序範圍,取值範圍是從0-139,這個值越小,優先順序越高。nice值的-20到19,對映到實際的優先順序範圍是100-139。

新產生程序的預設優先順序被定義為:

#define DEFAULT_PRIO   (MAX_RT_PRIO + NICE_WIDTH / 2)

實際上對應的就是nice值的0。

正常情況下,任何一個程序的優先順序都是這個值,即使我們通過nice和renice命令調整了程序的優先順序,它的取值範圍也不會超出100-139的範圍,除非這個程序是一個實時程序,那麼它的優先順序取值才會變成0-99這個範圍中的一個。

這裡隱含了一個資訊,就是說當前的Linux是一種已經支援實時程序的作業系統。

什麼是實時作業系統

我們就不再這裡詳細解釋其含義以及在工業領域的應用了,有興趣的可以參考一下實時作業系統的維基百科。

簡單來說,實時作業系統需要保證相關的實時程序在較短的時間內響應,不會有較長的延時,並且要求最小的中斷延時和程序切換延時。

對於這樣的需求,一般的程序排程演算法,無論是O1還是CFS都是無法滿足的,所以核心在設計的時候,將實時程序單獨映射了100個優先順序,這些優先順序都要高於正常程序的優先順序(nice值),而實時程序的排程演算法也不同,它們採用更簡單的排程演算法來減少排程開銷。

總的來說,Linux系統中執行的程序可以分成兩類:

  • 實時程序
  • 非實時程序

它們的主要區別就是通過優先順序來區分的。

所有優先順序值在0-99範圍內的,都是實時程序,所以這個優先順序範圍也可以叫做實時程序優先順序,而100-139範圍內的是非實時程序

在系統中可以使用chrt命令來檢視、設定一個程序的實時優先順序狀態。我們可以先來看一下chrt命令的使用:

linux

我們先來關注顯示出的Policy options部分,會發現系統給各種程序提供了5種排程策略。

但是這裡並沒有說明的是,這五種排程策略是分別給兩種程序用的,對於實時程序可以用的排程策略是:SCHED_FIFO、SCHED_RR,而對於非實時程序則是:SCHED_OTHER、SCHED_OTHER、SCHED_IDLE。

系統的整體優先順序策略是:

  • 如果系統中存在需要執行的實時程序,則優先執行實時程序;
  • 直到實時程序退出或者主動讓出CPU時,才會排程執行非實時程序。

實時程序可以指定的優先順序範圍為1-99,將一個要執行的程式以實時方式執行的方法為:

[[email protected] zorro]# chrt 10 bash

[[email protected] zorro]# chrt -p $$

pid 14840’s current scheduling policy: SCHED_RR

pid 14840’s current scheduling priority: 10

可以看到,新開啟的bash已經是實時程序,預設排程策略為SCHED_RR,優先順序為10。如果想修改排程策略,就加個引數:

[[email protected] zorro]# chrt -f 10 bash

[[email protected] zorro]# chrt -p $$

pid 14843’s current scheduling policy: SCHED_FIFO

pid 14843’s current scheduling priority: 10

剛才說過,SCHED_RR和SCHED_FIFO都是實時排程策略,只能給實時程序設定。對於所有實時程序來說,優先順序高的(就是priority數字小的)程序一定會保證先於優先順序低的程序執行。

SCHED_RR和SCHED_FIFO的排程策略只有當兩個實時程序的優先順序一樣的時候才會發生作用,其區別也是顧名思義:

SCHED_FIFO

以先進先出的佇列方式進行排程,在優先順序一樣的情況下,誰先執行的就先排程誰,除非它退出或者主動釋放CPU。

SCHED_RR

以時間片輪轉的方式對相同優先順序的多個程序進行處理。時間片長度為100ms。

這就是Linux對於實時程序的優先順序和相關排程演算法的描述。整體很簡單,也很實用。

而相對更麻煩的是非實時程序,它們才是Linux上程序的主要分類。對於非實時程序優先順序的處理,我們首先還是要來介紹一下它們相關的排程演算法:O1和CFS。

什麼是O1排程?

O1排程演算法是在Linux 2.6開始引入的,到Linux 2.6.23之後核心將排程演算法替換成了CFS。

雖然O1演算法已經不是當前核心所預設使用的排程演算法了,但是由於大量線上的伺服器可能使用的Linux版本還是老版本,所以我相信很多伺服器還是在使用著O1排程器,那麼費一點口舌簡單交代一下這個排程器也是有意義的。

這個排程器的名字之所以叫做O1,主要是因為其演算法的時間複雜度是O1。

O1排程器仍然是根據經典的時間片分配的思路來進行整體設計的。

簡單來說,時間片的思路就是將CPU的執行時間分成一小段一小段的,假如是5ms一段。於是多個程序如果要“同時”執行,實際上就是每個程序輪流佔用5ms的cpu時間,而從1s的時間尺度上看,這些程序就是在“同時”執行的。

當然,對於多核系統來說,就是把每個核心都這樣做就行了。而在這種情況下,如何支援優先順序呢?

實際上就是將時間片分配成大小不等的若干種,優先順序高的程序使用大的時間片,優先順序小的程序使用小的時間片。這樣在一個週期結速後,優先順序大的程序就會佔用更多的時間而因此得到特殊待遇。

O1演算法還有一個比較特殊的地方是,即使是相同的nice值的程序,也會再根據其CPU的佔用情況將其分成兩種型別:CPU消耗型和IO消耗性。

典型的CPU消耗型的程序的特點是,它總是要一直佔用CPU進行運算,分給它的時間片總是會被耗盡之後,程式才可能發生排程。

比如常見的各種算數運算程式。

而IO消耗型的特點是,它經常時間片沒有耗盡就自己主動先釋放CPU了。

比如vi,emacs這樣的編輯器就是典型的IO消耗型程序。

為什麼要這樣區分呢?因為IO消耗型的程序經常是跟人互動的程序,比如shell、編輯器等。

當系統中既有這種程序,又有CPU消耗型程序存在,並且其nice值一樣時,假設給它們分的時間片長度是一樣的,都是500ms,那麼人的操作可能會因為CPU消耗型的程序一直佔用CPU而變的卡頓。

可以想象,當bash在等待人輸入的時候,是不佔CPU的,此時CPU消耗的程式會一直運算,假設每次都分到500ms的時間片,此時人在bash上敲入一個字元的時候,那麼bash很可能要等個幾百ms才能給出響應,因為在人敲入字元的時候,別的程序的時間片很可能並沒有耗盡,所以系統不會排程bash程度進行處理。

為了提高IO消耗型程序的響應速度,系統將區分這兩類程序,並動態調整CPU消耗的程序將其優先順序降低,而IO消耗型的將其優先順序變高,以降低CPU消耗程序的時間片的實際長度。

已知nice值的範圍是-20-19,其對應priority值的範圍是100-139,對於一個預設nice值為0的程序來說,其初始priority值應該是120,隨著其不斷執行,核心會觀察程序的CPU消耗狀態,並動態調整priority值,可調整的範圍是+-5。

就是說,最高優先順序可以被自動調整到115,最低到125。這也是為什麼nice值叫做靜態優先順序,而priority值叫做動態優先順序的原因。不過這個動態調整的功能在排程器換成CFS之後就不需要了,因為CFS換了另外一種CPU時間分配方式,這個我們後面再說。

什麼是CFS完全公平排程?

O1已經是上一代排程器了,由於其對多核、多CPU系統的支援效能並不好,並且核心功能上要加入cgroup等因素,Linux在2.6.23之後開始啟用CFS作為對一般優先順序(SCHED_OTHER)程序排程方法。

在這個重新設計的排程器中,時間片,動態、靜態優先順序以及IO消耗,CPU消耗的概念都不再重要。CFS採用了一種全新的方式,對上述功能進行了比較完善的支援。

其設計的基本思路是:我們想要實現一個對所有程序完全公平的排程器

又是那個老問題:如何做到完全公平?答案跟上一篇IO排程中CFQ的思路類似:

如果當前有n個程序需要排程執行,那麼排程器應該在一個比較小的時間範圍內,把這n個程序全都排程執行一遍,並且它們平分cpu時間,這樣就可以做到所有程序的公平排程。

那麼這個比較小的時間就是任意一個R狀態程序被排程的最大延時時間,即:任意一個R狀態程序,都一定會在這個時間範圍內被排程響應。這個時間也可以叫做排程週期,其英文名字叫做:sched_latency_ns。

CFS的優先順序

當然,CFS中還需要支援優先順序。在新的體系中,優先順序是以時間消耗(vruntime增長)的快慢來決定的。

就是說,對於CFS來說,衡量的時間累積的絕對值都是一樣紀錄在vruntime中的,但是不同優先順序的程序時間增長的比率是不同的,高優先順序程序時間增長的慢,低優先順序時間增長的快。

比如,優先順序為19的程序,實際佔用cpu為1秒,那麼在vruntime中就記錄1s。但是如果是-20優先順序的程序,那麼它很可能實際佔CPU用10s,在vruntime中才會紀錄1s。

CFS真實實現的不同nice值的cpu消耗時間比例在核心中是按照“每差一級cpu佔用時間差10%左右”這個原則來設定的。

這裡的大概意思是說,如果有兩個nice值為0的程序同時佔用cpu,那麼它們應該每人佔50%的cpu,如果將其中一個程序的nice值調整為1的話,那麼此時應保證優先順序高的程序比低的多佔用10%的cpu,就是nice值為0的佔55%,nice值為1的佔45%。那麼它們佔用cpu時間的比例為55:45。

這個值的比例約為1.25。就是說,相鄰的兩個nice值之間的cpu佔用時間比例的差別應該大約為1.25。根據這個原則,核心對40個nice值做了時間計算比例的對應關係,它在核心中以一個數組存在:

linux

多CPU的CFS排程是怎樣的?

在上面的敘述中,我們可以認為系統中只有一個CPU,那麼相關的排程佇列只有一個。

實際情況是系統是有多核甚至多個CPU的,CFS從一開始就考慮了這種情況,它對每個CPU核心都維護一個排程佇列,這樣每個CPU都對自己的佇列程序排程即可。

這也是CFS比O1排程演算法更高效的根本原因:每個CPU一個佇列,就可以避免對全域性佇列使用大核心鎖,從而提高了並行效率。

當然,這樣最直接的影響就是CPU之間的負載可能不均,為了維持CPU之間的負載均衡,CFS要定期對所有CPU進行load balance操作,於是就有可能發生程序在不同CPU的排程佇列上切換的行為。

這種操作的過程也需要對相關的CPU佇列進行鎖操作,從而降低了多個執行佇列帶來的並行性。

不過總的來說,CFS的並行佇列方式還是要比O1的全域性佇列方式要高效。尤其是在CPU核心越來越多的情況下,全域性鎖的效率下降顯著增加。

最後

本文的目的是從Linux系統程序的優先順序為出發點,通過了解相關的知識點,希望大家對系統的程序排程有個整體的瞭解。

其中我們也對CFS排程演算法進行了比較深入的分析。在我的經驗來看,這些知識對我們在觀察系統的狀態和相關優化的時候都是非常有用的。

比如在使用top命令的時候,NI和PR值到底是什麼意思?類似的地方還有ps命令中的NI和PRI值、ulimit命令-e和-r引數的區別等等。當然,希望看完本文後,能讓大家對這些命令顯示的瞭解更加深入。

除此之外,我們還會發現,雖然top命令中的PR值和ps -l命令中的PRI值的含義是一樣的,但是在優先順序相同的情況下,它們顯示的值確不一樣。

那麼你知道為什麼它們顯示會有區別嗎?這個問題的答案留給大家自己去尋找吧。

說明:考慮到微信閱讀的便利性,本文略有刪減,如下閱讀完整版本,請點選文末“閱讀原文”連結。

怎麼讓運維更具逼格?

運維發展至今,早已不是刀耕火種的時代,不應該仍然是“背黑鍋俠”,“背伺服器俠”。運維可以更高逼格、更高價值,運維明天可以更美好!

“重新定義運維”讓這些成為可能

匯聚整個行業的力量,集合海內外專家的智慧,我們在路上!

以“重新定義運維”為主題的GOPS2016全球運維大會·上海站,已正式啟動。您可掃描下方二維碼,或點選文末“閱讀原文”連結,以瞭解詳情:

filehelper_1464329947339_65