Linux 实时调度器:带宽限制

devtools/2024/11/13 8:16:30/

文章目录

  • 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.ppmy.cn/devtools/102264.html

相关文章

配电房挂轨机器人巡检系统的主要优点包括

背景 配电房是724h工作的封闭环境&#xff0c;人工巡检无法在时间上和空间上对配电室进行全量监控。有限的巡检时间&#xff0c;必然带来设备运转的黑盒时间&#xff0c;设备故障和隐患无法及时监控与消缺。因而不可避免存在漏检、误检的情况&#xff0c;不仅容易隐藏电力系统…

探索深度学习的强大工具:PyTorch的torch.utils.data模块

探索深度学习的强大工具&#xff1a;PyTorch的torch.utils.data模块 在深度学习的世界里&#xff0c;数据是模型训练的基石。PyTorch&#xff0c;作为当前最流行的深度学习框架之一&#xff0c;提供了一个强大的torch.utils.data模块&#xff0c;它使得数据加载、处理和迭代变…

Nginx+ModSecurity(3.0.x)安装教程及配置WAF规则文件

本文主要介绍ModSecurity v3.0.x在Nginx环境下的安装、WAF规则文件配置、以及防御效果的验证&#xff0c;因此对于Nginx仅进行简单化安装。 服务器操作系统&#xff1a;linux 位最小化安装 一、安装相关依赖工具 Bash yum install -y git wget epel-release yum install -y g…

【MATLAB学习笔记】绘图——分割绘图背景并填充不同的颜色

目录 前言分割背景函数示例基本绘图分割背景函数的使用保存图片 总代码总结 前言 在MATLAB中&#xff0c;使用窗口对象的Color属性可以轻松地设置不同的背景颜色&#xff0c;但是只能设置一种单一颜色。若需要将绘图背景设置成多种颜色&#xff0c;比如左右两边不同的颜色&…

使用vueuse在组件内复用模板

1. 安装vueusae pnpm i vueuse/core2. 组件内复用模板 createReusableTemplate 是vueuse中的一个实用工具&#xff0c;用于在 Vue 3 中创建可重复使用的模板片段&#xff0c;同时保持状态的独立性。这对于需要在多个组件中重复使用相同的结构和逻辑时非常有用。 因为这些可复…

无人机及固定机巢自动化控制软件技术详解

随着科技的飞速发展&#xff0c;无人机技术已成为众多行业中不可或缺的一部分&#xff0c;特别是在航拍、环境监测、农业植保、应急救援等领域展现出巨大潜力。无人机及固定机巢自动化控制软件作为支撑无人机高效、安全、自主运行的核心&#xff0c;集成了先进的系统架构、飞行…

React 入门第四天:理解React中的路由与导航

在React学习的第四天&#xff0c;我将目光聚焦在React Router上。路由是任何单页应用&#xff08;SPA&#xff09;的核心部分&#xff0c;决定了用户如何在应用中导航&#xff0c;以及不同URL对应的内容如何渲染。通过学习React Router&#xff0c;我体会到了React处理路由的强…

打手机检测算法源码样本展示打手机检测算法实际应用场景介绍

打手机检测算法是一种利用计算机视觉技术来监测和识别人们在特定区域如驾驶舱、考场或其他敏感区域非法使用手机的行为。这种算法对于提高安全性和确保规则的遵守具有重要意义。以下是关于打手机检测算法源码及其实际应用的详细阐述&#xff1a; 1. 算法实现 - 深度学习框架&a…