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列):

1
2
3
4
5
6
7
8
9
10
11
12
13
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.测试代码-查看线程的调度策略


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#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.测试代码-多线程的优先级配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#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.测试代码-进程的优先权配置


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#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。

 

  

 

posted on   能量星星  阅读(1073)  评论(1编辑  收藏  举报

编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示