Linux 实时调度器:带宽限制

文章目录

  • 1. 前言
  • 2. 概念
  • 3. 实时进程 的 带宽限制
    • 3.1 实时进程 带宽限制 初始化
    • 3.2 启动 实时进程 带宽 监测定时器
    • 3.3 累加 实时进程 消耗的带宽
    • 3.4 查看 实时进程 带宽消耗情况
    • 3.5 小结

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 概念

Linux 实时调度器(RT scheduler)带宽限制,是指限制系统中实时进程占用的 CPU 时间的配额、比例。和实时进程打过交道的读者,应该有见过如下内核日志:

[ 7957.249361] sched: RT throttling activated

这条日志表示系统中的实时进程消耗的 CPU 时间,已经超过了设置的配额。每个 CPU 分配给实时进程的默认时间配额为 95%

/** period over which we measure -rt task CPU usage in us.* default: 1s*/
unsigned int sysctl_sched_rt_period = 1000000;....../** part of the period that we allow rt tasks to run in us.* default: 0.95s*/
int sysctl_sched_rt_runtime = 950000;

实时进程的 CPU 时间配额,是以 sysctl_sched_rt_period 为一个周期进行统计的;每个统计周期内,分配给实时进程的时间配额sysctl_sched_rt_runtime。默认配置下实时进程 CPU 时间配额占比为 950000 / 1000000 = 95%

3. 实时进程 的 带宽限制

实时进程的带宽限制,就是限制实时进程 CPU 时间占比

3.1 实时进程 带宽限制 初始化

初始化的过程,主要是设置:

1. 实时进程带宽检查周期(sysctl_sched_rt_period)
2. 每周期实时进程允许占用的 CPU 时间上限(sysctl_sched_rt_runtime)
3. 实时进程带宽周期监测定时器
start_kernel()sched_init()
void __init sched_init(void)
{...init_rt_bandwidth(&def_rt_bandwidth, global_rt_period(), global_rt_runtime());...for_each_possible_cpu(i) {struct rq *rq;rq = cpu_rq(i); /* 返回 CPU @i 的运行队列 */.../* 初始化 RT 运行队列 每周期 实时进程 运行时间配额 */rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;...}...
}static inline u64 global_rt_period(void)
{return (u64)sysctl_sched_rt_period * NSEC_PER_USEC;
}static inline u64 global_rt_runtime(void)
{if (sysctl_sched_rt_runtime < 0)return RUNTIME_INF;return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;
}void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64 runtime)
{rt_b->rt_period = ns_to_ktime(period);rt_b->rt_runtime = runtime;raw_spin_lock_init(&rt_b->rt_runtime_lock);/* 初始化 RT 运行队列消耗 CPU 时间、每周期 检查更新 定时器 */hrtimer_init(&rt_b->rt_period_timer,CLOCK_MONOTONIC, HRTIMER_MODE_REL);rt_b->rt_period_timer.function = sched_rt_period_timer;
}

3.2 启动 实时进程 带宽 监测定时器

在实时进程插入到运行队列时,启动实时进程带宽周期监测定时器。过程中,代码会检查是否已经激活定时器,如果已经激活,则不会重复启动定时器。

/* kernel/sched/rt.c */enqueue_task_rt()enqueue_rt_entity(rt_se, flags)__enqueue_rt_entity(rt_se, flags)inc_rt_tasks(rt_se, rt_rq)inc_rt_group(rt_se, rt_rq)start_rt_bandwidth(&def_rt_bandwidth)static void start_rt_bandwidth(struct rt_bandwidth *rt_b)
{...raw_spin_lock(&rt_b->rt_runtime_lock);if (!rt_b->rt_period_active) { /* 判定周期监测定时器是否已经激活 */rt_b->rt_period_active = 1; /* 设置激活标记,防止周期监测定时器到期前重复激活 *//* 启动实时进程带宽周期监测定时器 */hrtimer_forward_now(&rt_b->rt_period_timer, ns_to_ktime(0));hrtimer_start_expires(&rt_b->rt_period_timer, HRTIMER_MODE_ABS_PINNED);}raw_spin_unlock(&rt_b->rt_runtime_lock);
}			

