疑问
两个线程分别有不同的调度策略,一个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时间;