订阅
纠错
加入自媒体

Linux :多处理器遇到实时进程和普通进程的程序设计

2021-06-16 13:58
道哥分享
关注

失败是成功之母,这篇文章就是一次真实的失败调试记录。

通过这篇文章,您能深刻体验到 Linux 系统中下面几个概念:

实时进程和普通进程的调度策略;

Linux 中混乱的进程优先级是如何计算的;

CPU亲和性的测试;

多处理器(SMP)遇到实时进程和普通进程的程序设计;

道哥的脑袋被门夹了一下的短路经历;

背景知识:Linux 调度策略

关于进程的调度策略,不同的操作系统有不同的整体目标,因此调度算法也就各不相同。

这需要根据进程的类型(计算密集型?IO密集型?)、优先级等因素来进行选择。

对于 Linux  x86 平台来说,一般采用的是 CFS:完全公平调度算法。

之所以叫做完全公平,是因为操作系统以每个线程占用 CPU 的比率来进行动态的计算,操作系统希望每一个进程都能够平均的使用 CPU 这个资源,雨露均沾。

我们在创建一个线程的时候,默认就是这个调度算法 SCHED_OTHER,默认的优先级为 0。

PS: 在 Linux 操作系统中,线程的内核对象与进程的内核对象(其实就是一些结构体变量)是很类似的,所以线程可以说是轻量级的进程。

在本文中,可以把线程约等于进程,有的地方也可能称为任务,在不同的语境下有一些不同的惯用说法。

可以这么理解:如果系统中一共有 N 个进程,那么每个进程会得到 1/N 的执行机会。每个进程执行一段时间之后,就被调出,换下一个进程执行。

如果这个 N 的数量太大了,导致每个进程刚开始执行时,分给它的时间就到了。如果这个时候就进行任务调度,那么系统的资源就耗费在进程上下文切换上去了。

因此,操作系统引入了最小粒度,也就是每个进程都有一个最小的执行时间保证,称作时间片。

除了 SCHED_OTHER 调度算法,Linux 系统还支持两种实时调度策略:

1. SCHED_FIFO:根据进程的优先级进行调度,一旦抢占到 CPU 则一直运行,直达自己主动放弃或被被更高优先级的进程抢占;

2. SCHED_RR:在 SCHED_FIFO 的基础上,加上了时间片的概念。当一个进程抢占到 CPU 之后,运行到一定的时间后,调度器会把这个进程放在 CPU 中,当前优先级进程队列的末尾,然后选择另一个相同优先级的进程来执行;

本文想测试的就是 SCHED_FIFO 与普通的 SCHED_OTHER 这两种调度策略混合的情况。

背景知识:Linux 线程优先级

在 Linux 系统中,优先级的管理显得比较混乱,先看下面这张图:

这张图表示的是内核中的优先级,分为两段。

前面的数值 0-99 是实时任务,后面的数值 100-139 是普通任务。

数值越低,代表这个任务的优先级越高。

数值越低,代表这个任务的优先级越高。

数值越低,代表这个任务的优先级越高。

再强调一下,以上是从内核角度来看的优先级。

好了,重点来了:

我们在应用层创建线程的时候,设置了一个优先级数值,这是从应用层角度来看的优先级数值。

但是内核并不会直接使用应用层设置的这个数值,而是经过了一定的运算,才得到内核中所使用的优先级数值(0 ~ 139)。

1. 对于实时任务

我们在创建线程的时候,可以通过下面这样的方式设置优先级数值(0 ~ 99):

struct sched_param param;
param.__sched_priority = xxx;

当创建线程函数进入内核层面的时候,内核通过下面这个公式来计算真正的优先级数值:

kernel priority = 100 - 1 - param.__sched_priority

如果应用层传入数值 0,那么在内核中优先级数值就是 99(100 - 1 - 0 = 99),在所有实时任务中,它的优先级是最低的。

如果应用层传输数值 99,那么在内核中优先级数值就是 0(100 - 1 - 99 = 0),在所有实时任务中,它的优先级是最高的。

因此,从应用层的角度看,传输人优先级数值越大,线程的优先级就越高;数值越小,优先级就越低。

与内核角度是完全相反的!

2. 对于普通任务

调整普通任务的优先级,是通过 nice 值来实现的,内核中也有一个公式来把应用层传入的 nice 值,转成内核角度的优先级数值:

kernel prifoity = 100 + 20 + nice

nice 的合法数值是:-20 ~ 19。

如果应用层设置线程 nice 数值为 -20,那么在内核中优先级数值就是 100(100 + 20 + (-20) = 100),在所有的普通任务中,它的优先级是最高的。

如果应用层设置线程 nice 数值为 19,那么在内核中优先级数值就是 139(100 +20 +19 = 139),在所有的普通任务中,它的优先级是最低的。

因此,从应用层的角度看,传输人优先级数值越小,线程的优先级就越高;数值越大,优先级就越低。

与内核角度是完全相同的!

背景知识交代清楚了,终于可以进行代码测试了!

测试代码说明

注意点:

#define _GNU_SOURCE 必须在 #include <sched.h> 之前定义;

#include <sched.h> 必须在 #include <pthread.h> 之前包含进来;

这个顺序能够保证在后面设置继承的 CPU 亲和性时,CPU_SET, CEPU_ZERO这两个函数能被顺利定位到。

// filename: test.c
#define _GNU_SOURCE
#include <unistd.h>  
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <pthread.h>
// 用来打印当前的线程信息:调度策略是什么?优先级是多少?
void get_thread_info(const int thread_index)

   int policy;
   struct sched_param param;
   printf("====> thread_index = %d ", thread_index);
   pthread_getschedparam(pthread_self(), &policy, &param);
   if (SCHED_OTHER == policy)
       printf("thread_index %d: SCHED_OTHER ", thread_index);
   else if (SCHED_FIFO == policy)
       printf("thread_index %d: SCHED_FIFO ", thread_index);
   else if (SCHED_RR == policy)
       printf("thread_index %d: SCHED_RR ", thread_index);
   printf("thread_index %d: priority = %d ", thread_index, param.sched_priority);

// 线程函数,
void *thread_routine(void *args)

   // 参数是:线程索引号。四个线程,索引号从 1 到 4,打印信息中使用。
   int thread_index = *(int *)args;
   // 为了确保所有的线程都创建完毕,让线程睡眠1秒。
   sleep(1);
   // 打印一下线程相关信息:调度策略、优先级。

1  2  3  下一页>  
声明: 本文由入驻维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

人工智能 猎头职位 更多
扫码关注公众号
OFweek人工智能网
获取更多精彩内容
文章纠错
x
*文字标题:
*纠错内容:
联系邮箱:
*验 证 码:

粤公网安备 44030502002758号