疑问
两个线程分别有不同的调度策略,一个SCHED_FIFO,一个SCHED_OTHER,按照之前的理解,SCHED_FIFO实时线程一定会占用CPU一直运行,导致SCHED_OTHER的普通线程得不到CPU,事实是这样么?
验证
写了一小段代码,一个是验证SCHED_FIFO的高优先级线程会不会抢占低优先级的线程,在不主动放弃的情况下一直运行,一个是测试普通优先级的线程会不会得到CPU时间;
1 1 #include <stdio.h> 2 2 #include <stdlib.h> 3 3 #include <unistd.h> 4 4 #define __USE_GNU 5 5 #include <pthread.h> 6 6 #include <sched.h> 7 7 8 8 long long a = 0; 9 9 long long b = 0; 1010 1111 int attach_cpu(int cpu_index) 1212 { 1313 int cpu_num = sysconf(_SC_NPROCESSORS_CONF); 1414 if (cpu_index < 0 || cpu_index >= cpu_num) 1515 { 1616 printf("cpu index ERROR!\n"); 1717 return -1; 1818 } 1919 2020 cpu_set_t mask; 2121 CPU_ZERO(&mask); 2222 CPU_SET(cpu_index, &mask); 2323 2424 if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) 2525 { 2626 printf("set affinity np ERROR!\n"); 2727 return -1; 2828 } 2929 3030 return 0; 3131 } 3232 3333 3434 void *thread1(void *param) 3535 { 3636 attach_cpu(0); 3737 3838 long long i; 3939 for (i = 0; i < 10000000000; i++) 4040 { 4141 a++; 4242 } 4343 } 4444 4545 void *thread2(void *param) 4646 { 4747 attach_cpu(0); 4848 4949 long long i; 5050 for (i = 0; i < 10000000000; i++) 5151 { 5252 b++; 5353 } 5454 } 5555 5656 5757 int main() 5858 { 5959 pthread_t t1; 6060 pthread_t t2; 6161 6262 int policy; 6363 struct sched_param param; 6464 6565 if (pthread_create(&t1, NULL, thread1, NULL) < 0) 6666 { 6767 printf("create t1 failed!\n"); 6868 return -1; 6969 } 7070 7171 param.sched_priority = 10; 7272 policy = SCHED_FIFO; 7373 pthread_setschedparam(t1, policy, ¶m); 7474 7575 if (pthread_create(&t2, NULL, thread2, NULL) < 0) 7676 { 7777 printf("create t2 failed!\n"); 7878 return -1; 7979 } 8080 8181 param.sched_priority = 11; 8282 policy = SCHED_FIFO; 8383 pthread_setschedparam(t2, policy, ¶m); 8484 8585 while (1) 8686 { 8787 printf("a=%lld, b=%lld\n", a, b); 8888 sleep(1); 8989 } 9090 9191 pthread_join(t1, NULL); 9292 pthread_join(t2, NULL); 9393 9494 return 0; 9595 }
通过运行结果来看:
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时间;