Linux進程調度


1.調度


  調度的重點是CPU處理任務的各種策略,線程實際上是共享一些資源的一系列進程而已,因此線程就是輕量級進程,因此在Linux中,線程的調度是按照進程的調度方式來進行調度的,也就是說線程是調度單元。

  關於進程和線程的優先級:

    進程的優先級取值范圍是[-20, 20],值越低表示優先權越高,分給進程的CPU時間越多。

    而線程的優先級只有當調度策略是SCHED_FIFO或SCHED_RR時才有作用,優先級越高表示越先執行。

  必須明確的是線程時調度的基本單位,所以最終要看線程的優先級。即如果所有進程都使用OTHER策略,則進程的優先權高會導致其里面的線程優先權高,如果有個進程里面的線程使用了實時策略,自然是這個線程的優先權高。

2.進程調度優先級


  進程優先權由nice值決定,nice值范圍是[-20, 20],nice值越小表示優先權越高。

  進程優先級相關函數:

    int setpriority(int which, int who, int prio);

       可用來設置進程、進程組和用戶的進程的nice值。參數which有三種數值, 參數who則依which值有不同定義:

        PRIO_PROCESS   who 為進程識別碼
        PRIO_PGRP     who 為進程的組識別碼
        PRIO_USER     who 為用戶識別碼

      參數prio介於-20至20之間.。代表進程執行優先權, 數值越低代表有較高的優先次序, 執行會較頻繁。

    int nice (int nic);

      該函數用來修改當前進程的nice值,參數nic是一個增量值,即在當前的nice值上面增加這個值(可以是負數)。

    int getpriority(int which, int who);

      可用來取得進程、進程組和用戶的進程的nice值。參數和上面的一樣。

3.線程調度策略和線程調度優先級


  在Linux中,調度器是基於線程的調度策略(scheduling policy)和靜態調度優先級(static scheduling priority)來決定那個線程來運行的。

  SCHED_FIFO或SCHED_RR這兩種方式支持的靜態優先級為1-99(數值越高,優先級越高),而SCHED_OTHER的靜態優先級固定為0(並且無法修改這個優先級)。

  所有的調度策略都是搶占式的,即如果一個具有更高靜態優先級的線程轉換為可以運行了,那么當前運行的線程會被強制進入其等待的隊列中,由於SCHED_FIFO、SCHED_RR優先級(優先級范圍:1-99)高於所有SCHED_OTHER的進程(優先級固定0),所以只要他們能夠運行,則在他們運行完之前,所有SCHED_OTHER的進程的都沒有得到執行的機會。

  另外,創建線程時,必須在創建之前設置線程的繼承策略為PTHREAD_EXPLICIT_SCHED,這時候創建的子線程才可以設置自己的調度策略和優先級。即,如果線程的繼承策略為PTHREAD_INHERIT_SCHED,則子線程不能夠自己修改調度策略與優先級。

  【1】調度策略

    [1]SCHED_OTHER :分時調度策略

      它是Linux線程默認的調度策略。

      當策略時SCHED_OTHER時,該策略上的線程列表的優先級總是為0。此時調度器基於nice值來調度,該值會隨着線程的運行時間而動態改變,以確保所有具有SCHED_OTHER策略的線程公平運行,nice越小,被調度的概率越大,也就是曾經使用了cpu最少的進程將會得到優先調度。

    [2]SCHED_FIFO:先入先出調度策略

      該策略簡單的說就是一旦線程占用cpu則一直運行,一直運行直到有更高優先級任務到達或自己放棄。

    [3]SCHED_RR:時間片輪轉調度

      該策略給每個線程增加了一個時間片限制,當時間片用完后,系統將把該線程置於隊列末尾。放在隊列尾保證了所有具有相同優先級的RR任務的調度公平。

  【2】線程調度和優先級相關函數:

    int sched_get_priority_max(int policy);
    int sched_get_priority_min(int policy);

      這兩個函數用來獲取某個調度策略的優先級取值范圍。

    int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
    int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

      這兩個函數用來獲取和設置當前線程的靜態優先級。

    int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
    int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

      這兩個函數用來獲取和設置線程的調度策略。

    int sched_yield(void);

      這個函數會讓出當前線程的CPU占有權,然后把線程放到靜態優先隊列的尾端,此時可以讓另一個級別等於或高於當前線程的線程先運行。如果沒有符合條件的線程,那么這個函數將會立刻返回然后繼續執行當前線程的程序。

      即在函數中調用此函數會暫停該函數的運行,等待cpu重新調度后從暫停處繼續運行。

    int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);

      該接口可以用來設置線程的CPU親和性(CPU affinity),設置線程的親和性可以使得線程綁定到一個或多個指定的CPU上運行。在多處理器系統上,設置CPU親和性可以提高性能(主要原因是盡可能避免了cache失效和切換到其他CPU的消耗)。