3.3 累加 实时进程 消耗的带宽

累加实时进程消耗的带宽,是指统计实时进程消耗的 CPU 时间。细节如下:

/* kernel/sched/rt.c */static void update_curr_rt(struct rq *rq)
{struct task_struct *curr = rq->curr;struct sched_rt_entity *rt_se = &curr->rt;u64 delta_exec;...delta_exec = rq_clock_task(rq) - curr->se.exec_start;...curr->se.exec_start = rq_clock_task(rq)...if (!rt_bandwidth_enabled())return; /* 未开启实时进程带宽控制,直接返回 */for_each_sched_rt_entity(rt_se) {struct rt_rq *rt_rq = rt_rq_of_se(rt_se);if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {raw_spin_lock(&rt_rq->rt_runtime_lock);rt_rq->rt_time += delta_exec; /* 统计实时进程消耗的 CPU 时间(带宽)到运行队列 */if (sched_rt_runtime_exceeded(rt_rq)) /* 检查实时进程带宽是否超出设定值 */resched_curr(rq);raw_spin_unlock(&rt_rq->rt_runtime_lock);}}
}static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)
{/* 一个周期内,实时进程允许消耗 CPU 总时间的上限值: @rt_rq->rt_runtime */u64 runtime = sched_rt_runtime(rt_rq);.../** 实时进程运行队列是每 CPU 的,实时进程消耗的 CPU 时间* 是分别统计到每个运行队列的。* 如果当前 CPU 运行队列上实时进程消耗总时间已经超过设定* 值 @rt_rq->rt_runtime ,balance_runtime() 尝试从别的 CPU * 借取一部分时间 - 如果别的 CPU 有空闲的分配给实时进程的* 时间的话。** 如果向别的 CPU 借取了时间的话,这时候 @rt_rq->rt_runtime* 会大于设定的阈值(但会限定在一个周期时间内),即 @rt_rq->rt_runtime * 发生了变化,所以需要通过 sched_rt_runtime() 重新读取。*/balance_runtime(rt_rq);runtime = sched_rt_runtime(rt_rq);...if (rt_rq->rt_time > runtime) { /* 当前 CPU 上发生了实时进程超过限定带宽的情况 */struct rt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);if (likely(rt_b->rt_runtime)) { /* 设定了实时进程带宽限制 */rt_rq->rt_throttled = 1; /* 标记当前 CPU 对实时进程限流 */printk_deferred_once("sched: RT throttling activated\n");} else {...}/* CPU 上实时进程限流处理: 将实时进程移出运行队列 */if (rt_rq_throttled(rt_rq)) {sched_rt_rq_dequeue(rt_rq);return 1; /* 返回 1,表示发生了限流 */}}return 0; /* 返回 0,表示没有限流 */
}

3.4 查看 实时进程 带宽消耗情况

每当 3.2 中启动的实时进程带宽监测定时器到期,查看一下实时进程带宽消耗情况,并做相应处理。细节如下:

