Linux cond_resched()简介

文章目录

  • 简介
  • 一、cond_resched
    • 1.1 _cond_resched
    • 1.2 should_resched
      • 1.2.1 __preempt_count:
      • 1.2.2 函数说明
    • 1.3 preempt_schedule_common
      • 1.3.1 preempt_schedule_common
      • 1.3.2 preempt_latency_start/stop
    • 1.3.3 preempt_disable_notrace
  • 参考资料

简介

Linux 内核版本:4.19.90
处理器架构:x86_64
操作系统:Kylin Linux Advanced Server V10

一、cond_resched

CONFIG_PREEMPT_NONE=y
# CONFIG_PREEMPT is not set

该操作系统没有配置CONFIG_PREEMPT,配置CONFIG_PREEMPT_NONE,表示该系统在内核态是不可抢占的。

/** cond_resched() and cond_resched_lock(): latency reduction via* explicit rescheduling in places that are safe. The return* value indicates whether a reschedule was done in fact.* cond_resched_lock() will drop the spinlock before scheduling,*/
#ifndef CONFIG_PREEMPT
extern int _cond_resched(void);
#else
static inline int _cond_resched(void) { return 0; }
#endif#define cond_resched() ({			\___might_sleep(__FILE__, __LINE__, 0);	\_cond_resched();			\
})

cond_resched() 的目的是显式请求当前任务进行调度。它在可以安全地主动让出处理器给其他任务的位置使用。函数 _cond_resched() 在 cond_resched() 内部调用,以执行实际的调度操作。

由于配置了内核态是不可抢占的,在内核态运行的程序可调用cond_resched主动让出cpu,是为了在不可抢占内核的一些耗时的内核处理路径中增加主动抢占点,防止其在内核态执行时间过长导致可能发生的soft lockup或者造成较大的调度延迟。

1.1 _cond_resched

#ifndef CONFIG_PREEMPT
int __sched _cond_resched(void)
{if (should_resched(0)) {preempt_schedule_common();return 1;}rcu_all_qs();return 0;
}
EXPORT_SYMBOL(_cond_resched);
#endif

数 _cond_resched() 是一个具有 int 返回类型的函数,其参数列表为空。它的作用是进行条件调度,即根据一定的条件决定是否进行调度操作。

函数内部首先调用 should_resched(0) 来检查是否应该进行调度。should_resched() 是一个用于检查是否应该进行调度的函数,它的参数是一个整数(在此处传入了0)。如果 should_resched() 返回真值(非零),则表示应该进行调度。

如果应该进行调度,函数 _cond_resched() 调用 preempt_schedule_common() 来执行实际的调度操作。preempt_schedule_common() 是一个用于进行抢占式调度的函数,它会切换到其他可运行的任务。

如果不需要进行调度,函数 _cond_resched() 调用 rcu_all_qs() 函数。rcu_all_qs() 是一个用于处理 RCU(Read-Copy-Update)的函数,它用于等待所有的 RCU 队列完成。

最后,根据是否进行了调度操作,函数返回相应的值。如果进行了调度,返回值为1,否则返回0。

1.2 should_resched

// linux-4.19.90/arch/x86/include/asm/preempt.hDECLARE_PER_CPU(int, __preempt_count);/** Returns true when we need to resched and can (barring IRQ state).*/
static __always_inline bool should_resched(int preempt_offset)
{return unlikely(raw_cpu_read_4(__preempt_count) == preempt_offset);
}

1.2.1 __preempt_count:

在内核中只要没有持有锁,就可以就行内核抢占,锁是内核态是否抢占的标志,因此引入了一个preempt_count值,preempt_count初始值为0,每当使用锁时,该值就加1,释放锁时,该值就减1。preempt_count等于0代表内核可抢占。

因此对于内核态抢占来说,除了要判断是否设置了_TIF_NEED_RESCHED标志位,还需要判断preempt_count值是否等于0,内核中使用preempt_count来控制内核抢占。只有设置了_TIF_NEED_RESCHED标志位和preempt_count值等于0才能发起内核态抢占。

1.2.2 函数说明

该函数的目的是检查是否需要进行调度,并且在没有 IRQ(中断)发生的情况下可以进行调度。

函数内部使用 raw_cpu_read_4(__preempt_count) 来读取一个名为 __preempt_count 的全局变量的值。__preempt_count 是一个表示抢占计数的变量,用于跟踪当前任务的抢占状态。

unlikely() 宏用于提示编译器这个条件的结果通常是不成立的(即返回值为假)。这有助于编译器进行优化,以提高代码的执行效率。

函数比较 raw_cpu_read_4(__preempt_count) 的返回值与 preempt_offset(在这里也就是0,也就是判断__preempt_count的值是否为0) 参数是否相等。如果这两个值相等,意味着当前任务的抢占计数等于0,即需要进行调度。

如果相等,函数返回真值(非零),表示需要进行调度。否则,返回假值(零),表示不需要进行调度或者 IRQ 发生导致无法进行调度。

这个函数使用了 __always_inline 修饰符,表示建议编译器对该函数进行内联展开,以减少函数调用的开销。

在上面_cond_resched函数中调用should_resched函数时传入的参数是0,如果__preempt_count等于0,那么就发生调度(抢占式调度)。

1.3 preempt_schedule_common

static void __sched notrace preempt_schedule_common(void)
{do {/** Because the function tracer can trace preempt_count_sub()* and it also uses preempt_enable/disable_notrace(), if* NEED_RESCHED is set, the preempt_enable_notrace() called* by the function tracer will call this function again and* cause infinite recursion.** Preemption must be disabled here before the function* tracer can trace. Break up preempt_disable() into two* calls. One to disable preemption without fear of being* traced. The other to still record the preemption latency,* which can also be traced by the function tracer.*/preempt_disable_notrace();preempt_latency_start(1);__schedule(true);preempt_latency_stop(1);preempt_enable_no_resched_notrace();/** Check again in case we missed a preemption opportunity* between schedule and now.*/} while (need_resched());
}

1.3.1 preempt_schedule_common

__schedule的参数preempt等于1表示是抢占调度,有处于运行态的任务发起的抢占调度。

preempt_schedule_common表示是抢占式调度。

函数内部使用一个循环,不断执行以下步骤:
(1)调用 preempt_disable_notrace(),禁用抢占,但不允许函数跟踪器(function tracer)对其进行跟踪。这是为了避免函数跟踪器在调用 preempt_enable_notrace() 时再次调用该函数,导致无限递归。
(2)调用 preempt_latency_start(1),开始记录抢占延迟时间,这也可以被函数跟踪器跟踪。
(3)调用 __schedule(true),进行实际的调度操作,切换到其他可运行的任务。
(4)调用 preempt_latency_stop(1),停止记录抢占延迟时间。
(5)调用 preempt_enable_no_resched_notrace(),启用抢占,但不允许函数跟踪器进行跟踪,并且不触发重新调度。

最后,在循环的末尾,通过调用 need_resched() 检查是否需要进行重新调度。如果需要重新调度,循环会继续执行上述步骤,直到不再需要重新调度为止。

这个函数的目的是在禁用抢占、记录抢占延迟、执行调度操作以及启用抢占的过程中,确保函数跟踪器的正常工作,并且避免出现无限递归的情况。同时,通过循环检查是否需要重新调度,确保不会错过可能的抢占机会。

1.3.2 preempt_latency_start/stop

preempt_latency_start() 和 preempt_latency_stop() 是用于记录抢占延迟时间的函数:

在多任务操作系统中,任务之间的切换是通过抢占机制实现的。当一个任务被抢占时,需要记录抢占的延迟时间,即从抢占开始到抢占结束的时间间隔。这个延迟时间对于性能分析和系统调优非常重要。

preempt_latency_start() 函数用于开始记录抢占延迟时间。它可能会调用系统计时器或其他相关机制来获取当前时间戳,并将其保存在某个数据结构中。通常,它会接收一个参数,用于标识抢占的类型或事件。

preempt_latency_stop() 函数用于停止记录抢占延迟时间。它也可能会使用系统计时器或其他机制获取当前时间戳,并将其与开始记录时的时间戳进行比较,以计算抢占的实际延迟时间。通常,它也会接收一个参数,用于标识抢占的类型或事件。

通过调用这两个函数,系统可以在抢占开始和结束时记录相关的时间戳,并计算抢占的延迟时间。这些延迟时间可以用于性能分析和诊断,以确定是否存在延迟问题,找出潜在的性能瓶颈,并进行系统调优。

1.3.3 preempt_disable_notrace

当需要禁用抢占且不被函数跟踪器追踪时,使用preempt_disable_notrace()函数。它用于禁用抢占,即当前任务不会被其他任务抢占。这个函数通常在代码的关键部分使用,确保当前任务能够无干扰地执行。

当需要重新启用抢占但不触发立即重新调度时,使用preempt_enable_no_resched_notrace()函数。它用于在禁用抢占后重新启用抢占。函数名中的no_resched表示不会立即重新调度。这意味着即使有更高优先级的任务可用,调度器也不会立即切换到其他任务。这在某些情况下非常有用,可以更加精确地控制调度行为。

总结一下,preempt_disable_notrace()用于禁用抢占且不被函数跟踪器追踪,确保当前任务不会被抢占。而preempt_enable_no_resched_notrace()用于重新启用抢占但不触发立即重新调度,从而更加精确地控制调度行为。这两个函数都用于关键代码区域,需要仔细管理抢占,同时避免受到函数跟踪器的干扰。

#define preempt_disable_notrace()		barrier()
#define preempt_enable_no_resched_notrace()	barrier()

barrier() 是一个预处理宏,用于插入一个内存屏障(memory barrier)。内存屏障是一种同步机制,用于确保在屏障之前和之后的操作按照特定的顺序执行,防止编译器或处理器对指令进行重排序。

在这种情况下,preempt_disable_notrace() 和 preempt_enable_no_resched_notrace() 宏被定义为 barrier(),是为了在禁用抢占和重新启用抢占之间插入内存屏障,以确保相关操作的顺序性。这可能是为了保证在禁用抢占和重新启用抢占之间的任何指令都不会被编译器或处理器进行重排序,从而确保这些操作的原子性和可预测性。

关于内存屏障用途(这段话的来自https://blog.csdn.net/orangeboyye 这个博主):
内存屏障有两种用途,一是设备内存,设备内存需要顺序执行,不能重排序。
二是多CPU,CPU乱序执行能保证单核逻辑的正确性,但是它不可能替你考虑多核逻辑的正确性,如果你的代码里有多核逻辑(多线程在多核并行运行),就需要考虑乱序执行带来的影响,就需要内存屏障了。为什么大部分情况你都没用过内存屏障,因为一般情况的多核逻辑你都会用锁,锁里面用的有内存屏障。

参考资料

Linux 4.19.90

Linux 调度器之抢占式调度
cond_resched的使用
深入理解Linux内核之主调度器(上)

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

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

相关文章

【mysql】—— 表的约束

目录 序言 (一)空属性 (二)默认值 (三)列描述 (四)zerofill (五)主键 (六)自增长 (七)唯一键 &#…

Angular安全专辑 —— CSP防止XSS攻击

什么是 CSP(Content Security Policy) CSP(Content Security Policy)是一种Web安全策略,用于减轻和防止跨站脚本攻击(XSS)等安全漏洞。它通过允许网站管理员定义哪些资源可以加载到网页中&#…

在序列化、反序列化下如何保持单例(Singleton)模式

1、序列化、反序列化 在 Java 中,当一个对象被序列化后再被反序列化,通常情况下会创建一个新的对象实例。这是因为序列化将对象的状态保存到字节流中,而反序列化则是将字节流重新转化为对象。在这个过程中,通常会使用类的构造函数…

Node.js:path文件路径操作模块

path 用于文件路径操作 官方文档 https://nodejs.org/api/path.html 一个不错的解释 ┌─────────────────────┬────────────┐│ dir │ base │├──────┬ ├──────┬─────┤│ ro…

vue强制刷新变量

在前端开发中,我们经常需要变量的值实时响应到界面上。Vue就是一个非常强大的前端框架,它的数据绑定能够非常好地实现变量与界面的同步更新。但是有时候,我们需要强制刷新某个变量的值,以便界面能及时地反映出它的变化。本文将介绍…

面试热题(LRU缓存)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 …

如何在页面中嵌入音频和视频?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 嵌入音频⭐ 嵌入视频⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚踏…

自监督学习的概念

Self-Supervised Learning (SSL)的主要思想是解决先验任务来学习特征提取器,在不使用标签的情况下生成有用的表示。 这里先验任务是指, 先使用原始数据和特征提取器来提取出 数据的有效表示. 对比方法(即对比学习, Contrastiv…

SpringSecurity 详解(通俗易懂)

SpringSecurity 详解 1、SpringSecurity讲解1.1、SpringSecurity完整流程1.2、认证流程 2、登录,退出,注册_分析说明2.1、登录2.2、校验2.3、退出2.4、注册2.5、SecurityContextHolder说明 3、代码实现3.1、引入依赖3.2、登录 退出 注册3.2.1、SpringSec…

k8部署安装

1 环境初始化 1.1 检查操作系统的版本 此方式下安装kubernetes集群要求Centos版本要在7.5或之上 [rootmaster ~]# cat /etc/redhat-release Centos Linux 7.5.1804 (Core)1.2主机名成解析 三台服务器的/etc/hosts文件 192.168.90.100 master 192.168.90.106 node1 192.168.…

18 | 基于DDD的微服务设计实例

为了更好地理解 DDD 的设计流程,这篇文章会用一个项目来带你了解 DDD 的战略设计和战术设计,走一遍从领域建模到微服务设计的全过程,一起掌握 DDD 的主要设计流程和关键点。 项目基本信息 项目的目标是实现在线请假和考勤管理。功能描述如下…

论文阅读---《Unsupervised ECG Analysis: A Review》

题目 无监督心电图分析一综述 摘要 电心图(ECG)是检测异常心脏状况的黄金标准技术。自动检测心电图异常有助于临床医生分析心脏监护仪每天产生的大量数据。由于用于训练监督式机器学习模型的带有心脏病专家标签的异常心电图样本数量有限,对…

K8S调度

K8S调度 一、List-Watch 机制 controller-manager、scheduler、kubelet 通过 List-Watch 机制监听 apiserver 发出的事件,apiserver 通过 List-Watch 机制监听 etcd 发出的事件1.scheduler 的调度策略 预选策略/预算策略:通过调度算法过滤掉不满足条件…

Ceph集群安装部署

Ceph集群安装部署 目录 Ceph集群安装部署 1、环境准备 1.1 环境简介1.2 配置hosts解析(所有节点)1.3 配置时间同步2、安装docker(所有节点)3、配置镜像 3.1 下载ceph镜像(所有节点执行)3.2 搭建制作本地仓库(ceph-01节点执行)3.3 配置私有仓库(所有节点执行)3.4 为 Docker 镜像…

opencv实战项目 手势识别-手势控制鼠标

手势识别系列文章目录 手势识别是一种人机交互技术,通过识别人的手势动作,从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪(定位手部关键点) 2.opencv实战项目 实现手势跟踪并返回位置信息&…

register_chrdev函数简单使用

register_chrdev函数网上介绍的文章比较多&#xff0c;就不概述了。 以下是例子。 #include<linux/module.h> #include<linux/kernel.h> #include<linux/init.h> #include<linux/fs.h> #include<linux/slab.h> #include<linux/timer.h> …

AAAI论文阅读

文章目录 Open-Vocabulary Multi-Label Classifcation via Multi-Modal Knowledge Transfer——知识蒸馏的范畴Med-EASi: Finely Annotated Dataset and Models for Controllable Simplifcation of Medical Texts——医学领域数据集构建“Nothing Abnormal”: Disambiguating M…

UE 5 GAS 在项目中处理AttributeSet相关

这一篇文章是个人的实战经验记录&#xff0c;如果对基础性的内容不了解的&#xff0c;可以看我前面一篇文章对基础的概念以及内容的讲解。 设置AttributeSet 使用GAS之前&#xff0c;首先需要设置参数集AS&#xff0c;这个是用于同步的一些参数&#xff0c;至于如何设置GAS&a…

Mysql之安装-字符集设置-用户及权限操作-sqlmode设置

1、概述 MySQL支持大型数据库&#xff0c;支持5000万条记录的数据仓库&#xff0c;32位系统表文件最大可支持4GB&#xff0c;64位系统支持最大的表文件为8TB。使用标准的SQL数据语言形式。 2、Linux的mysql安装 &#xff08;1&#xff09;检查是否已安装&#xff1a;rpm -qa…

梯度下降求极值,机器学习深度学习

目录 梯度下降求极值 导数 偏导数 梯度下降 机器学习&深度学习 学习形式分类