linux线程调度策略

news2025/1/19 14:14:55

系统中既有分时调度,又有时间片轮转调度和先进先出调度

学习这个主要为了在linux多线程中,解决几条指令间延时在1-2ms内;
1.比如之前处理过:给一个板子发送一个can指令,接着需要给另外一个模块发送移动指令,且两则间隔要求不超过4ms;
2.比如现在又遇到一个:给一个模块发一个串口rtu指令(阻塞的),然后给另外一个模块发一个串口rtu指令(阻塞的),然后给一个板子发can移动指令,且三者之间的间隔固定,偏差不超过2ms;

linux系统默认的调度方式

对于 Linux x86 平台来说,一般采用的是 CFS:完全公平调度算法。之所以叫做完全公平,是因为操作系统以每个线程占用 CPU 的比率来进行动态的计算,操作系统希望每一个进程都能够平均的使用 CPU 这个资源,雨露均沾。

我们在创建一个线程的时候,默认就是这个调度算法 SCHED_OTHER,默认的优先级为 0。(这个我在多个平台和板子进行了验证)

Linux 系统还支持两种实时调度策略:

  1. SCHED_FIFO:根据进程的优先级进行调度,一旦抢占到 CPU 则一直运行,直达自己主动放弃或被被更高优先级的进程抢占;
  2. SCHED_RR:在 SCHED_FIFO 的基础上,加上了时间片的概念。当一个进程抢占到 CPU 之后,运行到一定的时间后,调度器会把这个进程放在 CPU 中,当前优先级进程队列的末尾,然后选择另一个相同优先级的进程来执行;

Linux 线程优先级

https://zhuanlan.zhihu.com/p/387806696
Linux 线程优先级
这张图表示的是内核中的优先级,分为两段。

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

数值越低,代表这个任务的优先级越高。以上是从内核角度来看的优先级。
但是内核并不会直接使用应用层设置的这个数值,而是经过了一定的运算,才得到内核中所使用的优先级数值(0 ~ 139)。

对于实时任务

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

struct sched_param param;
param.__sched_priority = xxx;

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

kernel priority = 100 - 1 - param.__sched_priority

与内核角度是相反的!

SCHED_FIFO : 0-99
SCHED_RR: 0-99

对于普通任务

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

kernel prifoity = 100 + 20 + nice

nice 的合法数值是:-20 ~ 19。
因此,从应用层的角度看,传输人优先级数值越小,线程的优先级就越高;数值越大,优先级就越低。
与内核角度是完全相同的

测试代码说明

// 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("\n====> thread_index = %d \n", thread_index);

    pthread_getschedparam(pthread_self(), &policy, &param);
    if (SCHED_OTHER == policy)
        printf("thread_index %d: SCHED_OTHER \n", thread_index);
    else if (SCHED_FIFO == policy)
        printf("thread_index %d: SCHED_FIFO \n", thread_index);
    else if (SCHED_RR == policy)
        printf("thread_index %d: SCHED_RR \n", thread_index);

    printf("thread_index %d: priority = %d \n", thread_index, param.sched_priority);
}

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

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

    // 打印一下线程相关信息:调度策略、优先级。
    get_thread_info(thread_index);

    long num = 0;
    for (int i = 0; i < 20; i++)
    {
        for (int j = 0; j < 5000000; j++)
        {
            // 没什么意义,纯粹是模拟 CPU 密集计算。
            float f1 = ((i+1) * 345.45) * 12.3 * 45.6 / 78.9 / ((j+1) * 4567.89);
            float f2 = (i+1) * 12.3 * 45.6 / 78.9 * (j+1);
            float f3 = f1 / f2;
            
        }usleep(100000);
        
        // 打印计数信息,为了能看到某个线程正在执行
        printf("thread_index %d: num = %ld \n", thread_index, num++);
    }
    
    // 线程执行结束
    printf("thread_index %d: exit \n", thread_index);
    return 0;
}

