一、進程調度策略設置
1. 函數使用說明
#include <sched.h> int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param); int sched_getscheduler(pid_t pid); struct sched_param { ... int sched_priority; ... };
描述:
sched_setscheduler()為pid中指定的線程設置調度策略和相關參數。 如果pid等於零,則將設置調用線程的調度策略和參數。 參數param的解釋取決於所選策略。 當前,Linux支持以下“常規”(即非實時)調度策略:
SCHED_OTHER:標准循環分時策策;
SCHED_BATCH:用於“批處理”樣式的進程執行;
SCHED_IDLE:用於運行優先級較低的后台作業。
還支持以下“實時”策略,用於支持需要嚴格控制選擇可運行線程來執行的方式的特殊時間緊迫的應用程序。
SCHED_FIFO:先進先出策略;
SCHED_RR:循環策略。
這些策略中的每一個的語義將在下面詳細說明。
sched_getscheduler() 查詢pid標識的線程的調度策略。 如果pid等於零,則將檢索調用線程的策略。
注:需要root權限才能設置成功。sched_setscheduler 設置的RT優先級數值直接寫到task->rt_priority里面,RT線程的task->prio = 99 - task->rt_priority,task->prio的數值越小,RT優先級越高。有個轉換的目的是讓在用戶空間進行代碼設置的時候,sched_param.sched_priority的數值越大,對應的優先級越大。可以看到migration線程的優先級是0(最大)。
1. 調度策略
調度程序是內核組件,它決定接下來將由CPU執行哪個可運行線程。 每個線程都有一個關聯的調度策略和一個靜態調度優先級sched_priority;這些是由 sched_setscheduler() 設置。 調度程序根據調度策略的知識和系統上所有線程的靜態優先級進行決策。
對於根據常規調度策略(SCHED_OTHER,SCHED_IDLE,SCHED_BATCH)之一調度的線程,sched_priority 不在調度決策中使用(必須指定為0)。
根據一種實時策略(SCHED_FIFO,SCHED_RR)調度的進程的 sched_priority 值在1(最低)到99(最高)之間。 (正如數字所暗示的那樣,實時線程總是比正常線程具有更高的優先級。)請注意:POSIX.1-2001要求實現至少支持32種不同的實時策略優先級, 某些系統僅提供此最小值。 可移植程序應使用 sched_get_priority_min(2) 和 sched_get_priority_max(2) 查找特定策略支持的優先級范圍。
從概念上講,調度程序為每個可能的 sched_priority 值維護一個可運行線程的列表。 為了確定接下來運行哪個線程,調度程序將查找具有最高(值最小)靜態優先級的非空列表,並在該列表的開頭選擇線程。
線程的調度策略確定將其插入到相同靜態優先級線程列表中的位置以及如何在列表中移動。
所有調度都是搶先的:如果具有較高靜態優先級的線程准備就緒,可以運行,則當前正在運行的線程將被搶占並返回其靜態優先級的等待列表。 調度策略僅在靜態優先級相等的可運行線程列表中確定順序。
2. SCHED_FIFO 先進先出調度
SCHED_FIFO只能在靜態優先級高於0的情況下使用,這意味着當SCHED_FIFO線程變為可運行時,它將始終立即搶占任何當前正在運行的 SCHED_OTHER,SCHED_BATCH 或 SCHED_IDLE 線程。 SCHED_FIFO 是一種簡單的調度算法,無需進行時間分片。 對於根據 SCHED_FIFO策略調度的線程,適用以下規則:
(1)被另一個更高優先級的線程搶占的 SCHED_FIFO 線程將保持其優先級在列表的開頭,並在所有更高優先級的線程阻塞時立即恢復執行。
(2)當SCHED_FIFO線程變為可運行線程時,將根據優先級將其插入列表的末尾。
(3)調用sched_setscheduler() 或 sched_setparam() 會將pid標識的SCHED_FIFO(或SCHED_RR)線程放在列表的開頭(如果可運行)。 因此,如果具有相同優先級的其它進程,它可能會搶占當前正在運行的線程。(POSIX.1-2001指定該線程應轉到列表的末尾。)
(4)調用sched_yield()的線程將放在列表的末尾。
沒有其他事件會在靜態優先級相等的可運行線程的等待列表中移動以 SCHED_FIFO 策略調度的線程。
SCHED_FIFO 線程將一直運行,直到被I/O請求阻止,被更高優先級的線程搶占或調用 sched_yield(2)。
3. SCHED_RR 循環調度
SCHED_RR 是 SCHED_FIFO 的簡單增強。上面針對 SCHED_FIFO 所述的所有內容也適用於 SCHED_RR,除了允許每個線程僅在最大時間范圍內運行。 如果 SCHED_RR 線程已經運行了等於或大於時間范圍的時間段,則將其放在其優先級列表的末尾。 SCHED_RR 線程已被更高優先級的線程搶占,並隨后在運行線程時恢復執行,將繼續執行完成其循環時間范圍的未到期部分。 可以使用 sched_rr_get_interval(2)獲取時間量的長度。
4. SCHED_OTHER 默認的Linux分時調度
SCHED_OTHER 只能以靜態優先級0使用。SCHED_OTHER是標准的Linux分時調度程序,適用於不需要特殊實時機制的所有線程。 基於僅在此列表內確定的動態優先級,從靜態優先級0列表中選擇要運行的線程。動態優先級基於nice值(由nice(2) 或 setpriority(2)設置),並在線程准備好運行但每次被調度程序拒絕運行時都增加。 這樣可以確保所有 SCHED_OTHER 線程之間取得公平的處理。
5. SCHED_BATCH 計划批處理
(從Linux 2.6.16開始。)SCHED_BATCH 僅可用於靜態優先級0。此策略與 SCHED_OTHER 類似,因為它根據線程的動態優先級(基於nice值)調度線程。 區別在於,此策略將使調度程序始終假定線程占用大量CPU。因此,調度程序將對喚醒行為施加較小的調度損失,從而使該線程在調度決策中受到輕微影響。
此策略對於非交互式但又不想降低其合理價值的工作負載,以及需要確定性調度策略而又不會引起交互(導致工作負載之間的任務)的交互性的工作負載非常有用。
6. SCHED_IDLE 計划非常低優先級的作業
(自Linux 2.6.23開始)SCHED_IDLE 僅可以在靜態優先級0上使用;進程的nice值對此策略沒有影響。
該策略旨在以極低的優先級運行作業(對於 SCHED_OTHER 或 SCHED_BATCH 策略,該值甚至低於+19 nice值)。
7. 重置子進程的調度策略
從Linux 2.6.32開始,可以在調用 sched_setscheduler() 時在策略中對 SCHED_RESET_ON_FORK 標志進行“或”運算。 作為包含此標志的結果,由 fork() 創建的子代不會繼承特權調度策略。 此功能適用於媒體播放應用程序,可用於通過創建多個子進程來防止應用程序逃避 RLIMIT_RTTIME 資源限制(請參閱 getrlimit(2))。
更准確地說,如果指定了 SCHED_RESET_ON_FORK 標志,則以下規則適用於隨后創建的子代:
(1)如果調用線程的調度策略為 SCHED_FIFO 或 SCHED_RR,則該策略將在子進程中重置為 SCHED_OTHER。
(2)如果調用進程的負值nice值,則子進程中的nice值將重置為零。
啟用 SCHED_RESET_ON_FORK 標志后,僅當線程具有 CAP_SYS_NICE 功能時才能將其重置。 在 fork(2) 創建的子進程中禁用此標志。SCHED_RESET_ON_FORK 標志在 sched_getscheduler()返回的策略值中可見
8. 特權和資源限制
在2.6.12之前的Linux內核中,只有特權(CAP_SYS_NICE)線程可以設置非零靜態優先級(即,設置實時調度策略)。
非特權線程可以進行的唯一更改是設置 SCHED_OTHER 策略,並且只有在 sched_setscheduler() 的調用者的有效用戶ID才可以執行此操作。
與要更改其策略的目標線程(即pid指定的線程)的實際或有效用戶ID匹配。
從Linux 2.6.12開始,RLIMIT_RTPRIO 資源限制為 SCHED_RR 和 SCHED_FIFO 策略定義了非特權線程的靜態優先級的上限。
更改調度策略和優先級的規則如下:
(1)如果非特權線程的 RLIMIT_RTPRIO 軟限制為非零,則它可以更改其調度策略和優先級,
但前提是不能將優先級設置為高於其當前優先級和 RLIMIT_RTPRIO 軟限制的最大值的限制。
(2)如果 RLIMIT_RTPRIO 軟限制為0,則唯一允許的更改是降低優先級或切換到非實時策略。
(3)遵循相同的規則,只要進行更改的線程的有效用戶ID與目標線程的實際或有效用戶ID匹配,另一個非特權線程也可以進行這些更改。
(4)特殊規則適用於 SCHED_IDLE。 在2.6.39之前的Linux內核中,在此策略下運行的非特權線程無法更改其策略,無論其 RLIMIT_RTPRIO 資源限制的值如何。
從2.6.39開始的Linux內核中,無特權線程可以切換到 SCHED_BATCH 或 SCHED_NORMAL 策略,只要它的好值落在其 RLIMIT_NICE 資源限制所允許
的范圍內(請參閱getrlimit(2))。
特權(CAP_SYS_NICE)線程將忽略 RLIMIT_RTPRIO 限制; 與較早的內核一樣,它們可以對調度策略和優先級進行任意更改。
有關 RLIMIT_RTPRIO的更多信息,請參見getrlimit(2)。
9. 響應時間
等待I/O的被阻塞的高優先級線程在重新調度之前有一定的響應時間。 設備驅動程序編寫者可以通過使用“慢中斷”中斷處理程序
來大大減少此響應時間。
10. 雜項
子進程在 fork(2) 上繼承調度策略和參數。調度策略和參數在 execve(2) 中保留。
實時過程通常需要使用內存鎖定來避免分頁延遲。 這可以通過 mlock(2) 或 mlockall(2) 完成。
由於在 SCHED_FIFO 或 SCHED_RR 下調度的線程中的無阻塞無限循環將永遠阻塞優先級較低的所有線程,
因此軟件開發人員應始終在控制台上保持以比測試應用程序更高的靜態優先級調度的shell在控制台上可用。
這樣可以緊急終止未按預期阻止或終止的經過測試的實時應用程序。 另請參見 getrlimit(2)中對 RLIMIT_RTTIME 資源限制的描述。
可以使用 sched_setscheduler() 和 sched_getscheduler() 的POSIX系統在<unistd.h>中定義_POSIX_PRIORITY_SCHEDULING。
11. 返回值
成功時,sched_setscheduler()返回零。 成功后,sched_getscheduler()返回線程的策略(非負整數)。 如果出錯,則返回-1,並正確設置errno。
錯誤
EINVAL 調度策略不是公認的策略之一,param為NULL,或者param對策略沒有意義。
EPERM 調用線程沒有適當的特權。
ESRCH 找不到ID為pid的線程。
12. 進程設置使用例子
(1). 試驗代碼
#include <sched.h> #include <stdio.h> int main(int argc,char *argv[]) { struct sched_param param; int maxpri, count; maxpri = sched_get_priority_max(SCHED_FIFO); if(maxpri == -1) { perror("sched_get_priority_max() failed"); return -1; } printf("max priority of SCHED_FIFO is %d\n", maxpri); #if 1 param.sched_priority = maxpri; if (sched_setscheduler(getpid(), SCHED_FIFO, ¶m) == -1) { perror("sched_setscheduler() failed"); return -1; } #endif fork(); fork(); while(1) { count++; } return 0; }
(2). 運行結果
#if 1的時候非常卡,top執行結果
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3819 root rt 0 4208 624 556 R 96.1 0.0 0:29.90 pp 3820 root rt 0 4208 80 0 R 95.7 0.0 0:29.54 pp 3821 root rt 0 4208 80 0 R 93.7 0.0 0:29.26 pp 3822 root rt 0 4208 80 0 R 93.7 0.0 0:29.16 pp
#if 0時不卡,top執行結果
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3830 root 20 0 4208 84 0 R 99.8 0.0 0:08.71 pp 3831 root 20 0 4208 84 0 R 99.8 0.0 0:09.02 pp 3832 root 20 0 4208 84 0 R 99.4 0.0 0:08.63 pp 3829 root 20 0 4208 720 648 R 98.1 0.0 0:08.50 pp
(3) 設置實時進程運行占比
實時進程的優先級比普通進程的有限級高,為了避免流氓實時進程占滿CPU導致普通進程餓死的情況,Linux內核導出文件來限制1s內實時進程運行的時間,默認如下:
# cat /proc/sys/kernel/sched_rt_period_us 1000000 # cat /proc/sys/kernel/sched_rt_runtime_us 950000
即1s內只允許實時進程運行950ms,剩下的50ms給其它進程使用。
試驗:
a. 運行后系統非常非常卡
b. 執行下面這個不卡了
# echo 100000 > /proc/sys/kernel/sched_rt_runtime_us
c. 執行下面這個后系統又重新變的非常非常卡
# echo 950000 > /proc/sys/kernel/sched_rt_runtime_us
此外,還有susfs文件可以指定實時進程調度的每個時間片的大小。
(4) 若想設置更多屬性,可以使用sched_setattr系統調用
int sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags); int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags);
kernel/sched/core.c中導出一系列調度相關的系統調用接口。
13. 線程調度策略設置
1. 線程的調度策略的設置在線程屬性中完成,pthread_attr_setschedpolicy()設置的策略支持的值為 SCHED_FIFO,SCHED_RR 和 SCHED_OTHER,其語義在sched_setscheduler()中進行了描述。
#include <stdio.h> #include <errno.h> #include <string.h> #include <pthread.h> #include <unistd.h> static int pthread_attr_init_with_sched_policy(pthread_attr_t *attr, struct sched_param *param, int rt_priority) { int ret; ret = pthread_attr_init(attr); if (ret) { printf("attr_init error is %s\n", strerror(ret)); return ret; } param->sched_priority = rt_priority; ret = pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED); //有這行,設置優先級才會生效 if (ret) { printf("setinheritsched error is %s\n", strerror(errno)); return ret; } ret = pthread_attr_setschedpolicy(attr, SCHED_RR); if (ret) { printf("setschedpolicy error is %s\n", strerror(errno)); return ret; } ret = pthread_attr_setschedparam(attr, param); if (ret) { printf("setschedpolicy error is %s\n", strerror(errno)); return ret; } return 0; } void * thread_run_function(void *data) { int ret, number; number = *(int*)data; while(1) { #if 0 printf("my thread num is %d\n", number); //sleep(1); #else ret++; #endif } return NULL; } int id[5] = {0, 1, 2, 3, 4}; int main() { int ret, i; pthread_t thread[5]; pthread_attr_t attr; struct sched_param param; #if 1 ret = pthread_attr_init_with_sched_policy(&attr, ¶m, 99); if (ret) { printf("pthread_attr_init_with_sched_policy error\n"); return ret; } #else ret = pthread_attr_init(&attr); if (ret) { printf("attr_init error is %s\n", strerror(ret)); return ret; } #endif for (i = 0; i < 5; i++) { ret = pthread_create(&thread[i], &attr, thread_run_function, &id[i]); if (ret) { printf("pthread_create error is %s\n", strerror(errno)); return ret; } } while(1) { sleep(1); } return 0; }
注:pthread_attr_init(&attr)的attr中設置了RT策略,需要root權限,若是沒有root權限返回1(但是errno沒有)。RT的優先級數值是“99 - param->sched_priority”
14. 內核線程調度策略和優先級設置
參考內核core_ctl.c中"core_ctl/X"線程的設置
15、chrt設置和查看進程調度策略和優先級
# ps -A | grep core_ctl root 492 2 0 0 try_core_ctl 0 S [core_ctl/0] root 493 2 0 0 try_core_ctl 0 S [core_ctl/4] root 494 2 0 0 try_core_ctl 0 S [core_ctl/7] # # chrt -p 492 pid 492's current scheduling policy: SCHED_FIFO pid 492's current scheduling priority: 99
# chrt -f -p <PID> <Priority> # chrt -f -p 10 20 # chrt -p 10 pid 10's current scheduling policy: SCHED_FIFO //-f:設置調度策略為SCHED_FIFO pid 10's current scheduling priority: 20
注: chrt -p 看普通進程優先級不准,都顯示0。所有進程都可以通過:cat /proc/<pid>/task/<tid>/sched | grep prio 進行查看
16. renice設置普通進程優先級
(1) # ps -lA 查看所有進程的優先級,AndroidR上,優先級+nice的和為19,nice取值-20--19。
(2) # renice -n 2 -p 3432 //-n 后面是nice值的增量,可正可負;-p 是進程號。此例表示nice值加2,優先級為19-nice
# ps -lA | grep com.tencent.mm F S UID PID PPID C PRI NI BIT SZ WCHAN TTY TIME CMD 5 D 10261 1782 838 1 6 13 64 1829811 __refrigerator ? 00:00:43 com.tencent.mm # renice -n -3 -p 1782 # ps -lA | grep com.tencent.mm 5 D 10261 1782 838 1 9 10 64 1829811 __refrigerator ? 00:00:43 com.tencent.mm
注:nice=13,對應的內核中的task->static_prio就是120+13.
參考:man