Skip to main content
 首页 » 操作系统

Linux 进程调度-应用内核设置调度策略和优先级

2022年07月19日28myhome

一、进程调度策略设置

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_prioritytask->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, &param) == -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, &param, 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设置和查看进程调度策略和优先级

(1)使用示例

# 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 进行查看。# chrt -f -p 1520 10 将CFS线程设置为 p->prio=89(99-10) 的RT线程。

(2) 设置为idle调度策略,可以用来做不影响性能的负载填充。

//执行死循环: 
let i=0; while true; do let i=i+1; done & 
//设置为 SCHED_IDLE 调度测量: 
chrt -i -p 10585 0

虽然设置为 SCHED_IDLE 调度策略了,但是trace上看其优先级还是120,cat /proc/pid/sched 看policy已经设置成功了,是5。

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。RT优先级 0-99, CFS优先级 100-139, nice取值 -20-19, CFS默认优先级120.

17. 一个有趣的实验

# 死循环 
let i=0; while true; do let i=i+1; done & 
# taskset -p 08 <pid> 
# renice -n 10 -p <pid> 
# chrt -f -p <pid> 15

参考:man


本文参考链接:https://www.cnblogs.com/hellokitty2/p/14547014.html