標籤:cpp 命令 ase 訊號 oar 連接埠 ted tool plain
核心在微觀上,把CPU的已耗用時間分成許多分,然後安排給各個進程輪流程執行,造成宏觀上所有的進程彷彿同時在執行。雙核CPU,實際上最多隻能有兩個進程在同時運行,大家在top、vmstat命令裡看到的正在啟動並執行進程,並不是真的在佔有著CPU哈。
所以,一些設計良好的高效能進程,比如nginx,都是實際上有幾顆CPU,就配幾個背景工作處理序,道理就在這。比如你的伺服器有8顆CPU,那麼nginx worker應當只有8個,當你多於8個時,核心可能會放超過多個nginx worker進程到1個runqueue裡,會發生什麼呢?就是在這顆CPU上,會比較均勻的把時間分配給這幾個nginx worker,每個worker進程運行完一個時間片後,核心需要做進程切換,把正在啟動並執行進程上下文儲存下來。假設核心分配的時間片是100ms,做進程切換的時間是5ms,那麼進程效能下降還是很明顯的,跟你配置的worker有關,越多下降得越厲害。
當然,這是跟nginx的設計有關的。nginx是事件驅動的全非同步進程,本身設計上就幾乎不存在阻塞和中斷,nginx的設計者就希望每一個nginx worker可以獨佔CPU的幾乎全部時間片,這點就是nginx worker數量配置的依據所在。
當然,實際的運行進程裡,大部分並不是nginx這種希望獨佔CPU全部時間片的進程,許多進程,比如vi,它在很多時間是在等待使用者輸入,這時vi在等待IO中斷,是不佔用時間片的,核心面對多樣化的進程,就需要技巧性的分配CPU時間片了。
核心分配時間片是有策略和傾向性的。換句話說,核心是偏心的,它喜歡的是IO消耗型進程,因為這類進程如果不能及時響應,使用者就會很不爽,所以它總會下意識的多分配CPU已耗用時間給這類進程。而CPU消耗進程核心就不太關心了。這有道理嗎?太有了,CPU消耗型慢一點使用者感知不出來,電訊號和生物訊號運轉速度差距巨大。雖然核心盡量多的分配時間片給IO消耗型進程,但IO消耗進程常常在睡覺,給它的時間片根本用不掉。很合理吧?
那麼核心具體是怎麼實現這種偏心呢?通過動態調整進程的優先順序,以及分配不同長短的CPU時間處來實現。先說核心如何決定時間片的長度。
對每一個進程,有一個整型static_prio表示使用者佈建的靜態優先順序,核心裡它與nice值是對應的。看看進程描述結構裡的static_prio成員。
[cpp] view plain copy
- struct task_struct {
- int prio, static_prio;
- ......}
nice值是什嗎?其實就是優先順序針對使用者進程的另一種標記法,nice的取值範圍是-20到+19,-20優先順序最高,+19最低。上篇曾經說過,核心優先順序共有140,而使用者能夠設定的NICE優先順序如何與這140個優先順序對應起來呢?看代碼:
[cpp] view plain copy
- #define MAX_USER_RT_PRIO 100
- #define MAX_RT_PRIO MAX_USER_RT_PRIO
- #define MAX_PRIO (MAX_RT_PRIO + 40)
可以看到,MAX_PRIO就是140,也就是核心定義的最大優先順序了。
[cpp] view plain copy
- #define USER_PRIO(p) ((p)-MAX_RT_PRIO)
- #define MAX_USER_PRIO (USER_PRIO(MAX_PRIO))
而MAX_USER_PRIO就是40,意指,普通進程指定的優先順序別最多40,就像前面我們講的那樣-20到+19。
[cpp] view plain copy
- #define NICE_TO_PRIO(nice) (MAX_RT_PRIO + (nice) + 20)
nice值是-20表示最高,對應著static_prio是多少呢?NICE_TO_PRIO(0)就是120,NICE_TO_PRIO(-20)就是100。
當該進程剛被其父進程fork出來時,是平分其父進程的剩餘時間片的。這個時間片執行完後,就會根據它的初始優先順序來重新分配時間片,優先順序為+19時最低,只分配最小時間片5ms,優先順序為0時是100ms,優先順序是-20時是最大時間片800ms。我們看看核心是如何計算時間片長度的,大家先看下task_timeslice時間片計算函數:
[cpp] view plain copy
- #define SCALE_PRIO(x, prio) \
- max(x * (MAX_PRIO - prio) / (MAX_USER_PRIO/2), MIN_TIMESLICE)
-
- static unsigned int task_timeslice(task_t *p)
- {
- if (p->static_prio < NICE_TO_PRIO(0))
- return SCALE_PRIO(DEF_TIMESLICE*4, p->static_prio);
- else
- return SCALE_PRIO(DEF_TIMESLICE, p->static_prio);
- }
這裡有一堆宏,我們把宏依次列出看看它們的值:
[cpp] view plain copy
- # define HZ 1000
- #define DEF_TIMESLICE (100 * HZ / 1000)
所以,DEF_TIMESLICE是100。假設nice值是-20,那麼static_prio就是100,那麼SCALE_PRIO(100*4, 100)就等於800,意味著最高優先順序-20情形下,可以分到時間片是800ms,如果nice值是+19,則只能分到最小時間片5ms,nice值是預設的0則能分到100ms。
貌似時間片只與nice值有關係。實際上,核心會對初始的nice值有一個-5到+5的動態調整。這個動態調整的依據是什麼呢?很簡單,如果CPU用得多的進程,就把nice值調高點,等價於優先順序調低點。CPU用得少的進程,認為它是互動性為主的進程,則會把nice值調低點,也就是優先順序調高點。這樣的好處很明顯,因為1、一個進程的初始優先值的設定未必是準確的,核心根據該進程的即時表現來調整它的執行情況。2、進程的表現不是始終如一的,比如一開始只是監聽80連接埠,此時進程大部分時間在sleep,時間片用得少,於是nice值動態降低來提高優先順序。這時有client接入80連接埠後,進程開始大量使用CPU,這以後nice值會動態增加來降低優先順序。
思想明白了,代碼實現上,優先順序的動態補償到底依據什麼呢?effective_prio返回動態補償後的優先順序,注釋非常詳細,大家先看下。
[cpp] view plain copy
- /*
- * effective_prio - return the priority that is based on the static
- * priority but is modified by bonuses/penalties.
- *
- * We scale the actual sleep average [0 .... MAX_SLEEP_AVG]
- * into the -5 ... 0 ... +5 bonus/penalty range.
- *
- * We use 25% of the full 0...39 priority range so that:
- *
- * 1) nice +19 interactive tasks do not preempt nice 0 CPU hogs.
- * 2) nice -20 CPU hogs do not get preempted by nice 0 tasks.
- *
- * Both properties are important to certain workloads.
- */
- static int effective_prio(task_t *p)
- {
- int bonus, prio;
-
- if (rt_task(p))
- return p->prio;
-
- bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;
-
- prio = p->static_prio - bonus;
- if (prio < MAX_RT_PRIO)
- prio = MAX_RT_PRIO;
- if (prio > MAX_PRIO-1)
- prio = MAX_PRIO-1;
- return prio;
- }
可以看到bonus會對初始優先順序做補償。怎麼計算出這個BONUS的呢?
[cpp] view plain copy
- #define CURRENT_BONUS(p) \
- (NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / \
- MAX_SLEEP_AVG)
可以看到,進程描述符裡還有個sleep_avg,動態補償完全是根據它的值來運作的。sleep_avg就是關鍵了,它表示進程睡眠和啟動並執行時間,當進程由休眠轉到運行時,sleep_avg會加上這次休眠用的時間。在運行時,每運行一個時鐘節拍sleep_avg就遞減直到0為止。所以,sleep_avg越大,那麼就會給到越大的動態優先順序補償,達到MAX_SLEEP_AVG時會有nice值-5的補償。
核心就是這麼偏愛互動型進程,從上面的優先順序和時間片分配上都能看出來。實際上,核心還有方法對互動型進程搞優待。上篇說過,runqueue裡的active和expired隊列,一般的進程時間片用完後進expired隊列,而對IO消耗的互動型進程來說,則會直接進入active隊列中,保證高靈敏的響應,可見什麼叫萬千寵愛於一身了。
linux核心調度演算法(2)--CPU時間片如何分配 轉!