Linux进程优先级系统——设置实时进程优先级

前言

出处@https://www.cnblogs.com/qinwanlin/p/8631185.html

最近研发的产品出了点小bug,最后查到根本原因是,其中一个进程A使用基于FIFO的实时进程优先级,而另一个进程B是使用普通调度的进程优先级,而A和B两个进程是互相通信的,进程B会被饿死,而进程A也不能正常工作。分析问题过程中查找了一些资料,以下记录一些特别注意的点。

Linux进程调度的三种策略

(1)SCHED_OTHER,分时调度策略

(2)SCHED_FIFO,实时调度策略,先到先服务

(3)SCHED_RR,实时调度策略,时间片轮转 

由于相关内容比较多,本人也末必比别人讲得更清楚,关于进程调度更详细的内容建议读《深入理解Linux内核》,网上的很多内容都是从此而来,也不一定比这本书讲得好。另可参考此链接

https://blog.csdn.net/maximuszhou/article/details/42042161

前面遇到bug的进程A就是使用SCHED_FIFO调度策略的,而进程B没有经过设置,默认是SCHED_OTHER。

如何设置为实时进程

查找资料的时候发现有个链接问,为什么设置FIFO策略,但和预想的不一致。链接在此 http://ask.csdn.net/questions/254095

从代码看是因为设置的方法不对,直接上代码,可以设置进程和线程的调度策略
 

复制代码

 1  #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <pthread.h>
 4 #include <sched.h>
 5 
 6 
 7 pid_t pid = getpid();
 8 struct sched_param param;
 9 param.sched_priority = sched_get_priority_max(SCHED_FIFO);   // 也可用SCHED_RR
10 sched_setscheduler(pid, SCHED_RR, &param);                   // 设置当前进程
11 pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);   // 设置当前线程

复制代码

可以通过top命令查看进程是否成功,如果为"rt"表示是实时进程了。如果不成功,可能是权限问题,需要roo权限。

调整进程优先级

 如果不调整调度策略,也可以提升进程优先级,使得进程得到更多的CPU,特别是交互式程序,用户体检更好。代码很简单,只需要调用nice(int n)函数即可。n的有效范围是-20~19,数值越小表示优先级越高。具体内容不在此复制粘贴,还是看《深入理解Linux内核》比较靠谱。

浅析Linux线程调度

     在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。

    对于下面三种调度策略SCHED_OTHER, SCHED_IDLE, SCHED_BATCH,其调度优先级sched_priority是不起作用的,即可以看成其调度优先级为0;调度策略SCHED_FIFO和SCHED_RR是实时策略,他们的调度值范围是1到99,数值越大优先级越高,另外实时调度策略的线程总是比前面三种通常的调度策略优先级更高。通常,调度器会为每个可能的调度优先级(sched_priority value)维护一个可运行的线程列表,并且是以最高静态优先级列表头部的线程作为下次调度的线程。所有的调度都是抢占式的:如果一个具有更高静态优先级的线程转换为可以运行了,那么当前运行的线程会被强制进入其等待的队列中。下面介绍几种常见的调度策略:

    SCHED_OTHER:该策略是是默认的Linux分时调度(time-sharing scheduling)策略,它是Linux线程默认的调度策略。SCHED_OTHER策略的静态优先级总是为0,对于该策略列表上的线程,调度器是基于动态优先级(dynamic priority)来调度的,动态优先级是跟nice中相关(nice值可以由接口nice, setpriority,sched_setattr来设置),该值会随着线程的运行时间而动态改变,以确保所有具有SCHED_OTHER策略的线程公平运行。在Linux上,nice值的范围是-20到+19,默认值为0;nice值越大则优先级越低,相比高nice值(低优先级)的进程,低nice值(高优先级)的进程可以获得更多的处理器时间。使用命令ps -el查看系统的进程列表,其中NI列就是进程对应的nice值;使用top命令,看到的NI列也是nice值。运行命令的时候可用nice –n xx cmd来调整cmd任务的nice值,xx的范围是-20~19之间。

    SCHED_FIFO:先入先出调度策略(First in-first out scheduling)。该策略简单的说就是一旦线程占用cpu则一直运行,一直运行直到有更高优先级任务到达或自己放弃。

    SCHED_RR:时间片轮转调度(Round-robin scheduling)。该策略是SCHED_FIFO基础上改进来的,他给每个线程增加了一个时间片限制,当时间片用完后,系统将把该线程置于队列末尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。使用top命令,如果PR列的值为RT,则说明该进程采用的是实时策略,即调度策略是SCHED_FIFO或者为SCHED_RR,而对于非实时调度策略(比如SCHED_OTHER)的进程,该列的值是NI+20,以供Linux内核使用。我们可以通过命令:

ps -eo state,uid,pid,ppid,rtprio,time,comm

来查看进程对应的实时优先级(位于RTPRIO列下),如果有进程对应的列显示“-”,则说明它不是实时进程。注意任何实时策略进程的优先级都高于普通的进程,也就说实时优先级和nice优先级处于互不相交的两个范畴。

    在Linux中,与调度相关的常见接口如下:

 
  1. #include <sched.h>

  2. int sched_get_priority_max(int policy);

该接口获取指定调度策略可以设置的最大优先级,类似的 sched_get_priority_min接口获取调度策略可以设置的最小优先级。在Linux中,对于SCHED_FIFO和SCHED_RR调度策略其优先级为1到99,其他调度策略优先级为0。注意在不同系统上,这个优先级范围可能不一样。

 
  1. #include <pthread.h>

  2. int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

该接口可以用来设置线程的调度策略,即设置线程属性attr。参数policy可以是CHED_FIFO, SCHED_RR和SCHED_OTHER。系统创建线程时,默认的线程调度策略是SCHED_OTHER。类似可以通过接口

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

获取线程的调度策略。

 
  1. #include <pthread.h>

  2. int pthread_attr_setschedparam(pthread_attr_t *attr,

  3. const struct sched_param *param);

该接口可以用来设置线程的调度优先级。结构sched_param定义如下:

 
  1. struct sched_param {

  2. int sched_priority; /* Scheduling priority */

  3. };

类似的的接口,可以用来获取线程调度的优先级:

 
  1. int pthread_attr_getschedparam(const pthread_attr_t *attr,

  2. struct sched_param *param);

 
  1. #include <sched.h>

  2. int sched_yield(void);

调用该接口可以使得当前线程主动交出CPU,并把该线程放到相应调度队列的末尾。如果当前线程是最高优先级队列中唯一的线程,则在调用sched_yield后,该线程继续保持运行。

 
  1. #include <sched.h>

  2. int sched_setaffinity(pid_t pid,

  3. size_t cpusetsize,const cpu_set_t *mask);

该接口可以用来设置线程的CPU亲和性(CPU affinity),设置线程的亲和性可以使得线程绑定到一个或多个指定的CPU上运行。在多处理器系统上,设置CPU亲和性可以提高性能(主要原因是尽可能避免了cache失效和切换到其他CPU的消耗)。CPU亲和性掩码是由cpu_set_t结果来实现的,该结构体需要用预定义好的宏来操作;参数pid是指定线程的TID,可以通过gettid()来获取,即线程在内核中对应进程id,若pid为0,则设置的是调用线程的CPU亲和性,注意用getpid()获取的是主线程的id;参数cpusetsize的值通常是mask的大小,即sizeof(mask)。除了这个接口外,设置线程亲和性接口还有:

 
  1. #include <pthread.h>

  2. int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,

  3. const cpu_set_t *cpuset);

  4. int pthread_attr_setaffinity_np(pthread_attr_t *attr,

  5. size_t cpusetsize, const cpu_set_t *cpuset);

