1. 程式人生 > >linux核心排程演算法(2)--CPU時間片如何分配

linux核心排程演算法(2)--CPU時間片如何分配

核心在微觀上,把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成員。

struct task_struct {
	int prio, static_prio;
......}
nice值是什麼?其實就是優先順序針對使用者程序的另一種表示法,nice的取值範圍是-20到+19,-20優先順序最高,+19最低。上篇曾經說過,核心優先順序共有140,而使用者能夠設定的NICE優先順序如何與這140個優先順序對應起來呢?看程式碼:
#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO		MAX_USER_RT_PRIO
#define MAX_PRIO		(MAX_RT_PRIO + 40)

可以看到,MAX_PRIO就是140,也就是核心定義的最大優先順序了。
#define USER_PRIO(p)		((p)-MAX_RT_PRIO)
#define MAX_USER_PRIO		(USER_PRIO(MAX_PRIO))

而MAX_USER_PRIO就是40,意指,普通程序指定的優先級別最多40,就像前面我們講的那樣-20到+19。
#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時間片計算函式:

#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);
}

這裡有一堆巨集,我們把巨集依次列出看看它們的值: