上回書說到 Linux進程的由來 和 Linux進程的創(chuàng)建 ,其實在同一時刻只能支持有限個進程或線程同時運行(這取決于CPU核數(shù)量,基本上一個進程對應(yīng)一個CPU),在一個運行的操作系統(tǒng)上可能運行著很多進程,如果運行的進程占據(jù)CPU的時間很長,就有可能導(dǎo)致其他進程餓死。為了解決這種問題,操作系統(tǒng)引入了 進程調(diào)度器 來進行進程的切換,輪流讓各個進程使用CPU資源。
創(chuàng)新互聯(lián)公司專注于連平網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供連平營銷型網(wǎng)站建設(shè),連平網(wǎng)站制作、連平網(wǎng)頁設(shè)計、連平網(wǎng)站官網(wǎng)定制、小程序設(shè)計服務(wù),打造連平網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供連平網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
1)rq: 進程的運行隊列( runqueue), 每個CPU對應(yīng)一個 ,包含自旋鎖(spinlock)、進程數(shù)量、用于公平調(diào)度的CFS信息結(jié)構(gòu)、當(dāng)前運行的進程描述符等。實際的進程隊列用紅黑樹來維護(通過CFS信息結(jié)構(gòu)來訪問)。
2)cfs_rq: cfs調(diào)度的進程運行隊列信息 ,包含紅黑樹的根結(jié)點、正在運行的進程指針、用于負載均衡的葉子隊列等。
3)sched_entity: 把需要調(diào)度的東西抽象成調(diào)度實體 ,調(diào)度實體可以是進程、進程組、用戶等。這里包含負載權(quán)重值、對應(yīng)紅黑樹結(jié)點、 虛擬運行時vruntime 等。
4)sched_class:把 調(diào)度策略(算法)抽象成調(diào)度類 ,包含一組通用的調(diào)度操作接口。接口和實現(xiàn)是分離,可以根據(jù)調(diào)度接口去實現(xiàn)不同的調(diào)度算法,使一個Linux調(diào)度程序可以有多個不同的調(diào)度策略。
1) 關(guān)閉內(nèi)核搶占 ,初始化部分變量。獲取當(dāng)前CPU的ID號,并賦值給局部變量CPU, 使rq指向CPU對應(yīng)的運行隊列 。 標(biāo)識當(dāng)前CPU發(fā)生任務(wù)切換 ,通知RCU更新狀態(tài),如果當(dāng)前CPU處于rcu_read_lock狀態(tài),當(dāng)前進程將會放入rnp- blkd_tasks阻塞隊列,并呈現(xiàn)在rnp- gp_tasks鏈表中。 關(guān)閉本地中斷 ,獲取所要保護的運行隊列的自旋鎖, 為查找可運行進程做準(zhǔn)備 。
2) 檢查prev的狀態(tài),更新運行隊列 。如果不是可運行狀態(tài),而且在內(nèi)核態(tài)沒被搶占,應(yīng)該從運行隊列中 刪除prev進程 。如果是非阻塞掛起信號,而且狀態(tài)為TASK_INTER-RUPTIBLE,就把該進程的狀態(tài)設(shè)置為TASK_RUNNING,并將它 插入到運行隊列 。
3)task_on_rq_queued(prev) 將pre進程插入到運行隊列的隊尾。
4)pick_next_task 選取將要執(zhí)行的next進程。
5)context_switch(rq, prev, next)進行 進程上下文切換 。
1) 該進程分配的CPU時間片用完。
2) 該進程主動放棄CPU(例如IO操作)。
3) 某一進程搶占CPU獲得執(zhí)行機會。
Linux并沒有使用x86 CPU自帶的任務(wù)切換機制,需要通過手工的方式實現(xiàn)了切換。
進程創(chuàng)建后在內(nèi)核的數(shù)據(jù)結(jié)構(gòu)為task_struct , 該結(jié)構(gòu)中有掩碼屬性cpus_allowed,4個核的CPU可以有4位掩碼,如果CPU開啟超線程,有一個8位掩碼,進程可以運行在掩碼位設(shè)置為1的CPU上。
Linux內(nèi)核API提供了兩個系統(tǒng)調(diào)用 ,讓用戶可以修改和查看當(dāng)前的掩碼:
1) sched_setaffinity():用來修改位掩碼。
2) sched_getaffinity():用來查看當(dāng)前的位掩碼。
在下次task被喚醒時,select_task_rq_fair根據(jù)cpu_allowed里的掩碼來確定將其置于哪個CPU的運行隊列,一個進程在某一時刻只能存在于一個CPU的運行隊列里。
在Nginx中,使用了CPU親和度來完成某些場景的工作:
worker_processes? ? ? 4;
worker_cpu_affinity 0001001001001000;
上面這個配置說明了4個工作進程中的每一個和一個CPU核掛鉤。如果這個內(nèi)容寫入Nginx的配置文件中,然后Nginx啟動或者重新加載配置的時候,若worker_process是4,就會啟用4個worker,然后把worker_cpu_affinity后面的4個值當(dāng)作4個cpu affinity mask,分別調(diào)用ngx_setaffinity,然后就把4個worker進程分別綁定到CPU0~3上。
worker_processes? ? ? 2;
worker_cpu_affinity 01011010;
上面這個配置則說明了兩個工作進程中的每一個和2個核掛鉤。
1.調(diào)度器的概述
多任務(wù)操作系統(tǒng)分為非搶占式多任務(wù)和搶占式多任務(wù)。與大多數(shù)現(xiàn)代操作系統(tǒng)一樣,Linux采用的是搶占式多任務(wù)模式。這表示對CPU的占用時間由操作系統(tǒng)決定的,具體為操作系統(tǒng)中的調(diào)度器。調(diào)度器決定了什么時候停止一個進程以便讓其他進程有機會運行,同時挑選出一個其他的進程開始運行。
2.調(diào)度策略
在Linux上調(diào)度策略決定了調(diào)度器是如何選擇一個新進程的時間。調(diào)度策略與進程的類型有關(guān),內(nèi)核現(xiàn)有的調(diào)度策略如下:
#define SCHED_NORMAL ? ? ? ?0#define SCHED_FIFO ? ? ?1#define SCHED_RR ? ? ? ?2#define SCHED_BATCH ? ? 3/* SCHED_ISO: reserved but not implemented yet */#define SCHED_IDLE ? ? ?5
0: 默認的調(diào)度策略,針對的是普通進程。
1:針對實時進程的先進先出調(diào)度。適合對時間性要求比較高但每次運行時間比較短的進程。
2:針對的是實時進程的時間片輪轉(zhuǎn)調(diào)度。適合每次運行時間比較長得進程。
3:針對批處理進程的調(diào)度,適合那些非交互性且對cpu使用密集的進程。
SCHED_ISO:是內(nèi)核的一個預(yù)留字段,目前還沒有使用
5:適用于優(yōu)先級較低的后臺進程。
注:每個進程的調(diào)度策略保存在進程描述符task_struct中的policy字段
3.調(diào)度器中的機制
內(nèi)核引入調(diào)度類(struct sched_class)說明了調(diào)度器應(yīng)該具有哪些功能。內(nèi)核中每種調(diào)度策略都有該調(diào)度類的一個實例。(比如:基于公平調(diào)度類為:fair_sched_class,基于實時進程的調(diào)度類實例為:rt_sched_class),該實例也是針對每種調(diào)度策略的具體實現(xiàn)。調(diào)度類封裝了不同調(diào)度策略的具體實現(xiàn),屏蔽了各種調(diào)度策略的細節(jié)實現(xiàn)。
調(diào)度器核心函數(shù)schedule()只需要調(diào)用調(diào)度類中的接口,完成進程的調(diào)度,完全不需要考慮調(diào)度策略的具體實現(xiàn)。調(diào)度類連接了調(diào)度函數(shù)和具體的調(diào)度策略。
武特師兄關(guān)于sche_class和sche_entity的解釋,一語中的。
調(diào)度類就是代表的各種調(diào)度策略,調(diào)度實體就是調(diào)度單位,這個實體通常是一個進程,但是自從引入了cgroup后,這個調(diào)度實體可能就不是一個進程了,而是一個組
4.schedule()函數(shù)
linux 支持兩種類型的進程調(diào)度,實時進程和普通進程。實時進程采用SCHED_FIFO 和SCHED_RR調(diào)度策略,普通進程采用SCHED_NORMAL策略。
preempt_disable():禁止內(nèi)核搶占
cpu_rq():獲取當(dāng)前cpu對應(yīng)的就緒隊列。
prev = rq-curr;獲取當(dāng)前進程的描述符prev
switch_count = prev-nivcsw;獲取當(dāng)前進程的切換次數(shù)。
update_rq_clock() :更新就緒隊列上的時鐘
clear_tsk_need_resched()清楚當(dāng)前進程prev的重新調(diào)度標(biāo)志。
deactive_task():將當(dāng)前進程從就緒隊列中刪除。
put_prev_task() :將當(dāng)前進程重新放入就緒隊列
pick_next_task():在就緒隊列中挑選下一個將被執(zhí)行的進程。
context_switch():進行prev和next兩個進程的切換。具體的切換代碼與體系架構(gòu)有關(guān),在switch_to()中通過一段匯編代碼實現(xiàn)。
post_schedule():進行進程切換后的后期處理工作。
5.pick_next_task函數(shù)
選擇下一個將要被執(zhí)行的進程無疑是一個很重要的過程,我們來看一下內(nèi)核中代碼的實現(xiàn)
對以下這段代碼說明:
1.當(dāng)rq中的運行隊列的個數(shù)(nr_running)和cfs中的nr_runing相等的時候,表示現(xiàn)在所有的都是普通進程,這時候就會調(diào)用cfs算法中的pick_next_task(其實是pick_next_task_fair函數(shù)),當(dāng)不相等的時候,則調(diào)用sched_class_highest(這是一個宏,指向的是實時進程),這下面的這個for()循環(huán)中,首先是會在實時進程中選取要調(diào)度的程序(p = class-pick_next_task(rq);)。如果沒有選取到,會執(zhí)行class=class-next;在class這個鏈表中有三種類型(fair,idle,rt).也就是說會調(diào)用到下一個調(diào)度類。
static inline struct task_struct *pick_next_task(struct rq *rq){ ? ?const struct sched_class *class; ? ?struct task_struct *p; ? ?/*
* Optimization: we know that if all tasks are in
* the fair class we can call that function directly:
*///基于公平調(diào)度的普通進程
if (likely(rq-nr_running == rq-cfs.nr_running)) {
?p = fair_sched_class.pick_next_task(rq); ? ? ? ?if (likely(p)) ? ? ? ? ? ?return p;
}//基于實時調(diào)度的實時進程
class = sched_class_highest; ? ?for ( ; ; ) {
?p = class-pick_next_task(rq); ?//實時進程的類
?if (p) ? ? ? ? ? ?return p; ? ? ? ?/*
? * Will never be NULL as the idle class always
? * returns a non-NULL p:
? */
?class = class-next; ?//rt-next = fair; ?fair-next = idle
}
}
在這段代碼中體現(xiàn)了Linux所支持的兩種類型的進程,實時進程和普通進程。回顧下:實時進程可以采用SCHED_FIFO 和SCHED_RR調(diào)度策略,普通進程采用SCHED_NORMAL調(diào)度策略。
在這里首先說明一個結(jié)構(gòu)體struct rq,這個結(jié)構(gòu)體是調(diào)度器管理可運行狀態(tài)進程的最主要的數(shù)據(jù)結(jié)構(gòu)。每個cpu上都有一個可運行的就緒隊列。剛才在pick_next_task函數(shù)中看到了在選擇下一個將要被執(zhí)行的進程時實際上用的是struct rq上的普通進程的調(diào)度或者實時進程的調(diào)度,那么具體是如何調(diào)度的呢?在實時調(diào)度中,為了實現(xiàn)O(1)的調(diào)度算法,內(nèi)核為每個優(yōu)先級維護一個運行隊列和一個DECLARE_BITMAP,內(nèi)核根據(jù)DECLARE_BITMAP的bit數(shù)值找出非空的最高級優(yōu)先隊列的編號,從而可以從非空的最高級優(yōu)先隊列中取出進程進行運行。
我們來看下內(nèi)核的實現(xiàn)
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
struct list_head queue[MAX_RT_PRIO];
};
數(shù)組queue[i]里面存放的是優(yōu)先級為i的進程隊列的鏈表頭。在結(jié)構(gòu)體rt_prio_array 中有一個重要的數(shù)據(jù)構(gòu)DECLARE_BITMAP,它在內(nèi)核中的第一如下:
define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
5.1對于實時進程的O(1)算法
這個數(shù)據(jù)是用來作為進程隊列queue[MAX_PRIO]的索引位圖。bitmap中的每一位與queue[i]對應(yīng),當(dāng)queue[i]的進程隊列不為空時,Bitmap的相應(yīng)位就為1,否則為0,這樣就只需要通過匯編指令從進程優(yōu)先級由高到低的方向找到第一個為1的位置,則這個位置就是就緒隊列中最高的優(yōu)先級(函數(shù)sched_find_first_bit()就是用來實現(xiàn)該目的的)。那么queue[index]-next就是要找的候選進程。
如果還是不懂,那就來看兩個圖
注:在每個隊列上的任務(wù)一般基于先進先出的原則進行調(diào)度(并且為每個進程分配時間片)
在內(nèi)核中的實現(xiàn)為:
static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq, ? ? ? ? ? ? ? ? ? ? ? ? ? struct rt_rq *rt_rq){ ? ?struct rt_prio_array *array = rt_rq-active; ? ?struct sched_rt_entity *next = NULL; ? ?struct list_head *queue; ? ?int idx;
idx = sched_find_first_bit(array-bitmap); //找到優(yōu)先級最高的位
BUG_ON(idx = MAX_RT_PRIO); ? ?queue = array-queue + idx; //然后找到對應(yīng)的queue的起始地址
next = list_entry(queue-next, struct sched_rt_entity, run_list); ?//按先進先出拿任務(wù)
return next;
}
那么當(dāng)同一優(yōu)先級的任務(wù)比較多的時候,內(nèi)核會根據(jù)
位圖:
將對應(yīng)的位置為1,每次取出最大的被置為1的位,表示優(yōu)先級最高:
5.2 關(guān)于普通進程的CFS算法:
我們知道,普通進程在選取下一個需要被調(diào)度的進程時,是調(diào)用的pick_next_task_fair函數(shù)。在這個函數(shù)中是以調(diào)度實體為單位進行調(diào)度的。其最主要的函數(shù)是:pick_next_entity,在這個函數(shù)中會調(diào)用wakeup_preempt_entity函數(shù),這個函數(shù)的主要作用是根據(jù)進程的虛擬時間以及權(quán)重的結(jié)算進程的粒度,以判斷其是否需要搶占。看一下內(nèi)核是怎么實現(xiàn)的:
wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
{
s64 gran, vdiff = curr-vruntime - se-vruntime;//計算兩個虛擬時間差//如果se的虛擬時間比curr還大,說明本該curr執(zhí)行,無需搶占
if (vdiff = 0) ? ? ? ?return -1;
gran = wakeup_gran(curr, se); ? ?if (vdiff gran) ? ? ? ?return 1; ? ?return 0;
}
gran為需要搶占的時間差,只有兩個時間差大于需要搶占的時間差,才需要搶占,這里避免太頻繁的搶占
wakeup_gran(struct sched_entity *curr, struct sched_entity *se)
{
unsigned long gran = sysctl_sched_wakeup_granularity; ? ?if (cfs_rq_of(curr)-curr sched_feat(ADAPTIVE_GRAN))
?gran = adaptive_gran(curr, se);
/*
* Since its curr running now, convert the gran from real-time
* to virtual-time in his units.
*/ ? ?if (sched_feat(ASYM_GRAN)) {
?/*
? * By using 'se' instead of 'curr' we penalize light tasks, so
? * they get preempted easier. That is, if 'se' 'curr' then
? * the resulting gran will be larger, therefore penalizing the
? * lighter, if otoh 'se' 'curr' then the resulting gran will
? * be smaller, again penalizing the lighter task.
? *
? * This is especially important for buddies when the leftmost
? * task is higher priority than the buddy.
? */ ? ? ? ?if (unlikely(se-load.weight != NICE_0_LOAD))
? ? ?gran = calc_delta_fair(gran, se);
} else { ? ? ? ?if (unlikely(curr-load.weight != NICE_0_LOAD))
? ? ?gran = calc_delta_fair(gran, curr);
} ? ?return gran;
}
6.調(diào)度中的nice值
首先需要明確的是:nice的值不是進程的優(yōu)先級,他們不是一個概念,但是進程的Nice值會影響到進程的優(yōu)先級的變化。
通過命令ps -el可以看到進程的nice值為NI列。PRI表示的是進程的優(yōu)先級,其實進程的優(yōu)先級只是一個整數(shù),它是調(diào)度器選擇進程運行的基礎(chǔ)。
普通進程有:靜態(tài)優(yōu)先級和動態(tài)優(yōu)先級。
靜態(tài)優(yōu)先級:之所有稱為靜態(tài)優(yōu)先級是因為它不會隨著時間而改變,內(nèi)核不會修改它,只能通過系統(tǒng)調(diào)用nice去修改,靜態(tài)優(yōu)先級用進程描述符中的static_prio來表示。在內(nèi)核中/kernel/sched.c中,nice和靜態(tài)優(yōu)先級的關(guān)系為:
#define NICE_TO_PRIO(nice) ?(MAX_RT_PRIO + (nice) + 20)
#define PRIO_TO_NICE(prio) ?((prio) - MAX_RT_PRIO - 20)
動態(tài)優(yōu)先級:調(diào)度程序通過增加或者減小進程靜態(tài)優(yōu)先級的值來獎勵I(lǐng)O小的進程或者懲罰cpu消耗型的進程。調(diào)整后的優(yōu)先級稱為動態(tài)優(yōu)先級。在進程描述中用prio來表示,通常所說的優(yōu)先級指的是動態(tài)優(yōu)先級。
由上面分析可知,我們可以通過系統(tǒng)調(diào)用nice函數(shù)來改變進程的優(yōu)先級。
#include stdlib.h#include stdio.h#include math.h#include unistd.h#include sys/time.h#define JMAX (400*100000)#define GET_ELAPSED_TIME(tv1,tv2) ( \
(double)( (tv2.tv_sec - tv1.tv_sec) \
? ? ?+ .000001 * (tv2.tv_usec - tv1.tv_usec)))//做一個延遲的計算double do_something (void){ ? ?int j; ? ?double x = 0.0; ? ?struct timeval tv1, tv2;
gettimeofday (tv1, NULL);//獲取時區(qū)
for (j = 0; j JMAX; j++)
?x += 1.0 / (exp ((1 + x * x) / (2 + x * x)));
gettimeofday (tv2, NULL); ? ?return GET_ELAPSED_TIME (tv1, tv2);//求差值}int main (int argc, char *argv[]){ ? ?int niceval = 0, nsched; ? ?/* for kernels less than 2.6.21, this is HZ
for tickless kernels this must be the MHZ rate
e.g, for 2.6 GZ scale = 2600000000 */
long scale = 1000; ? ?long ticks_cpu, ticks_sleep; ? ?pid_t pid;
FILE *fp; ? ?char fname[256]; ? ?double elapsed_time, timeslice, t_cpu, t_sleep; ? ?if (argc 1)
?niceval = atoi (argv[1]);
pid = getpid (); ? ?if (argc 2)
?scale = atoi (argv[2]); ? ?/* give a chance for other tasks to queue up */
sleep (3); ? ?sprintf (fname, "/proc/%d/schedstat", pid);//讀取進程的調(diào)度狀態(tài)
/*
?在schedstat中的數(shù)字是什么意思呢?:
*/
/* ? ?printf ("Fname = %s\n", fname); */
if (!(fp = fopen (fname, "r"))) { ? ? ? ?printf ("Failed to open stat file\n"); ? ? ? ?exit (-1);
} ? ?//nice系統(tǒng)調(diào)用
if (nice (niceval) == -1 niceval != -1) { ? ? ? ?printf ("Failed to set nice to %d\n", niceval); ? ? ? ?exit (-1);
}
elapsed_time = do_something ();//for 循環(huán)執(zhí)行了多長時間
fscanf (fp, "%ld %ld %d", ticks_cpu, ticks_sleep, nsched);//nsched表示調(diào)度的次數(shù)
t_cpu = (float)ticks_cpu / scale;//震動的次數(shù)除以1000,就是時間
t_sleep = (float)ticks_sleep / scale;
timeslice = t_cpu / (double)nsched;//除以調(diào)度的次數(shù),就是每次調(diào)度的時間(時間片)
printf ("\nnice=%3d time=%8g secs pid=%5d"
? ? ?" ?t_cpu=%8g ?t_sleep=%8g ?nsched=%5d"
? ? ?" ?avg timeslice = %8g\n",
? ? ?niceval, elapsed_time, pid, t_cpu, t_sleep, nsched, timeslice);
fclose (fp); ? ?exit (0);
}
說明:?首先說明的是/proc/[pid]/schedstat:在這個文件下放著3個變量,他們分別代表什么意思呢?
第一個:該進程擁有的cpu的時間
第二個:在對列上的等待時間,即睡眠時間
第三個:被調(diào)度的次數(shù)
由結(jié)果可以看出當(dāng)nice的值越小的時候,其睡眠時間越短,則表示其優(yōu)先級升高了。
7.關(guān)于獲取和設(shè)置優(yōu)先級的系統(tǒng)調(diào)用:sched_getscheduler()和sched_setscheduler
#include sched.h#include stdlib.h#include stdio.h#include errno.h#define DEATH(mess) { perror(mess); exit(errno); }void printpolicy (int policy){ ? ?/* SCHED_NORMAL = SCHED_OTHER in user-space */
if (policy == SCHED_OTHER) ? ? ? ?printf ("policy = SCHED_OTHER = %d\n", policy); ? ?if (policy == SCHED_FIFO) ? ? ? ?printf ("policy = SCHED_FIFO = %d\n", policy); ? ?if (policy == SCHED_RR) ? ? ? ?printf ("policy = SCHED_RR = %d\n", policy);
}int main (int argc, char **argv){ ? ?int policy; ? ?struct sched_param p; ? ?/* obtain current scheduling policy for this process */
//獲取進程調(diào)度的策略
policy = sched_getscheduler (0);
printpolicy (policy); ? ?/* reset scheduling policy */
printf ("\nTrying sched_setscheduler...\n");
policy = SCHED_FIFO;
printpolicy (policy);
p.sched_priority = 50; ? ?//設(shè)置優(yōu)先級為50
if (sched_setscheduler (0, policy, p))
?DEATH ("sched_setscheduler:"); ? ?printf ("p.sched_priority = %d\n", p.sched_priority); ? ?exit (0);
}
輸出結(jié)果:
[root@wang schedule]# ./get_schedule_policy policy = SCHED_OTHER = 0
Trying sched_setscheduler...
policy = SCHED_FIFO = 1
p.sched_priority = 50
可以看出進程的優(yōu)先級已經(jīng)被改變。
Linux的調(diào)度策略區(qū)分實時進程和普通進程,實時進程的調(diào)度策略是SCHED_FIFO和SCHED_RR,普通的,非實時進程的調(diào)度策略是SCHED_NORMAL(SCHED_OTHER)。
實時調(diào)度策略被實時調(diào)度器管理,普通調(diào)度策略被完全公平調(diào)度器來管理。實時進程的優(yōu)先級要高于普通進程(nice越小優(yōu)先級越高)。
SCHED_FIFO實現(xiàn)了一種簡單的先入先出的調(diào)度算法,它不使用時間片,但支持搶占,只有優(yōu)先級更高的SCHED_FIFO或者SCHED_RR進程才能搶占它,否則它會一直執(zhí)行下去,低優(yōu)先級的進程不能搶占它,直到它受阻塞或自己主動釋放處理器。
SCHED_RR是帶有時間片的一種實時輪流調(diào)度算法,當(dāng)SCHED_RR進程耗盡它的時間片時,同一優(yōu)先級的其它實時進程被輪流調(diào)度,時間片只用來重新調(diào)用同一優(yōu)先級的進程,低優(yōu)先級的進程決不能搶占SCHED_RR任務(wù),即使它的時間片耗盡。SCHED_RR是帶時間片的SCHED_FIFO。
Linux的實時調(diào)度算法提供了一種軟實時工作方式,軟實時的含義是盡力調(diào)度進程,盡力使進程在它的限定時間到來前運行,但內(nèi)核不保證總能滿足這些進程的要求,相反,硬實時系統(tǒng)保證在一定的條件下,可以滿足任何調(diào)度的要求。
SCHED_NORMAL使用完全公平調(diào)度算法(CFS),之前的算法直接將nice值對應(yīng)時間片的長度,而在CFS中,nice值只作為進程獲取處理器運行比的權(quán)重,每個進程都有一個權(quán)重,nice優(yōu)先級越高,權(quán)重越大,表示應(yīng)該運行更長的時間。Linux的實現(xiàn)中,每個進程都有一個vruntime字段,vruntime是經(jīng)過量化的進程運行時間,也就是實際運行時間除以權(quán)重,所以每個量化后的vruntime應(yīng)該相等,這就體現(xiàn)了公平性。
CFS當(dāng)然也支持搶占,但與實時調(diào)度算法不同,實時調(diào)度算法是根據(jù)優(yōu)先級進行搶占,CFS是根據(jù)vruntime進行搶占,vruntime小就擁有優(yōu)先被運行的權(quán)利。
為了計算時間片,CFS算法需要為完美多任務(wù)中的無限小調(diào)度周期設(shè)定近似值,這個近似值也稱作目標(biāo)延遲,指每個可運行進程在目標(biāo)延遲內(nèi)都會調(diào)度一次,如果進程數(shù)量太多,則時間粒度太小,所以約定時間片的默認最小粒度是1ms。
進程可以分為I/O消耗型和處理器消耗型,這兩種進程的調(diào)度策略應(yīng)該不同,I/O消耗型應(yīng)該更加實時,給對端的感覺是響應(yīng)很快,同時它一般又不會消耗太多的處理器,因而I/O消耗型需要調(diào)度頻繁。相對來說,處理器消耗型不需要特別實時,應(yīng)該盡量降低它的調(diào)度頻度,延長其運行時間。
參考: linux內(nèi)核分析——CFS(完全公平調(diào)度算法) - 一路向北你好 - 博客園