4.進程調度時機


  Linux由函數schedule()實現調度程序。它的任務是從運行隊列的鏈表rq中找到一個進程,並隨后將CPU分配給這個進程。

  schedule()函數的調用時機:

    [1]進程狀態轉換的時刻:進程終止、進程睡眠(比如I/O阻塞就會導致這種情況),還比如進程調用sleep()或exit()等函數進行狀態轉換。

    [2]當前進程的時間片用完時。

    [3]設備驅動程序,設備驅動程序在每次反復循環中,驅動程序讀檢查是否需要調度,如果必要,則調用調度程序schedule()放棄CPU。

    [4]進程從中斷、異常及系統調用返回到用戶態時。

5.查看linux進程的調度策略


  ps -eo class,cmd

  該命令可以輸出進程的調度策略。CLS列的可能值:

    -       not reported

    TS      SCHED_OTHER

    FF      SCHED_FIFO

    RR      SCHED_RR

    B       SCHED_BATCH

    ISO     SCHED_ISO

    IDL     SCHED_IDLE

    ?       unknown value

6.問題


  問題1:有以下情況:

    進程1:綁定CPU0,優先權為20,並且設置線程調度策略為FIFO(或者RR),線程優先級隨意。

    進程2:綁定CPU0,優先權為0,線程調度策略默認為OTHER。

    則此時一共兩個線程,但是進程1的線程調度策略為實時調度,如果沒有使用調度函數時,進程1會占用所有CPU。

    因此可以看到線程才是CPU調度的基本單位。即多個進程都使用默認調度策略(OTHER)時,則線程調度的策略繼承主線程(進程)的策略,並且優先權由進程優先權決定;如果有線程使用了自己的調度策略,則就會有自己的優先權。

  問題2:有以下情況:

    進程1:綁定CPU0,優先權為20,2個線程,線程1使用默認調度策略,線程2使用FIFO。

    進程2:綁定CPU0,優先權為0,2個線程,線程1和線程2都使用默認調度策略。

    則此時一共4個線程,線程的優先級:進程1的線程2 > 進程2的線程1、2 > 進程1的線程1。此時如果沒有使用調度函數時,進程1的線程2會占用所有CPU,當進程1線程2執行完之后,則進程2的兩個線程會比進程1的線程1占用cpu多。

  問題3:由於開發的程序都使用的默認策略,所以常用的是用setpriority()或者nice()函數設置進程的優先權。通過top可以看到進程的優先權設置(NI列):

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                               
    1 root      20   0  191380   4408   2508 S   0.0  0.0   1:04.97 systemd       
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.07 kthreadd      
    3 root      20   0       0      0      0 S   0.0  0.0   0:04.41 ksoftirqd/0   
    5 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H  
    6 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kworker/u16:0 
    8 root      rt   0       0      0      0 S   0.0  0.0   0:00.44 migration/0   
    9 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh        
   10 root      20   0       0      0      0 S   0.0  0.0   1:34.24 rcu_sched     
   11 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain 
   12 root      rt   0       0      0      0 S   0.0  0.0   0:04.85 watchdog/0    
   13 root      rt   0       0      0      0 S   0.0  0.0   0:05.47 watchdog/1    
   14 root      rt   0       0      0      0 S   0.0  0.0   0:01.12 migration/1   

7.測試代碼-查看線程的調度策略


#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stdlib.h>

pthread_attr_t attr;

static void print_thread_info()
{	
	int policy;
	int priority, max_priority, min_priority;
	struct sched_param param;

	// 輸出調度策略
	if(pthread_attr_getschedpolicy(&attr, &policy) < 0){
		printf("error: pthread_attr_getschedpolicy, errno:%d.\n", errno);
		return ;
	}

	switch(policy){
		case SCHED_FIFO:
			printf("current thread policy: SCHED_FIFO\n");
			break;
			
		case SCHED_RR:
			printf("current thread policy: SCHED_RR\n");
			break;
		
		case SCHED_OTHER:
			printf("current thread policy: SCHED_OTHER\n");
			break;
		
		default:
			printf("current thread policy: %d\n", policy);
			break;
	}
	
	// 輸出優先級、最小最大優先級
	if(pthread_attr_getschedparam(&attr, &param) < 0){
		printf("error: pthread_attr_getschedparam, errno:%d.\n", errno);
		return ;
	}
	
	priority = param.__sched_priority;
	max_priority = sched_get_priority_max(policy);
	min_priority = sched_get_priority_min(policy);
		
	printf("current thread priority:%d\n", priority);
	printf("current thread max_priority:%d, min_priority:%d\n", max_priority, min_priority);

}

static void set_thread_policy(int policy)
{
	if(pthread_attr_setschedpolicy(&attr, policy) < 0){
		printf("error: set_thread_policy, errno:%d.\n", errno);
		return ;
	}
}

int main(void)
{
	if(pthread_attr_init(&attr) < 0){
		printf("error: pthread_attr_init.\n");
		return ;
	}

	print_thread_info();

	set_thread_policy(SCHED_FIFO);
	printf("\nset policy SCHED_FIFO------------------------------\n");
	print_thread_info();
	
	set_thread_policy(SCHED_RR);
	printf("\nset policy SCHED_RR------------------------------\n");
	print_thread_info();

	pthread_attr_destroy(&attr);
	return 0;
}

  代碼編譯方法:gcc -lpthread -o test test.c

  雖然不加-lpthread也可以編譯運行,但是會產生錯誤輸出。

