Linux进程调度-组调度及带宽控制
1. 概述
组调度(task_group)是使用Linux cgroup(control group)的cpu子系统来实现的,可以将进程进行分组,按组来分配CPU资源等。
比如,看一个实际的例子:
A和B两个用户使用同一台机器,A用户16个进程,B用户2个进程,如果按照进程的个数来分配CPU资源,显然A用户会占据大量的CPU时间,这对于B用户是不公平的。组调度就可以解决这个问题,分别将A、B用户进程划分成组,并将两组的权重设置成占比50%即可。
带宽(bandwidth)控制,是用于控制用户组(task_group)的CPU带宽,通过设置每个用户组的限额值,可以调整CPU的调度分配。在给定周期内,当用户组消耗CPU的时间超过了限额值,该用户组内的任务将会受到限制。
由于组调度和带宽控制紧密联系,因此本文将探讨这两个主题,本文的讨论都基于CFS调度器,开始吧。
2. task_group
组调度,在内核中是通过struct task_group来组织的,task_group本身支持cfs组调度和rt组调度,本文主要分析cfs组调度。
CFS调度器管理的是sched_entity调度实体,task_struct(代表进程)和task_group(代表进程组)中分别包含sched_entity,进而来参与调度;
关于组调度的相关数据结构,组织如下:

内核维护了一个全局链表task_groups,创建的task_group会添加到这个链表中;
内核定义了root_task_group全局结构,充当task_group的根节点,以它为根构建树状结构;
struct task_group的子节点,会加入到父节点的siblings链表中;
每个struct task_group会分配运行队列数组和调度实体数组(以CFS为例,RT调度类似),其中数组的个数为系统CPU的个数,也就是为每个CPU都分配了运行队列和调度实体;
对应到实际的运行中,如下:

struct cfs_rq包含了红黑树结构,sched_entity调度实体参与调度时,都会挂入到红黑树中,task_struct和task_group都属于被调度对象;
task_group会为每个CPU再维护一个cfs_rq,这个cfs_rq用于组织挂在这个任务组上的任务以及子任务组,参考图中的Group A;
调度器在调度的时候,比如调用pick_next_task_fair时,会从遍历队列,选择sched_entity,如果发现sched_entity对应的是task_group,则会继续往下选择;
由于sched_entity结构中存在parent指针,指向它的父结构,因此,系统的运行也能从下而上的进行遍历操作,通常使用函数walk_tg_tree_from进行遍历;
2.2 task_group权重
进程或进程组都有权重的概念,调度器会根据权重来分配CPU的时间。
进程组的权重设置,可以通过/sys文件系统进行设置,比如操作
/sys/fs/cgoup/cpu/A/shares;
调用流程如下图:

sched_group_set_shares来完成最终的设置;
task_group为每个CPU都分配了一个sched_entity,针对当前sched_entity设置更新完后,往上对sched_entity->parent设置更新,直到根节点;
shares的值计算与load相关,因此也需要调用update_load_avg进行更新计算;
看一下实际的效果图吧:

写节点操作可以通过echo XXX > /sys/fs/cgroup/cpu/A/B/cpu.shares;
橙色的线代表传入参数指向的对象;
紫色的线代表每次更新涉及到的对象,包括三个部分;
处理完sched_entity后,继续按同样的流程处理sched_entity->parent
3. cfs_bandwidth
先看一下/sys/fs/cgroup/cpu下的内容吧:

有两个关键的字段:cfs_period_us和cfs_quota_us,这两个与cfs_bandwidth息息相关;
period表示周期,quota表示限额,也就是在period期间内,用户组的CPU限额为quota值,当超过这个值的时候,用户组将会被限制运行(throttle),等到下一个周期开始被解除限制(unthrottle);
来一张图直观理解一下:

- 在每个周期内限制在quota的配额下,超过了就throttle,下一个周期重新开始;
3.1 数据结构
内核中使用struct cfs_bandwidth来描述带宽,该结构包含在struct task_group中。
此外,struct cfs_rq中也有与带宽控制相关的字段。
还是来看一下代码吧:
struct cfs_bandwidth {
#ifdef CONFIG_CFS_BANDWIDTH
raw_spinlock_t lock;
ktime_t period;
u64 quota, runtime;
s64 hierarchical_quota;
u64 runtime_expires;
int idle, period_active;
struct hrtimer period_timer, slack_timer;
struct list_head throttled_cfs_rq;
/* statistics */
int nr_periods, nr_throttled;
u64 throttled_time;
#endif
};
period:周期值;
quota:限额值;
runtime:记录限额剩余时间,会使用quota值来周期性赋值;
hierarchical_quota:层级管理任务组的限额比率;
runtime_expires:每个周期的到期时间;
idle:空闲状态,不需要运行时分配;
period_active:周期性计时已经启动;
period_timer:高精度周期性定时器,用于重新填充运行时间消耗;
slack_timer:延迟定时器,在任务出列时,将剩余的运行时间返回到全局池里;
throttled_cfs_rq:限流运行队列列表;
nr_periods/nr_throttled/throttled_time:统计值;
struct cfs_rq结构中相关字段如下:
struct cfs_rq {
...
#ifdef CONFIG_CFS_BANDWIDTH
int runtime_enabled;
u64 runtime_expires;
s64 runtime_remaining;
u64 throttled_clock, throttled_clock_task;
u64 throttled_clock_task_time;
int throttled, throttle_count;
struct list_head throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
...
}
runtime_enabled:周期计时器使能;
runtime_expires:周期计时器到期时间;
runtime_remaining:剩余的运行时间;
3.2 流程分析
3.2.1 初始化流程
先看一下初始化的操作,初始化函数init_cfs_bandwidth本身比较简单,完成的工作就是将struct cfs_bandwidth结构体进程初始化。

注册两个高精度定时器:period_timer和slack_timer;
period_timer定时器,用于在时间到期时重新填充关联的任务组的限额,并在适当的时候unthrottlecfs运行队列;
slack_timer定时器,slack_period周期默认为5ms,在该定时器函数中也会调用distribute_cfs_runtime从全局运行时间中分配runtime;
start_cfs_bandwidth和start_cfs_slack_bandwidth分别用于启动定时器运行,其中可以看出在dequeue_entity的时候会去利用slack_timer,将运行队列的剩余时间返回给tg->cfs_b这个runtime pool;
unthrottle_cfs_rq函数,会将throttled_list中的对应cfs_rq删除,并且从下往上遍历任务组,针对每个任务组调用tg_unthrottle_up处理,最后也会根据cfs_rq对应的sched_entity从下往上遍历处理,如果sched_entity不在运行队列上,那就重新enqueue_entity以便参与调度运行,这个也就完成了解除限制的操作;
do_sched_cfs_period_timer函数与do_sched_cfs_slack_timer()函数都调用了distrbute_cfs_runtime(),该函数用于分发tg->cfs_b的全局运行时间runtime,用于在该task_group中平衡各个CPU上的cfs_rq的运行时间runtime,来一张示意图:

系统中两个CPU,因此task_group针对每个cpu都维护了一个cfs_rq,这些cfs_rq来共享该task_group的限额运行时间;
CPU0上的运行时间,浅黄色模块表示超额了,那么在下一个周期的定时器点上会进行弥补处理;
3.2.2 用户设置流程
用户可以通过操作/sys中的节点来进行设置:

操作/sys/fs/cgroup/cpu/下的cfs_quota_us/cfs_period_us节点,最终会调用到tg_set_cfs_bandwidth函数tg_set_cfs_bandwidth会从root_task_group根节点开始,遍历组调度树,并逐个设置限额比率 ;
更新cfs_bandwidth的runtime信息;
如果使能了cfs_bandwidth功能,则启动带宽定时器;
遍历task_group中的每个cfs_rq队列,设置runtime_remaining值,如果cfs_rq队列限流了,则需要进行解除限流操作;
3.2.3 throttle限流操作
cfs_rq运行队列被限制,是在throttle_cfs_rq函数中实现的,其中调用关系如下图:

调度实体sched_entity入列时,进行检测是否运行时间已经达到限额,达到则进行限制处理;
pick_next_task_fair/put_prev_task_fair在选择任务调度时,也需要进行检测判断;
3.2.4 总结
总体来说,带宽控制的原理就是通过task_group中的cfs_bandwidth来管理一个全局的时间池,分配给属于这个任务组的运行队列,当超过限额的时候则限制队列的调度。同时,cfs_bandwidth维护两个定时器,一个用于周期性的填充限额并进行时间分发处理,一个用于将未用完的时间再返回到时间池中,大抵如此。
Linux进程调度-组调度及带宽控制的更多相关文章
- 【原创】(四)Linux进程调度-组调度及带宽控制
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【原创】(六)Linux进程调度-实时调度器
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux进程组调度机制分析【转】
转自:http://oenhan.com/task-group-sched 又碰到一个神奇的进程调度问题,在系统重启过程中,发现系统挂住了,过了30s后才重新复位,真正系统复位的原因是硬件看门狗重启的 ...
- 【原创】(五)Linux进程调度-CFS调度器
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【转】【进程管理】Linux进程调度:调度时机
转自:https://zhuanlan.zhihu.com/p/163728119 概述: 进程切换分为自愿(voluntary)和强制(involuntary)两种.通常自愿切换是指任务由于等待某种 ...
- Linux进程调度器的设计--Linux进程的管理与调度(十七)
1 前景回顾 1.1 进程调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为 ...
- Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1 背景知识 1.1 什么是调度器 ...
- 深入解读Linux进程调度Schedule【转】
转自:https://blog.csdn.net/Vince_/article/details/88982802 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文 ...
- linux进程调度之 FIFO 和 RR 调度策略
转载 http://blog.chinaunix.net/uid-24774106-id-3379478.html linux进程调度之 FIFO 和 RR 调度策略 2012-10-19 18 ...
- Linux进程调度原理
Linux进程调度原理 Linux进程调度机制 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交互性能: ...
随机推荐
- mybatisplus关于驼峰命名法与下划线的映射
今天遇到一个很坑的事情,我在测试之前的案例的时候我有一个字段的名字是typeId,我调试之后发现插入出现了错误. 开启sql日志之后我发现mybatisplus自动把我的typeId改成type_id ...
- Java--普通方法重载
[转载自本科老师上课课件] 调用一个重载过的方法时,Java编译程序是如何确定究竟应该调用哪一个方法?以下代码定义了三个重载方法: public void f(char ch){ System.out ...
- 【Java】比较业务实体信息变化的工具类
一.业务需求 需要将业务表每次更新操作的前后记录进行保存,写入更新历史表中 方便用户查阅该业务记录发生的历史变化 二.代码实现 import lombok.AllArgsConstructor; im ...
- 【Windows】固定Win系统的IP地址
是我迟钝了,突然想到这个事情就记一下 先开终端查看IP信息 : IPCONFIG 找到当前连接: IPv4协议设置: 家庭网络设置就这样,公司内网有自己的一个DNS服务地址,这个网管知道 在Win11 ...
- 路径规划综述博客:A* Optimizations and Improvements
地址: https://lucho1.github.io/JumpPointSearch/ 原作者还开发了A* 算法的Windows系统上的小程序:(重点:小程序意义不大,这个综述还是不赖的) 项目地 ...
- AI大模型 —— 国产大模型 —— 华为大模型
有这么一句话,那就是AI大模型分两种,一种是大模型:另一种是华为大模型. 如果从技术角度来分析,华为的技术不论是在软件还是硬件都比国外的大公司差距极大,甚至有些技术评论者认为华为的软硬件技术至少落后2 ...
- docker 常用工具
windows 下常常需要linux环境 直接安装虚拟机不方便也浪费资源 所以直接在docker下安装一个centos 然后搭建好开发环境就是个不错的办法 一.Linux 环境 1.安装centos ...
- POI1999 Store-keeper 题解
前言 题目链接:洛谷:SPOJ:hydro & bzoj. \(\Theta(nm)\) 的算法. 题意简述 在一个划分为 \(n \times m\) 个区域的二维仓库中,称有公共边的两个区 ...
- 痞子衡嵌入式:探析i.MXRT1050在GPIO上增加RC延时电路后导致边沿中断误触发问题(上篇)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT1050在GPIO上增加RC延时电路后导致边沿中断误触发问题探析. 前段时间有一个 RT1052 客户反馈了一个有趣的问题, ...
- 推荐一款Python开源移动应用安全测试分析工具!!!
今天给大家推荐一个安全测试相关的开源项目:nccgroup/house 1.介绍 它是一个由 NCC Group 开发的,一个基于Frida和Python编写的动态运行时移动应用分析工具包,提供了基于 ...