通过fork创建的子进程继承父进程的CPU亲和性,通过 execve()后,亲和性仍然保持不变。我们可以下面命令来查看多核cpu的负载:

    I)cat /proc/cpuinfo  查看所有cpu的信息;

    II)top命令,然后再输入1,则显示多个cpu的使用信息;

    III)top命令,然后按下f,进入top Current Fields设置页面,然后按下j,表示要求显示进程使用那个cpu,回车后,回到刚才界面,此时P 显示此进程使用哪个CPU。

    下面是测试代码:

 
  1. #include <stdio.h>

  2. #include <pthread.h>

  3. #include <sched.h>

  4. #include <assert.h>

  5.  
  6. static int get_thread_policy(pthread_attr_t *attr)

  7. {

  8. int policy;

  9. int rs = pthread_attr_getschedpolicy(attr,&policy);

  10. assert(rs==0);

  11.  
  12. switch(policy)

  13. {

  14. case SCHED_FIFO:

  15. printf("policy=SCHED_FIFO\n");

  16. break;

  17.  
  18. case SCHED_RR:

  19. printf("policy=SCHED_RR\n");

  20. break;

  21.  
  22. case SCHED_OTHER:

  23. printf("policy=SCHED_OTHER\n");

  24. break;

  25.  
  26. default:

  27. printf("policy=UNKNOWN\n");

  28. break;

  29. }

  30. return policy;

  31. }

  32.  
  33. static void show_thread_priority(pthread_attr_t *attr,int policy)

  34. {

  35. int priority = sched_get_priority_max(policy);

  36. assert(priority != -1);

  37. printf("max_priority=%d\n",priority);

  38.  
  39. priority= sched_get_priority_min(policy);

  40. assert(priority != -1);

  41. printf("min_priority=%d\n",priority);

  42. }

  43.  
  44. static int get_thread_priority(pthread_attr_t *attr)

  45. {

  46. struct sched_param param;

  47. int rs = pthread_attr_getschedparam(attr,¶m);

  48. assert(rs == 0);

  49.  
  50. printf("priority=%d\n",param.__sched_priority);

  51. return param.__sched_priority;

  52. }

  53.  
  54. static void set_thread_policy(pthread_attr_t *attr,int policy)

  55. {

  56. int rs = pthread_attr_setschedpolicy(attr,policy);

  57. assert(rs==0);

  58. }

  59.  
  60. int main(void)

  61. {

  62. pthread_attr_t attr;

  63. int rs;

  64.  
  65. rs = pthread_attr_init(&attr);

  66. assert(rs==0);

  67.  
  68. int policy = get_thread_policy(&attr);

  69.  
  70. printf("Show current configuration of priority\n");

  71. get_thread_policy(&attr);

  72. show_thread_priority(&attr,policy);

  73.  
  74. printf("show SCHED_FIFO of priority\n");

  75. show_thread_priority(&attr,SCHED_FIFO);

  76.  
  77. printf("show SCHED_RR of priority\n");

  78. show_thread_priority(&attr,SCHED_RR);

  79.  
  80. printf("show priority of current thread\n");

  81. get_thread_priority(&attr);

  82.  
  83. printf("Set thread policy\n");

  84.  
  85. printf("set SCHED_FIFO policy\n");

  86. set_thread_policy(&attr,SCHED_FIFO);

  87. get_thread_policy(&attr);

  88. get_thread_priority(&attr);

  89.  
  90. printf("set SCHED_RR policy\n");

  91. set_thread_policy(&attr,SCHED_RR);

  92. get_thread_policy(&attr);

  93.  
  94. printf("Restore current policy\n");

  95. set_thread_policy(&attr,policy);

  96. get_thread_priority(&attr);

  97.  
  98. rs = pthread_attr_destroy(&attr);

  99. assert(rs==0);

  100.  
  101. return 0;

  102. }

编译和运行程序结果如下:

 
  1. $gcc -Wall -lpthread hack_thread_sched.c -o hack_thread_sched

  2. $./hack_thread_sched

  3. policy=SCHED_OTHER

  4. Show current configuration of priority

  5. policy=SCHED_OTHER

  6. max_priority=0

  7. min_priority=0

  8. show SCHED_FIFO of priority

  9. max_priority=99

  10. min_priority=1

  11. show SCHED_RR of priority

  12. max_priority=99

  13. min_priority=1

  14. show priority of current thread

  15. priority=0

  16. Set thread policy

  17. set SCHED_FIFO policy

  18. policy=SCHED_FIFO

  19. priority=0

  20. set SCHED_RR policy

  21. policy=SCHED_RR

  22. Restore current policy

  23. priority=0

从输出结果,我们可以看到:

    I)线程默认的调度策略为SCHED_OTHER,并且最大和最小调度优先级都是0。

    II)调度策略SCHED_FIFO和SCHED_RR的优先级范围为1到99,并且初始设置时对应的调度优先级初始值为0。

    在Linux中,调度程序是一个叫schedule()的函数,该函数调用的频率很高,由它来决定是否要执行进程的切换,如果要切换的话,切换到那个进程等。那么在Linux中,在什么情况下要执行这个调度程序呢?我们把这种情况叫作调度时机。Linux调度时机主要有:

   I)进程状态转换的时刻:进程终止、进程睡眠(比如I/O阻塞就会导致这种情况),还比如进程调用sleep()或exit()等函数进行状态转换。 

   II)当前进程的时间片用完时。

     III)设备驱动程序,当设备驱动程序执行长而重复的任务时,在每次反复循环中,驱动程序读检查是否需要调度,如果必要,则调用调度程序schedule()放弃CPU。

     IV)进程从中断、异常及系统调用返回到用户态时。

猜你喜欢

转载自blog.csdn.net/yangming2466/article/details/83280643