/* kernel/sched/rt.c */static enum hrtimer_restart sched_rt_period_timer(struct hrtimer *timer)
{struct rt_bandwidth *rt_b =container_of(timer, struct rt_bandwidth, rt_period_timer);int idle = 0;int overrun;raw_spin_lock(&rt_b->rt_runtime_lock);for (;;) {/** 重启定时器。* 超时时间: @rt_b->rt_period,即 监测周期*/overrun = hrtimer_forward_now(timer, rt_b->rt_period);if (!overrun)break;raw_spin_unlock(&rt_b->rt_runtime_lock);idle = do_sched_rt_period_timer(rt_b, overrun);raw_spin_lock(&rt_b->rt_runtime_lock);}...raw_spin_unlock(&rt_b->rt_runtime_lock);...
}static int do_sched_rt_period_timer(struct rt_bandwidth *rt_b, int overrun)
{int i, idle = 1, throttled = 0;const struct cpumask *span;span = sched_rt_period_mask();...for_each_cpu(i, span) {int enqueue = 0;struct rt_rq *rt_rq = sched_rt_period_rt_rq(rt_b, i);struct rq *rq = rq_of_rt_rq(rt_rq);int skip;...raw_spin_lock(&rq->lock);update_rq_clock(rq);if (rt_rq->rt_time) {u64 runtime;raw_spin_lock(&rt_rq->rt_runtime_lock);if (rt_rq->rt_throttled) /* 如果 运行队列当前处于 带宽限制 状态,*/balance_runtime(rt_rq); /* 如果别的 CPU 有多的 实时进程的运行时间,从别的 CPU 借一些 */runtime = rt_rq->rt_runtime;/** 周期性定时器到期后,将 运行队列消耗总时间 减掉 周期时间: * 运行队列总时间 按 每周期 进行检查,看是否超过了允许的比例。*/rt_rq->rt_time -= min(rt_rq->rt_time, overrun*runtime);/* 如果 运行队列 处于 带宽限制 状态,且 运行队列 的 实时进程运行时间 还有余额,*/if (rt_rq->rt_throttled && rt_rq->rt_time < runtime) {/* 解除 运行队列 的 带宽限制 状态  */rt_rq->rt_throttled = 0;enqueue = 1;...}raw_spin_unlock(&rt_rq->rt_runtime_lock);} else if (rt_rq->rt_nr_running) {idle = 0;if (!rt_rq_throttled(rt_rq))enqueue = 1;}...if (enqueue) /* 解除带宽限制,重新将进程插入运行队列 */sched_rt_rq_enqueue(rt_rq);raw_spin_unlock(&rq->lock);}...return idle;
}

3.5 小结

一方面,实时调度器通过累加实时进程的消耗的 CPU 总时间;另一方面,实时调度器启动一个监测定时器,周期性地查看实时进程消耗的 CPU 时间,如果发现当前监测周期(sysctl_sched_rt_period)内,实时进程消耗的 CPU 时间超过设定的阈值(sysctl_sched_rt_runtime),则会爆出内核日志:

sched: RT throttling activated

从前面的分析得知,这个日志在多 CPU 场景下,只代表某个 CPU 的实时进程消耗超过了阈值,并非所有。笔者认为,在这个日志里面,加上 CPU 信息可能会更好。另外,出现这个日志,只代表当前的情形,一段时间后,随着系统的运行,实时进程的CPU 消耗可能会恢复到带宽控制阈值范围内。

最后,Linux 内核提供了修改实时进程带宽监测周期 sysctl_sched_rt_period,以及周期内 CPU 消耗带宽阈值 sysctl_sched_rt_runtime 的用户接口:

/proc/sys/kernel/sched_rt_period_us
/proc/sys/kernel/sched_rt_runtime_us

接口代码实现如下:

/* kernel/sysctl.c */static struct ctl_table kern_table[] = {...{.procname = "sched_rt_period_us",.data  = &sysctl_sched_rt_period,.maxlen  = sizeof(unsigned int),.mode  = 0644,.proc_handler = sched_rt_handler,},{.procname = "sched_rt_runtime_us",.data  = &sysctl_sched_rt_runtime,.maxlen  = sizeof(int),.mode  = 0644,.proc_handler = sched_rt_handler,},...
};

通过同一接口 sched_rt_handler() 修改 /proc/sys/kernel/sched_rt_period_us/proc/sys/kernel/sched_rt_runtime_us

int sched_rt_handler(struct ctl_table *table, int write,void __user *buffer, size_t *lenp,loff_t *ppos)
{int old_period, old_runtime;static DEFINE_MUTEX(mutex);int ret;mutex_lock(&mutex);old_period = sysctl_sched_rt_period;old_runtime = sysctl_sched_rt_runtime;/** write == 0: 读 sysctl_sched_rt_period 或 sysctl_sched_rt_runtime* write == 1: 写 sysctl_sched_rt_period 或 sysctl_sched_rt_runtime*/ret = proc_dointvec(table, write, buffer, lenp, ppos);/* 改写 sysctl_sched_rt_period 或 sysctl_sched_rt_runtime */if (!ret && write) {  /* 写操作 */.../* 将新写入的值应用到运行队列 */ret = sched_rt_global_constraints();if (ret)goto undo;sched_rt_do_global(); /* 更新 RT 调度器的带宽控制参数 def_rt_bandwidth */sched_dl_do_global(); /* 更新 DL 调度器的带宽控制参数 def_dl_bandwidth */}if (0) {
undo:sysctl_sched_rt_period = old_period;sysctl_sched_rt_runtime = old_runtime;}mutex_unlock(&mutex);return ret;
}

从上面的代码分析可以看到,对 /proc/sys/kernel/sched_rt_period_us/proc/sys/kernel/sched_rt_runtime_us 的修改,不仅影响 RT(Real-Time) 实时调度器,也影响 DL(DeadLine) 实时调度器。

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

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

相关文章

JAVA毕业设计165—基于Java+Springboot+vue3的二手房交易管理系统(源代码+数据库+11000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的二手房交易管理系统(源代码数据库11000字论文)165 一、系统介绍 本项目前后端分离(还有ssm版本)&#xff0c;分为用户、卖家、管理员三种角色 1、用户&a…

TOMCAT-企业级WEB应用服务器

一 WEB技术 1.1 HTTP协议和B/S 结构 HTTP&#xff08;HyperText Transfer Protocol&#xff09;协议即超文本传输协议&#xff0c;是用于在万维网&#xff08;WWW&#xff09;上传输超文本内容的基础协议。 一、HTTP 协议的特点 1、简单快速 客户向服务器请求服务时&#…

leetcode234. 回文链表(java实现)

题目描述&#xff1a; 本道题的思路可以使用集合先存储链表的值&#xff0c;然后进行判断即可。 总体思路比较简单。 代码实现&#xff1a; class Solution {public boolean isPalindrome(ListNode head) {List<Integer> res new ArrayList();ListNode cur head;whil…

Python lambda(匿名函数)

Python 使用 lambda 来创建匿名函数。 lambda 函数是一种小型、匿名的、内联函数&#xff0c;它可以具有任意数量的参数&#xff0c;但只能有一个表达式。 匿名函数不需要使用 def 关键字定义完整函数。 lambda 函数通常用于编写简单的、单行的函数&#xff0c;通常在需要函…

代谢组数据分析(十八):随机森林构建代谢组诊断模型

介绍 使用随机森林算法和LASSO特征选择构建了一种胃癌(GC)诊断预测模型。参与者(队列1,n=426)通过随机分层抽样分为发现数据集(n=284)和测试集(n=142)。接下来,在发现数据集上执行LASSO回归,以选择能够识别胃癌患者的较少数量的特征。我们将L1约束的系数设置为0.01…

OpenCV绘图函数(3)判断点一条直线是否在一个矩形范围内的函数clipLine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 裁剪线段与图像矩形相交的部分。 cv::clipLine 函数计算出完全位于指定矩形内的线段部分。如果线段完全位于矩形之外&#xff0c;则返回 false。…

2024年小红书图文制作超火爆风格,1单19.9!趣味性插画AI表情包项目思路

今天我想跟大家分享一个每天只需花20分钟就能轻松赚钱的AI表情包项目。 这个项目在小红书上非常受欢迎&#xff0c;因为它符合小红书的用户习惯&#xff0c;而且操作简单。下面我来详细讲讲如何利用软件快速创作有趣的插画&#xff01; 项目简介 这个项目的原理很简单&#x…

工厂模式和策略模式区别以及使用

1. 简介 1.1. 概述 1.1.1. 工厂模式 工厂模式的核心思想是将对象的创建过程封装起来,使得客户端(Caller)不需要直接与具体类(Concrete Class)交互,而是通过一个接口(Interface)来与它们交互。这样做的好处是,当需要更改对象的创建方式时,只需修改工厂方法即可,无需…

计算机网络-2-tcpip协议

1.说说 TCP/IP 四层模型&#xff1f; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff09;模型是一种用于描述互联网通信的协议层次结构。它分为四个主要层次&#xff0c;每个层次都定义了不同的协议来实现特定的功能。下面是TCP/IP模型各层的常用…

AES对称加密算法

1. 简介 AES是一种对称加密算法, 它有3种类型: AES-128: 密钥为128位(16字节)的AES, 加密10轮AES-192: 密钥为192位(24字节)的AES, 加密12轮AES-256: 密钥为256位(32字节)的AES, 加密14轮 密钥长度越长, 加密的强度越大, 当然与此同时开销也越大。每种类型下都有几种操作模式…

Python序列化与反序列化:pickle库使用详解

pickle是Python中一个用于对象序列化与反序列化的模块。它可以将Python对象转换成字节流&#xff0c;这样这些对象就可以容易地存储到文件中&#xff0c;或者通过网络传输。同样地&#xff0c;pickle也可以将这些字节流重新转换成原来的Python对象。 pickle库的主要功能 将Py…

给自己复盘的tjxt笔记day9

优惠券管理 开发流程 需求分析&#xff0c;接口统计&#xff0c;数据库设计&#xff0c;创建分支&#xff0c;创建新模块&#xff08;依赖&#xff0c;配置&#xff0c;启动类&#xff09;&#xff0c;生成代码&#xff0c;引入枚举状态 优惠券管理 增删改查的业务代码&#…

NC 数组中的最长连续子序列

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定无序数组…

前端算法 === 力扣 111 二叉树的最小深度

目录 问题描述 DFS&#xff08;深度优先搜索&#xff09;方案 BFS&#xff08;广度优先搜索&#xff09;方案 总结 力扣&#xff08;LeetCode&#xff09;上的题目111是关于二叉树的最小深度问题。这个问题可以通过深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&…

python 实现二维矩阵运算的函数算法

二维矩阵运算的函数算法介绍 在二维矩阵运算中&#xff0c;除了之前提到的加法和乘法之外&#xff0c;还有其他一些常见的运算&#xff0c;如矩阵的转置、求逆&#xff08;针对方阵且行列式不为零&#xff09;、点积&#xff08;也称为内积&#xff0c;但通常不直接用于整个矩…

科技温柔拥抱梦乡!康姿百德柔压磁性枕舒适与科技的甜蜜邂逅

解锁未来睡眠新姿势&#xff01;康姿百德柔压磁性枕&#xff0c;科技护航每一夜好梦 在现代家居产品的设计中&#xff0c;科技与舒适性的结合越来越受到人们的关注。康姿百德柔压磁性枕正是一款将科技与舒适结合的产品&#xff0c;为现代生活注入了新的活力。 康姿百德柔压磁性…

Jmeter下载、配置环境变量

Jmeter下载 下载地址&#xff1a;Apache JMeter - Download Apache JMeter 下载后无需安装&#xff0c;解压后即可使用。解压后目录如下 配置环境变量 JMETER_HOME 环境变量Path %JMETER_HOME%\bin 环境变量CLASSPATH %JMETER_HOME%\lib 验证是否配置成功 在cmd命令窗中 输入…

JetBrains Rider 2024 for Mac/Win:跨平台.NET IDE集成开发环境的全面解析

JetBrains Rider 2024作为一款专为Mac和Windows用户设计的跨平台.NET IDE集成开发环境&#xff0c;以其强大的功能和卓越的性能&#xff0c;在.NET开发领域脱颖而出。这款IDE不仅集成了IntelliJ IDEA的代码编辑优势&#xff0c;还融合了ReSharper的C#开发体验&#xff0c;为开发…

ES6笔记总结:第四天(ES6完结)

Xmind鸟瞰图&#xff1a; 简单文字总结&#xff1a; node的模块化&#xff1a; 1.CommonJS 规范&#xff1a;Node.js 遵循 CommonJS 模块规范&#xff0c;该规范定义了如何在服务器环境中实现模块化&#xff0c;包括如何定义模块、如何引入和使用模块。 2.模块的定义&…

LabVIEW便携涡流检测系统开发

针对便携式脉冲涡流检测系统的开发需求&#xff0c;使用LabVIEW进行软件设计与实现。系统需要集成对铁磁性和非铁磁性材料的检测功能&#xff0c;并提供友好的用户界面&#xff0c;便于操作与数据读取。 硬件选型&#xff1a; 脉冲涡流主机&#xff1a; 选择理由&#xff1a; …