int main(void)
{
    // 一共创建四个线程:0和1-实时线程,2和3-普通线程(非实时)
    int thread_num = 4;
    
    // 分配的线程索引号,会传递给线程参数
    int index[4] = {1, 2, 3, 4};

    // 用来保存 4 个线程的 id 号
    pthread_t ppid[4];
    
    // 用来设置 2 个实时线程的属性:调度策略和优先级
    pthread_attr_t attr[2];
    struct sched_param param[2];

    // 实时线程,必须由 root 用户才能创建
    if (0 != getuid())
    {
        printf("Please run as root \n");
        //exit(0);
    }

    // 创建 4 个线程
    for (int i = 0; i < thread_num; i++)
    {
	 
				cpu_set_t mask;
		int cpus = sysconf(_SC_NPROCESSORS_CONF);
		CPU_ZERO(&mask);
		CPU_SET(0, &mask);
		if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
		{
			printf("set thread affinity failed! \n");
		}	
        if (i <= 1)    // 前2个创建实时线程
        {
            // 初始化线程属性
            pthread_attr_init(&attr[i]);
            
            // 设置调度策略为:SCHED_FIFO SCHED_RR
            int res = pthread_attr_setschedpolicy(&attr[i], SCHED_FIFO);
            if(res != 0) printf("i =1 or 2 \n");
            // 设置优先级为 51,52。
            param[i].__sched_priority = 51 + i;
            res = pthread_attr_setschedparam(&attr[i], &param[i]);
            if(res != 0) printf("i =1 or 2 \n");
            // 设置线程属性:不要继承 main 线程的调度策略和优先级。
            pthread_attr_setinheritsched(&attr[i], PTHREAD_EXPLICIT_SCHED);
            
            // 创建线程
            pthread_create(&ppid[i], &attr[i],(void *)thread_routine, (void *)&index[i]);
        }
        else        // 后两个创建普通线程
        {
            pthread_create(&ppid[i], 0, (void *)thread_routine, (void *)&index[i]);
        }
        
    }

    // 等待 4 个线程执行结束
    for (int i = 0; i < 4; i++)
        pthread_join(ppid[i], 0);

    for (int i = 0; i < 2; i++)
        pthread_attr_destroy(&attr[i]);
}

编译:

gcc -o test test.c -lpthread

预期:

实时性优先级高的先执行,后面普通优先级在执行

note:

1.可能需要使用root权限
2.如果是多核的片子,需要把几个线程设置到一个核进行跑,才会出预期,不然可能出现同步情况

1,RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。

2,当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。

3,RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的位置决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。

调度策略

系统中的每个线程都关联了一个调度策略和优先级,调度器正是根据调度策略和优先级进行线程调度的,从而决定哪个线程将在下一个调度中得到CPU时间;

对于普通调度策略(SCHED_OTHER, SCHED_IDLE, SCHED_BATCH),优先级是没有作用的,实际上必须是0,这样实时测量线程可以马上抢占普通线程;

对于实时调度策略(SCHED_FIFO, SCHED_RR),优先级需要设置为1(最小)-99(最大)中的某个值;

调度器为每个优先级维护了一个待调度线程的列表,当需要进行调度时,调度器访问最高优先级的非空的列表,然后从列表头选择一个线程调度运行;

线程的调度策略决定了一个可调度线程应该放在哪个列表的哪个位置;

所有的调度都是支持抢占的,如果有高优先级的线程准备好运行了,那么它将抢占当前运行的线程,这使得当前线程被重新加入到等待调度的链表中;调度策略决定了在同一个优先级列表中的可调度线程的顺序;

其它查询和设置模式和优先级的函数

伪代码:
查看policy

int ******::get_thread_policy( pthread_attr_t &attr )
{
     int policy;
     int rs = pthread_attr_getschedpolicy( &attr, &policy );
     if(rs != 0)
     {
         apl_error("[%s] set err! \n",__func__);
     }
     switch ( policy )
      {
         case SCHED_FIFO:
                  cout << "policy = SCHED_FIFO" << endl;
                 break;
         case SCHED_RR:
                  cout << "policy = SCHED_RR" << endl;
                 break;
         case SCHED_OTHER:
                  cout << "policy = SCHED_OTHER" << endl;
                 break;
         default:
                  cout << "policy = UNKNOWN" << endl;
                 break;
      }
     return policy;
}

查询前模式的最大最小优先级

void ******::show_thread_priority( pthread_attr_t &attr, int policy )
{
     int priority = sched_get_priority_max( policy );
      cout << "max_priority = " << priority << endl;
      priority = sched_get_priority_min( policy );
      cout << "min_priority = " << priority << endl;
}

查询优先级

int ******::getThreadPriority( pthread_attr_t &attr )
{
     struct sched_param param;
     int rs = pthread_attr_getschedparam( &attr, &param );
     if(rs != 0)
     {
         apl_error("[%s] set err! \n",__func__);
     }
     apl_info("[%s] priority:%d \n" ,__func__ ,param.sched_priority);
     return param.sched_priority;
}

设置优先级

void ******::setThreadPolicy( pthread_attr_t &attr, int policy )
{
     int rs = pthread_attr_setschedpolicy( &attr, policy );
     if(rs != 0)
     {
         apl_error("[%s] set err! \n",__func__);
     }
     getThreadPolicy( attr );
}

设置模式

// 设置线程调度策略为SCHED_FIFO
 int res = pthread_attr_setschedpolicy(&(pUnit->startMethodTaskAttr), SCHED_FIFO);
  if(res != 0) apl_error("[%s] pthread_attr_setschedpolicy set err! \n",__func__);
// 设置线程优先级为99
                  param.sched_priority = 99;
                  res = pthread_attr_setschedparam(&(pUnit->startMethodTaskAttr), &param);

设置不继承
// 设置线程属性:不继承 main 线程的调度策略和优先级
pthread_attr_setinheritsched(&(*Attr), PTHREAD_EXPLICIT_SCHED);

其它

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

sched_rt_runtime_us 表示所有实时进程一次能占有CPU的最长时间,缺省是1秒,当这个时间被用完,他们必须等待下面参数sched_rt_period_us 表示的时间(缺省是0.95s)才能被重新调度。
sched_rt_period_us 表示下一次调度实时进程的时间。
看来这两个参数是在调度实时进程和非实时进程之间做调整和平衡。

两个文件的默认值是1s和0.95s,表示每秒种为一个周期,在这个周期中,所有实时进程运行的总时间不超过0.95秒,剩下的至少0.05秒会留给普通进程。也就是说,实时进程占有不超过95%的CPU。而在这两个文件出现之前,实时进程的运行时间是没有限制的,如果一直有处于TASK_RUNNING状态的实时进程,则普通进程会一直不能得到运行。相当于sched_rt_runtime_us等于sched_rt_period_us。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32设备驱动-CCS811数字气体空气质量传感器驱动

CCS811数字气体空气质量传感器驱动 文章目录 CCS811数字气体空气质量传感器驱动1、CCS811介绍2、硬件准备3、软件准备4、驱动实现1、CCS811介绍 CCS811 是一种低功耗数字气体传感器解决方案,它集成了用于检测通常在室内发现的低水平 VOC 的气体传感器解决方案、微控制器单元 …

机器学习——分类算法

K-近邻算法(KNN) K Nearest Neighbor算法又叫KNN算法&#xff0c;它的原理是如果一个样本在特征空间中的k个最相似&#xff08;即特征空间中最邻近&#xff09;的样本中的大多数属于某一个类别&#xff0c;则该样本也属于这个类别。 两个样本间距离可通过欧式距离计算&#x…

FE_HTML标签学习

1 图像标签 <body> <img src"./image/img.png" alt"图片标签属性" title"提示文本&#xff0c;鼠标放到图像上显示的文字" > </body>2 超链接标签 <body><h4>1.外部链接</h4><a href"http://www.…

4点决定你在银行的到手薪资

众所周知&#xff0c;银行的工资水平在整个国家中也是排名靠前的。然而&#xff0c;全国范围之内&#xff0c;有4000多家银行&#xff0c;有20多万个网点&#xff0c;不同的银行&#xff0c;甚至同一银行不同区域的网点之间的工资差别都是比较大的&#xff0c;即使是在同一家银…

手搭手SpringBoot之REST接口风格

REST一种软件架构风格 REST即表述性状态传递&#xff08;英文&#xff1a;Representational State Transfer&#xff0c;简称REST,中文&#xff1a;表示层状态转移&#xff09;是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计…

gRPC-Go源码解读二 传输层数据处理流程

本篇文章主要介绍gRPC Client传输层的处理流程&#xff0c;如有疑问&#xff0c;欢迎指教。 gRPC版本&#xff1a; 1.54.0-dev gRPC基于http2传输&#xff0c;传输层主要处理http2相关的内容。RFC7540制定了http2协议规范&#xff0c;因此&#xff0c;这部分代码的逻辑绝大部分…

科普|FCC的卫星标准 为什么又说是FCC Part25呢?

我们今天介绍的FCC的卫星标准&#xff0c;在美国是作为一种法律规定&#xff0c;具有法律效力的标准&#xff0c;通常又称为法规文件。 01 — FCC Part 25 我们先从CFR说起&#xff0c;《美国联邦法规》&#xff08; Code of Federal Regulations &#xff0c;简称CFR&#…

【JAVAEE】网络原理之网络发展史

目录 &#x1f381;1. 独立模式 &#x1f383;2. 网络互连 &#x1f388;2.1 局域网 LAN ✨2.1.1 基于网线直连 &#x1f451;2.2.2 基于集线器组建 &#x1f48b;2.2.3 基于交换机组建 &#x1f457;2.2.4 基于交换机与路由器组建 &#x1f388;2.2 广域网 21世纪是一…

我的第一台电脑------计算机类专业学生购置电脑的一些个人心得

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

Web 攻防之业务安全:接口参数账号篡改测试(修改别人邮箱 || 手机号为自己的)

Web 攻防之业务安全&#xff1a;接口参数账号篡改测试. 业务安全是指保护业务系统免受安全威胁的措施或手段。广义的业务安全应包括业务运行的软硬件平台&#xff08;操作系统、数据库&#xff0c;中间件等&#xff09;、业务系统自身&#xff08;软件或设备&#xff09;、业务…

HCIP之LSP静态搭建实验

目录 HCIP之LSP静态搭建实验 实验图 基本配置 R1 R2 R3 R4 配置方法 搭建从1.0 - 4.0 网段的LSP 搭建静态路由 配置MPLS 配置LSR - ID 激活MPLS 全局激活 接口激活 搭建静态LSP 搭建入站LSR R1配置 搭建中转LSR R2配置 R3配置 搭建出站LSR R4配置 搭建从…

Java语言-----泛型的认识

目录 一.什么是泛型 二.泛型类的使用 2.1泛型类的定义 2.2泛型类的数组使用 三.泛型的上界 四.泛型的方法 五.泛型与集合 &#x1f63d;个人主页&#xff1a; tq02的博客_CSDN博客-C语言,Java领域博主 &#x1f308;梦的目标&#xff1a;努力学习&#xff0c;向Java进发…

八大数据库全面对比,让你明确数据库怎么去选!

随着互联网和大数据时代的到来&#xff0c;各种数据管理技术也在迅猛发展。而在数据管理技术中&#xff0c;数据库无疑是最重要的一环。现今市场上涌现出了众多数据库产品&#xff0c;不同的数据库产品针对不同的业务需求和应用场景&#xff0c;有着不同的特点和优势。本文将介…

【双碳系列】LEAP碳排放预测、LCA生命周期、GAMS电力、CGE一般均衡模型

本文围绕双碳专题分为五大内容&#xff0c;分别为&#xff1a; 基于LEAP模型的能源环境发展、碳排放建模预测及不确定性分析实践应用 (qq.com) 双碳目标下农田温室气体排放模拟实践技术应用 (qq.com) 环境影响与碳排放生命周期评估应用及案例分析 (qq.com) “双碳”目标下资…

如何实现一个可靠的 UDP

QUIC是如何实现可靠传输的&#xff1f; 市面上的基于UDP协议实现的可靠传输协议的成熟方案&#xff0c;应用在HTTP/3上。 UDP报文头部和TCP报文头部夹着三层头部 Packet Header Packet Header细分这两种&#xff1a; Long Packet Header 用于首次建立连接Short Packet Hea…

深元ai智慧工地视频分析盒子提高建筑施工现场安全效率

随着社会的快速发展&#xff0c;建筑行业安全问题日益受到重视。为了解决传统人工巡查的诸多问题&#xff0c;AI智慧工地视频分析盒子应运而生&#xff0c;通过人工智能技术&#xff0c;全面提高建筑施工现场的安全工作效率。 一、AI智慧工地视频分析盒子解决传统巡查的痛点 …

【产品设计】Android 和 IOS 的交互设计对垒

在手机操作系统百花齐放的年代&#xff0c;也是产品经理最头疼的年代&#xff0c;因为需要根据不同的操作系统做出不同的设计。而如今&#xff0c;手机操作系统基本只剩下安卓和IOS两大阵营&#xff0c;只需处理好安卓和IOS交互上的差异部分就可以做好产品设计了。 手机操作系统…

不良事件上报系统源码 有演示,已在多家医院运营多年

不良事件上报系统源码&#xff0c;医院安全不良事件管理系统源码 技术架构&#xff1a;前后端分离&#xff0c;仓储模式&#xff0c;BS架构&#xff0c;有演示&#xff0c;已在多家医院完美运营。 相关技术&#xff1a;PHPvscodevue2elementlaravel8mysql5.7 文末获取联系&am…

【每日一练】基础题目练习

1、打印1-100之间所有的素数 素数&#xff1a;(也说质数) 数学上指在大于1的整数中只能被1和它本身整除的数。如2、3、5、7、11、43、109。过去。 方法一&#xff1a; 如果数据i能够被[2 &#xff0c;qsrt(i)]之间的数整除&#xff0c;则表示这个数不是素数。 当一个数na*b时&a…

CVE漏洞复现-CVE-2022-22947-Spring Cloud Gateway RCE

CVE-2022-22947-Spring Cloud Gateway RCE 基本介绍 微服务架构与Spring Cloud 最开始时&#xff0c;我们开发java项目时&#xff0c;所有的代码都在一个工程里&#xff0c;我们把它称为单体架构。当我们的项目的代码量越来越大时&#xff0c;开发的成员越来越多时&#xff…