疑問
兩個線程分別有不同的調度策略,一個SCHED_FIFO,一個SCHED_OTHER,按照之前的理解,SCHED_FIFO實時線程一定會占用CPU一直運行,導致SCHED_OTHER的普通線程得不到CPU,事實是這樣么?
驗證
寫了一小段代碼,一個是驗證SCHED_FIFO的高優先級線程會不會搶占低優先級的線程,在不主動放棄的情況下一直運行,一個是測試普通優先級的線程會不會得到CPU時間;
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #define __USE_GNU 5 #include <pthread.h> 6 #include <sched.h> 7 8 long long a = 0; 9 long long b = 0; 10 11 int attach_cpu(int cpu_index) 12 { 13 int cpu_num = sysconf(_SC_NPROCESSORS_CONF); 14 if (cpu_index < 0 || cpu_index >= cpu_num) 15 { 16 printf("cpu index ERROR!\n"); 17 return -1; 18 } 19 20 cpu_set_t mask; 21 CPU_ZERO(&mask); 22 CPU_SET(cpu_index, &mask); 23 24 if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) 25 { 26 printf("set affinity np ERROR!\n"); 27 return -1; 28 } 29 30 return 0; 31 } 32 33 34 void *thread1(void *param) 35 { 36 attach_cpu(0); 37 38 long long i; 39 for (i = 0; i < 10000000000; i++) 40 { 41 a++; 42 } 43 } 44 45 void *thread2(void *param) 46 { 47 attach_cpu(0); 48 49 long long i; 50 for (i = 0; i < 10000000000; i++) 51 { 52 b++; 53 } 54 } 55 56 57 int main() 58 { 59 pthread_t t1; 60 pthread_t t2; 61 62 int policy; 63 struct sched_param param; 64 65 if (pthread_create(&t1, NULL, thread1, NULL) < 0) 66 { 67 printf("create t1 failed!\n"); 68 return -1; 69 } 70 71 param.sched_priority = 10; 72 policy = SCHED_FIFO; 73 pthread_setschedparam(t1, policy, ¶m); 74 75 if (pthread_create(&t2, NULL, thread2, NULL) < 0) 76 { 77 printf("create t2 failed!\n"); 78 return -1; 79 } 80 81 param.sched_priority = 11; 82 policy = SCHED_FIFO; 83 pthread_setschedparam(t2, policy, ¶m); 84 85 while (1) 86 { 87 printf("a=%lld, b=%lld\n", a, b); 88 sleep(1); 89 } 90 91 pthread_join(t1, NULL); 92 pthread_join(t2, NULL); 93 94 return 0; 95 }
通過運行結果來看:
1. SCHED_FIFO的高優先級線程會搶占低優先級線程;
2.SCHED_FIFO的高優先級線程一旦占用CPU並不主動放棄CPU的情況下將一直占用,此時低優先級線程得不到CPU時間;
3.主線程為SCHED_OTHER普通線程,得到了CPU時間,能夠打印出信息;
原理
對於上述現象在manpage中已經有了很好的描述,以下摘錄一些;前面的疑問在下面的限制實時線程的CPU使用時間部分;
調度策略
系統中的每個線程都關聯了一個調度策略和優先級,調度器正是根據調度策略和優先級進行線程調度的,從而決定哪個線程將在下一個調度中得到CPU時間;
對於普通調度策略(SCHED_OTHER, SCHED_IDLE, SCHED_BATCH),優先級是沒有作用的,實際上必須是0,這樣實時測量線程可以馬上搶占普通線程;
對於實時調度策略(SCHED_FIFO, SCHED_RR),優先級需要設置為1(最小)-99(最大)中的某個值;
調度器為每個優先級維護了一個待調度線程的列表,當需要進行調度時,調度器訪問最高優先級的非空的列表,然后從列表頭選擇一個線程調度運行;
線程的調度策略決定了一個可調度線程應該放在哪個列表的哪個位置;
所有的調度都是支持搶占的,如果有高優先級的線程准備好運行了,那么它將搶占當前運行的線程,這使得當前線程被重新加入到等待調度的鏈表中;調度策略決定了在同一個優先級列表中的可調度線程的順序;
SCHED_FIFO:先進先出調度
SCHED_FIFO線程的優先級必須大於0,當它運行時,一定會搶占正在運行的普通策略的線程(SCHED_OTHER, SCHED_IDLE, SCHED_BATCH);SCHED_FIFO策略是沒有時間片的算法,需要遵循以下規則:
1)如果一個SCHED_FIFO線程被高優先級線程搶占了,那么它將會被添加到該優先級等待列表的首部,以便當所有高優先級的線程阻塞的時候得到繼續運行;
2)當一個阻塞的SCHED_FIFO線程變為可運行時,它將被加入到同優先級列表的尾部;
3)如果通過系統調用改變線程的優先級,則根據不同情況有不同的處理方式:
a)如果優先級提高了,那么線程會被添加到所對應新優先級的尾部,因此,這個線程有可能會搶占當前運行的同優先級的線程;
b)如果優先級沒變,那么線程在列表中的位置不變;
c)如果優先級降低了,那么它將被加入到新優先級列表的首部;
根據POSIX.1-2008規定,除了使用pthread_setschedprio(3)以外,通過使用其他方式改變策略或者優先級會使得線程加入到對應優先級列表的尾部;
4)如果線程調用了sched_yield(2),那么它將被加入到列表的尾部;
SCHED_FIFO會一直運行,直到它被IO請求阻塞,或者被更高優先級的線程搶占,亦或者調用了sched_yield();
SCHED_RR:輪轉調度
SCHED_RR是SCHED_FIFO的簡單增強,除了對於線程占用的時間總量之外,對於SCHED_FIFO適用的規則對於SCHED_RR同樣適用;如果SCHED_RR線程的運行時間大於等於時間總量,那么它將被加入到對應優先級列表的尾部;如果SCHED_RR線程被搶占了,當它繼續運行時它只運行剩余的時間量;時間總量可以通過sched_rr_get_interval()函數獲取;
SCHED_OTHER:默認Linux時間共享調度
SCHED_OTHER只能用於優先級為0的線程,SCHED_OTHER策略是所有不需要實時調度線程的統一標准策略;調度器通過動態優先級來決定調用哪個SCHED_OTHER線程,動態優先級是基於nice值的,nice值隨着等待運行但是未被調度執行的時間總量的增長而增加;這樣的機制保證了所有SCHED_OTHER線程調度的公平性;
限制實時線程的CPU使用時間
SCHED_FIFO, SCHED_RR的線程如果內部是一個非阻塞的死循環,那么它將一直占用CPU,使得其它線程沒有機會運行;
在2.6.25以后出現了限制實時線程運行時間的新方式,可以使用RLIMIT_RTTIME來限制實時線程的CPU占用時間;Linux也提供了兩個proc文件,用於控制為非實時線程運行預留CPU時間;
/proc/sys/kernel/sched_rt_period_us
這個文件中的數值指定了總CPU(100%)時間的寬度值,默認值是1,000,000;
/proc/sys/kernel/sched_rt_runtime_us
這個文件中的數值指定了實時線程可以運行的CPU時間寬度,如果設置為-1,則認為不給非實時線程預留任何運行時間,默認值是950,000,因為第一個文件的總量是1,000,000,也就是說默認配置為非實時線程預留了5%的CPU時間;
manpage連接:http://man7.org/linux/man-pages/man7/sched.7.html