8.測試代碼-多線程的優先級配置


#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sched.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

struct timeval end;
unsigned long long count1, count2;

#define Task1_Prio      7
#define Task2_Prio      6

pthread_barrier_t barrier;

void *Task1(void *arg);
void *Task2(void *arg);

int finish()
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	if(tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec){
		return 1;
	}

	return 0;
}

int main(void)
{
    int policy,inher;
    pthread_t tid1, tid2;
    pthread_attr_t attr;
    struct sched_param param;
	cpu_set_t mask;

	//設置為使用單核cpu
	CPU_ZERO(&mask);
	CPU_SET(0, &mask);	
	if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
		printf("sched_setaffinity failue, error:%s\n", strerror(errno));
		return 0; 
	}	
	
    pthread_barrier_init(&barrier,NULL,2+1);

    pthread_attr_init(&attr);
    pthread_attr_getinheritsched(&attr, &inher);

    if(inher == PTHREAD_EXPLICIT_SCHED)
        printf("getinheritsched PTHREAD_EXPLICIT_SCHED\n");
    else if(inher == PTHREAD_INHERIT_SCHED){
        printf("getinheritsched PTHREAD_INHERIT_SCHED, set PTHREAD_EXPLICIT_SCHED\n");
        inher = PTHREAD_EXPLICIT_SCHED;
		pthread_attr_setinheritsched(&attr, inher);    //必須設置繼承策略為PTHREAD_EXPLICIT_SCHED時,子線程才能修改自己的策略和優先級
    }
   
    policy = SCHED_RR; // policy = SCHED_FIFO;
    pthread_attr_setschedpolicy(&attr, policy);

	gettimeofday(&end, NULL);
	end.tv_sec += 10;
	
    param.sched_priority = Task1_Prio;
    pthread_attr_setschedparam(&attr,&param);
    pthread_create(&tid1, &attr,Task1,NULL);

    param.sched_priority = Task2_Prio;
    pthread_attr_setschedparam(&attr,&param);
    pthread_create(&tid2, &attr,Task2,NULL);
    
    sleep(1);
    pthread_barrier_wait(&barrier);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
}

void *Task1(void *arg)
{
    pthread_barrier_wait(&barrier);
	while(1){
		if(++count1 == 0){
			printf("Task1 counter overflow\n");
			exit(0);
		}
		
		if(finish()){
			printf("Task1, count:%llu\n", count1);
			break;
		}

		//usleep(100);
	}
    pthread_exit(NULL);
}

void *Task2(void *arg)
{
    pthread_barrier_wait(&barrier);
	while(1){
		if(++count2 == 0){
			printf("Task2 counter overflow\n");
			exit(0);
		}
		
		if(finish()){
			printf("Task2, count:%llu\n", count2);
			break;
		}
	}

    pthread_exit(NULL);
}

  以上代碼創建了2個線程,Task1的優先級為7,Task1的優先級為6,調度策略為RR。因此程序運行時Task1會占用全部cpu(因為Task1中沒有引起調度的函數),導致當Task1結束時才輪到Task2執行。

  而如果Task1中加入usleep()函數,此函數會導致進程調度,從而使得Task2有機會執行。

9.測試代碼-進程的優先權配置


#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sched.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

unsigned long long count;
struct timeval end;

void checktime(char *str)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	if(tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec){
		printf("%s count :%lld\n", str, count);
		exit(0);
	}
}

int main(void)
{
	char *s;
	pid_t pid;
	cpu_set_t mask;

	CPU_ZERO(&mask);
	CPU_SET(0, &mask);	
	if(sched_setaffinity(0, sizeof(mask), &mask) == -1) {
		printf("sched_setaffinity failue, error:%s\n", strerror(errno));
		exit(0);
	}	

	gettimeofday(&end, NULL);
	end.tv_sec += 10;

	pid = fork();
	if(pid < 0){
		printf("fork error.\n");
		exit(0);
	}

	if(pid == 0){
		s = "child";

		if(nice(10) < 0){		//setpriority(PRIO_PROCESS, getpid(), 20)
			printf("adjust child nice error.\n");
			exit(0);
		}
		
		printf("child pid:%d, priority:%d\n", getpid(), getpriority(PRIO_PROCESS, getpid()));
	}else{
		s = "parent";

		if(nice(1) < 0){		//setpriority(PRIO_PROCESS, getpid(), 1)
			printf("adjust parent nice error.\n");
			exit(0);
		}
		
		printf("parent pid:%d, priority:%d\n", getpid(), getpriority(PRIO_PROCESS, getpid()));
	}

	while(1){
		if(++count == 0){
			printf("%s counter overflow\n", s);
			exit(0);
		}
		
		checktime(s);
	}

	return 0;
}

  由於子進程的nice值增加了10,父進程的nice值增加了1,所以父進程會占用更多的CPU。

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM