1、綜述
Linux作為多任務(wù)、多用戶(hù)的操作系統(tǒng),其進(jìn)程/線(xiàn)程調(diào)度管理是實(shí)現(xiàn)這些特性的關(guān)鍵部分。調(diào)度管理決定系統(tǒng)中的眾多線(xiàn)程中哪個(gè)線(xiàn)程獲得執(zhí)行、什么時(shí)候開(kāi)始執(zhí)行、執(zhí)行多久。一個(gè)好的調(diào)度算法能優(yōu)化系統(tǒng)資源的使用,提高系統(tǒng)使用效率。
Linux內(nèi)核中實(shí)現(xiàn)了Scheduler Classes,來(lái)實(shí)現(xiàn)多個(gè)調(diào)度類(lèi)(Scheduler class)的協(xié)同工作,每個(gè)不同的調(diào)度類(lèi)對(duì)應(yīng)不同的類(lèi)型的線(xiàn)程,而且每個(gè)調(diào)度類(lèi)都有自身的優(yōu)先級(jí),Linux調(diào)度管理基礎(chǔ)代碼會(huì)遍歷在內(nèi)核中注冊(cè)了的調(diào)度類(lèi),選擇高優(yōu)先級(jí)的調(diào)度類(lèi),然后讓此調(diào)度類(lèi)按照自己的調(diào)度算法選擇下一個(gè)執(zhí)行的線(xiàn)程。Linux系統(tǒng)中常用的幾種調(diào)度類(lèi)為SCHED_NORMAL、SCHED_FIFO、SCHED_RR。其中SCHED_NORMAL是用于普通線(xiàn)程的調(diào)度類(lèi),而SCHED_FIFO和SCHED_RR是用于實(shí)時(shí)線(xiàn)程的調(diào)度類(lèi),優(yōu)先級(jí)高于SCHED_NORMAL。內(nèi)核中區(qū)分普通線(xiàn)程與實(shí)時(shí)線(xiàn)程是根據(jù)線(xiàn)程的優(yōu)先級(jí),實(shí)時(shí)線(xiàn)程擁有實(shí)時(shí)優(yōu)先級(jí)(real-time priority),默認(rèn)取值為0~99,數(shù)值越高優(yōu)先級(jí)越高,而普通線(xiàn)程只具有nice值,nice值映射到用戶(hù)層的取值范圍為-20~+19,數(shù)值越高優(yōu)先級(jí)越低,默認(rèn)初始值為0 ,子線(xiàn)程會(huì)繼承父線(xiàn)程的優(yōu)先級(jí)。對(duì)于實(shí)時(shí)線(xiàn)程,Linux系統(tǒng)會(huì)盡量使其調(diào)度延時(shí)在一個(gè)時(shí)間期限內(nèi),但是不能保證總是如此,不過(guò)正常情況下已經(jīng)可以滿(mǎn)足比較嚴(yán)格的時(shí)間要求了。下面將分別介紹這些調(diào)度類(lèi)。
2、SCHED_NORMAL
對(duì)于SCHED_NORMAL,2.6之前的版本內(nèi)核中的調(diào)度管理程序是根據(jù)線(xiàn)程的優(yōu)先級(jí)(nice值)來(lái)分配一個(gè)固定的時(shí)間片(timeslice)給線(xiàn)程,比如nice值0對(duì)應(yīng)于100ms的時(shí)間片,而nice值-20對(duì)應(yīng)于5ms時(shí)間片,線(xiàn)程在執(zhí)行過(guò)程中進(jìn)入阻塞狀態(tài)(I/O操作等引起)或者是執(zhí)行的時(shí)間達(dá)到分配的時(shí)間片后將會(huì)被搶占,而新進(jìn)入運(yùn)行態(tài)的線(xiàn)程會(huì)根據(jù)其優(yōu)先級(jí)和可用時(shí)間片來(lái)決定是否搶占當(dāng)前正在執(zhí)行的程序。
2.6之后版本的Linux中SCHED_NORMAL使用的是Linux 內(nèi)核在2.6.23版本中引入的CFS(Complete Fair Scheduler)調(diào)度管理程序。CFS與之前的調(diào)度不同的是,線(xiàn)程的優(yōu)先級(jí)與時(shí)間片之間并沒(méi)有一個(gè)固定的關(guān)系,而是影響該線(xiàn)程在整個(gè)系統(tǒng)CPU運(yùn)行時(shí)間中占有比例的一個(gè)因素。比如有兩個(gè)線(xiàn)程,對(duì)應(yīng)的nice值分別為0(普通線(xiàn)程)和+19(低優(yōu)先級(jí)線(xiàn)程),那么普通線(xiàn)程將會(huì)占有19/20×100%的CPU時(shí)間,而低優(yōu)先級(jí)線(xiàn)程將會(huì)占有1/20×100%的CPU時(shí)間(具體數(shù)值只做舉例說(shuō)明用,Linux內(nèi)核中的計(jì)算出來(lái)的數(shù)值會(huì)不一樣)。而如果同時(shí)運(yùn)行的只有兩個(gè)相同優(yōu)先級(jí)的線(xiàn)程,那么他們分到的CPU時(shí)間各是50%。這樣每個(gè)線(xiàn)程能夠分配到的CPU時(shí)間占有比例跟系統(tǒng)當(dāng)前的負(fù)載(所有處于運(yùn)行態(tài)的線(xiàn)程數(shù)以及各線(xiàn)程的優(yōu)先級(jí))有關(guān),同一個(gè)線(xiàn)程在他本身優(yōu)先級(jí)不變的情況下分到的CPU時(shí)間占比會(huì)根據(jù)系統(tǒng)負(fù)載變化而發(fā)生變化,也即與時(shí)間片沒(méi)有一個(gè)固定的對(duì)應(yīng)關(guān)系。
理想情況下CFS對(duì)CPU時(shí)間占比的衡量是在一個(gè)無(wú)限小的時(shí)間片內(nèi)計(jì)算單個(gè)線(xiàn)程執(zhí)行時(shí)間的占比,CFS的目標(biāo)是所有線(xiàn)程在這個(gè)小的時(shí)間片周期內(nèi)執(zhí)行的時(shí)間都是相同的,無(wú)限小在現(xiàn)實(shí)中顯然是不存在的,Linux系統(tǒng)中這個(gè)時(shí)間片周期是與系統(tǒng)CPU數(shù)有關(guān)的,默認(rèn)情況下單核CPU對(duì)應(yīng)6ms,雙核CPU對(duì)應(yīng)12ms,8核CPU對(duì)應(yīng)24ms,當(dāng)線(xiàn)程數(shù)增加到很大數(shù)量時(shí),CFS會(huì)保證每個(gè)線(xiàn)程獲得最小執(zhí)行時(shí)間, 單核CPU對(duì)應(yīng)0.75ms,雙核CPU對(duì)應(yīng)1.5ms,8核CPU對(duì)應(yīng)3ms。在CFS管理下,某個(gè)線(xiàn)程運(yùn)行時(shí)如果進(jìn)入阻塞態(tài)(或其他非運(yùn)行態(tài))或者當(dāng)前時(shí)間片周期內(nèi)的CPU時(shí)間占比達(dá)到后將會(huì)暫停運(yùn)行,CFS然后將會(huì)選擇當(dāng)前時(shí)間片周期內(nèi)已執(zhí)行時(shí)間最少的運(yùn)行態(tài)線(xiàn)程繼續(xù)運(yùn)行。當(dāng)然CPU時(shí)間占比在內(nèi)核中也是以運(yùn)行時(shí)間衡量的,比如某個(gè)單核CPU系統(tǒng)中只有兩個(gè)相同優(yōu)先級(jí)的線(xiàn)程同時(shí)處于運(yùn)行態(tài),那么CFS將會(huì)以6ms為周期來(lái)調(diào)度所有線(xiàn)程,而每個(gè)6ms周期內(nèi)每個(gè)線(xiàn)程分得3ms執(zhí)行時(shí)間,如果某個(gè)線(xiàn)程中有I/O操作等其他操作使該線(xiàn)程進(jìn)入非運(yùn)行態(tài),CFS將會(huì)立即使另外一個(gè)線(xiàn)程繼續(xù)運(yùn)行,如果兩個(gè)線(xiàn)程都是基本沒(méi)有I/O操作等會(huì)引起阻塞的操作(比如忙循環(huán)),那么線(xiàn)程將會(huì)在自己的執(zhí)行時(shí)間結(jié)束(本質(zhì)上是超出CPU時(shí)間占比)后被CFS程序調(diào)度出當(dāng)前正在執(zhí)行的狀態(tài),另外一個(gè)線(xiàn)程獲得CPU資源開(kāi)始執(zhí)行。
需要注意的是,進(jìn)入CFS(或其他調(diào)度算法)需要調(diào)度事件的產(chǎn)生,調(diào)度事件可以是線(xiàn)程自己調(diào)用函數(shù)顯示執(zhí)行調(diào)度,或者線(xiàn)程執(zhí)行I/O操作等會(huì)進(jìn)入阻塞的操作以及等待的事件發(fā)生線(xiàn)程進(jìn)入運(yùn)行態(tài)等(內(nèi)核中有固定的調(diào)度點(diǎn)),如果一個(gè)程序一直處于忙計(jì)算(比如忙循環(huán)程序),那么就會(huì)需要系統(tǒng)軟時(shí)間中斷來(lái)產(chǎn)生調(diào)度事件從而進(jìn)入CFS調(diào)度判斷下一個(gè)可執(zhí)行程序。目前我們的Linux內(nèi)核普遍配置的系統(tǒng)軟時(shí)間中斷產(chǎn)生的頻率為100Hz,也即每10ms產(chǎn)生一次中斷,那么系統(tǒng)中只有忙計(jì)算類(lèi)(如忙循環(huán))線(xiàn)程的情況下,只有當(dāng)系統(tǒng)產(chǎn)生軟時(shí)間中斷時(shí),CFS才會(huì)被調(diào)用來(lái)判斷下一個(gè)執(zhí)行的線(xiàn)程并使其占有CPU開(kāi)始執(zhí)行,這個(gè)現(xiàn)象看起來(lái)就好象是Linux調(diào)度策略簡(jiǎn)單的給每個(gè)線(xiàn)程分配了10ms的時(shí)間片,其實(shí)并不是這樣。如果系統(tǒng)中同時(shí)有忙計(jì)算類(lèi)的線(xiàn)程和經(jīng)常進(jìn)行I/O操作類(lèi)的線(xiàn)程,由于I/O類(lèi)線(xiàn)程基本處于等待事件的阻塞態(tài)中,執(zhí)行的時(shí)間很少,而計(jì)算類(lèi)線(xiàn)程在執(zhí)行的時(shí)間會(huì)比較長(zhǎng),如果計(jì)算類(lèi)線(xiàn)程正在執(zhí)行時(shí),I/O類(lèi)線(xiàn)程等待的事件發(fā)生了,CFS馬上就會(huì)判斷出I/O類(lèi)線(xiàn)程在之前時(shí)間段內(nèi)執(zhí)行的時(shí)間很少,即已使用的CPU占比與分配給他的相比很小,而計(jì)算類(lèi)線(xiàn)程很有可能已經(jīng)超過(guò)了分配的CPU占比,那么CFS將會(huì)馬上使I/O類(lèi)的線(xiàn)程占有CPU開(kāi)始執(zhí)行,如此系統(tǒng)總是能及時(shí)響應(yīng)I/O類(lèi)線(xiàn)程。
3、SCHED_FIFO和SCHED_RR
SCHED_FIFO和SCHED_RR是實(shí)時(shí)線(xiàn)程使用的調(diào)度管理算法。SCHED_FIFO即先進(jìn)先出,處于相同優(yōu)先級(jí)的實(shí)時(shí)線(xiàn)程會(huì)根據(jù)進(jìn)入運(yùn)行態(tài)的次序依次執(zhí)行。正在執(zhí)行的線(xiàn)程會(huì)一直執(zhí)行直到線(xiàn)程阻塞或者其主動(dòng)調(diào)用調(diào)度線(xiàn)程放棄執(zhí)行,處于此調(diào)度策略下的線(xiàn)程沒(méi)有預(yù)先分配的時(shí)間片,可以永遠(yuǎn)執(zhí)行下去。只有擁有更高實(shí)時(shí)優(yōu)先級(jí)且處于SCHED_RR或者SCHED_FIFO管理下的線(xiàn)程能搶占正在運(yùn)行的實(shí)時(shí)線(xiàn)程。
SCHED_RR在SCHED_FIFO的基礎(chǔ)上會(huì)預(yù)先給定線(xiàn)程一個(gè)時(shí)間片,時(shí)間片達(dá)到后會(huì)使其他相同優(yōu)先級(jí)的線(xiàn)程開(kāi)始執(zhí)行。SCHED_RR的時(shí)間片輪詢(xún)機(jī)制只對(duì)同等實(shí)時(shí)優(yōu)先級(jí)的線(xiàn)程有效,更高實(shí)時(shí)優(yōu)先級(jí)的線(xiàn)程總是會(huì)搶占正在執(zhí)行的線(xiàn)程,而低優(yōu)先級(jí)的線(xiàn)程不能搶占高優(yōu)先級(jí)的線(xiàn)程,即使其時(shí)間片已到。
實(shí)時(shí)線(xiàn)程優(yōu)先級(jí)高于所有普通線(xiàn)程,如果有實(shí)時(shí)線(xiàn)程處于運(yùn)行態(tài),則系統(tǒng)調(diào)度時(shí)一定會(huì)選擇調(diào)用實(shí)時(shí)線(xiàn)程;正在運(yùn)行的實(shí)時(shí)線(xiàn)程只會(huì)被擁有更高實(shí)時(shí)優(yōu)先級(jí)的線(xiàn)程搶占。所以在應(yīng)用中如果需要將某個(gè)線(xiàn)程設(shè)置為實(shí)時(shí)線(xiàn)程,則需要用戶(hù)自己確保該線(xiàn)程不會(huì)處于忙執(zhí)行而完全占用CPU資源,導(dǎo)致其他普通線(xiàn)程沒(méi)法獲得CPU資源而一直被阻塞得不到執(zhí)行,并且需要合理給予優(yōu)先級(jí)的值,太高有可能會(huì)影響重要系統(tǒng)線(xiàn)程的運(yùn)行。所有用戶(hù)態(tài)線(xiàn)程默認(rèn)沒(méi)有實(shí)時(shí)優(yōu)先級(jí),都屬于普通線(xiàn)程。
4、相關(guān)接口函數(shù)
Linux系統(tǒng)提供了一系列函數(shù),這些函數(shù)可以讓用戶(hù)方便的修改線(xiàn)程/進(jìn)程的優(yōu)先級(jí)(包括nice值和real-time priority)、以及修改調(diào)度策略、設(shè)置運(yùn)行線(xiàn)程的CPU核心等。下面簡(jiǎn)單介紹一下常用的函數(shù)。
(1)修改nice值
int nice ( int incr )
將調(diào)用進(jìn)程的nice值增加incr,incr為負(fù)數(shù)是提高優(yōu)先級(jí),為正數(shù)時(shí)降低優(yōu)先級(jí)。成功返回0。
int setpriority ( int which, id_t who, int prio )
將指定的線(xiàn)程/線(xiàn)程組/用戶(hù)的nice值設(shè)置為prio,whice對(duì)應(yīng)可以取值PRIO_PROCESS、PRIO_PGRP、PRIO_USER,who對(duì)應(yīng)為線(xiàn)程/進(jìn)程id,組id或者用戶(hù)id,prio取值范圍為-20~19。成功返回0,錯(cuò)誤返回-1。
(2)修改real-time priority以及調(diào)度策略
int pthread_attr_setschedparam (pthread_attr_t *attr, const struct sched_param *param)
設(shè)置調(diào)度屬性。對(duì)于SCHED_FIFO和SCHED_RR,sched_param值包含 int sched_priority,也即real-time priority。下面所有param定義相同。
int pthread_attr_getschedparam (const pthread_attr_t *attr, struct sched_param *param)
獲取調(diào)度屬性。
int pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy)
設(shè)置調(diào)度策略,policy可設(shè)置為SCHED_FIFO,SCHED_RR和SCHED_NORMAL。
int pthread_attr_getschedpolicy (const pthread_attr_t *attr, int *policy)
獲取調(diào)度策略。
int sched_setparam (pid_t pid, const struct sched_param *param)
設(shè)置pid進(jìn)程的real-time priority,需要pid進(jìn)程出具SCHED_FIFO或者SCHED_RR調(diào)度策略管理下。
int sched_getparam(pid_t pid, struct sched_param *param)
獲取real-time priority。
int sched_setscheduler (pid_t pid, int policy, const struct sched_param *param)
設(shè)置pid進(jìn)程的調(diào)度策略以及調(diào)度屬性。
int sched_getscheduler (pid_t pid)
返回pid進(jìn)程調(diào)度策略。
(3)設(shè)置線(xiàn)程在哪個(gè)CPU核心上運(yùn)行
int pthread_setaffinity_np (pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset)
int pthread_getaffinity_np (pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset)
int sched_setaffinity (pid_t pid, size_t cpusetsize, const cpu_set_t *cpuset)
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *cpuset)
設(shè)置/獲取線(xiàn)程可以運(yùn)行的CPU核心, cpusetsize 可以設(shè)置為sizeof(cpu_set_t), cpuset可以用宏CPU_ZERO和CPU_SET設(shè)置,函數(shù)設(shè)置成功后,線(xiàn)程將只會(huì)在設(shè)置的CPU核心(比如8核CPU可以設(shè)置核心1、3、4)上運(yùn)行,如果cpuset只指定了一個(gè)核心,那么線(xiàn)程將只會(huì)在此核心上運(yùn)行。函數(shù)出錯(cuò)返回-1,成功返回0。
關(guān)于以上函數(shù)以及更多與調(diào)度相關(guān)的函數(shù)的詳細(xì)信息請(qǐng)參考
http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html
http://pubs.opengroup.org/onlinepubs/7908799/xsh/sched.h.html
以及相關(guān)函數(shù)的Linux man手冊(cè)和其他相關(guān)書(shū)籍資料。
5、編程示例
下面提供一個(gè)示例程序,程序用于展示real-time priority對(duì)于進(jìn)程運(yùn)行時(shí)調(diào)度管理的影響,程序運(yùn)行于ESM6802(雙核)工控主板上。程序會(huì)在主進(jìn)程中使用fork新建一個(gè)進(jìn)程,然后調(diào)用sched_setscheduler設(shè)置子進(jìn)程的實(shí)時(shí)優(yōu)先級(jí)為30, 同時(shí)設(shè)置其使用SCHED_FIFO調(diào)度策略,而主進(jìn)程只有nice值為0 的普通優(yōu)先級(jí)。兩個(gè)進(jìn)程主體部分相同,均為在忙循環(huán)中置高然后置低一位GPIO的輸出電平,通過(guò)示波器觀察GPIO的狀態(tài),如果進(jìn)程一直執(zhí)行,則會(huì)看到連續(xù)的周期較固定的方波,而如果進(jìn)程被其他進(jìn)程搶占,則會(huì)看到GPIO的狀態(tài)很長(zhǎng)時(shí)間沒(méi)有發(fā)生變化,以此來(lái)展示實(shí)時(shí)優(yōu)先級(jí)對(duì)系統(tǒng)調(diào)度的影響。程序部分代碼如下:
int gpio = GPIO6;
structsched_param rt_param = {
.sched_priority = 30 }; //實(shí)時(shí)優(yōu)先級(jí)30
child_pid = fork(); //創(chuàng)建子進(jìn)程
if(child_pid!=0) //child_pid不等于0為主進(jìn)程,等于0為子進(jìn)程
{
if( -1 == sched_setscheduler ( child_pid, SCHED_FIFO, &rt_param ) )
printf("sched_setscheduler failed\n");
//設(shè)置子進(jìn)程實(shí)時(shí)優(yōu)先級(jí)以及調(diào)度算法
printf ( "child_pid = %d\n", child_pid );
gpio = GPIO5; //主進(jìn)程和子進(jìn)程操作不同的GPIO
}
printf("Driver esm6800_gpio test\n");
fd = open("/dev/esm6800_gpio", O_RDWR);
printf("open file = %d\n", fd);
rc = GPIO_OutEnable(fd, 0xff); //set GPIO as output
if(rc < 0)
{
printf("GPIO_OutEnable::failed %d\n", rc);
returnrc;
}
for(;;) //無(wú)限循環(huán)
{
//忙循環(huán)中置高然后置低gpio輸出電平
rc = GPIO_OutSet(fd, gpio); //使GPIO輸出高電平
if(rc < 0)
{
printf("GPIO_OutSet::failed %d\n", rc);
returnrc;
}
rc = GPIO_OutClear(fd, gpio); //使GPIO輸出低電平
if(rc < 0)
{
printf("GPIO_OutClear::failed %d\n", rc);
returnrc;
}
}
使用示波器查看到的GPIO狀態(tài)如下圖,其中黃色信號(hào)為主進(jìn)程操作的GPIO5,藍(lán)色信號(hào)為有實(shí)時(shí)優(yōu)先級(jí)的子進(jìn)程操作的GPIO6:
可以看出藍(lán)色信號(hào)代表的擁有實(shí)時(shí)優(yōu)先級(jí)的進(jìn)程一直處于執(zhí)行當(dāng)中,沒(méi)有被其他低優(yōu)先級(jí)的進(jìn)程搶占,而黃色信號(hào)代表的普通優(yōu)先級(jí)的程序gpio狀態(tài)切換有很明顯的中斷,也即其他進(jìn)程被搶占而中斷執(zhí)行。此結(jié)果與第一節(jié)介紹的Linux調(diào)度策略一致:實(shí)時(shí)線(xiàn)程只會(huì)被擁有更高實(shí)時(shí)優(yōu)先級(jí)的線(xiàn)程搶占,處于SCHED_FIFO下的實(shí)時(shí)線(xiàn)程可以無(wú)限執(zhí)行。
用戶(hù)在實(shí)際編程中應(yīng)該仔細(xì)規(guī)劃自己的程序,合理利用系統(tǒng)調(diào)度接口函數(shù),來(lái)優(yōu)化自己程序的執(zhí)行效果,同時(shí)避免錯(cuò)誤的使用導(dǎo)致系統(tǒng)正常運(yùn)行。
-
Linux
+關(guān)注
關(guān)注
87文章
11123瀏覽量
207900 -
嵌入式主板
+關(guān)注
關(guān)注
7文章
6081瀏覽量
34939
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論