[linux][调度] 内核抢占入门 —— 线程调度次数与 CONFIG_PREEMPTION

在工作中,如果你正在做开发的工作,正在在写代码,这个时候测试同事在测试过程中测出了问题,需要你来定位解决,那么你就应该先暂停写代码的工作,转而来定位解决测试的问题;如果你正在定位测试的问题,这个时候线上系统出现了问题,你就需要先将测试的问题暂停,转而去定位线上的问题。这就是抢占,线上问题优先级比测试问题优先级高,所以线上问题可以抢占测试问题;测试问题比开发工作优先级高,所以测试问题可以抢占开发工作。

在非抢占式内核中,内核线程是不能被抢占的,只有线程主动调用 schedule(),或者显式睡眠以及发生阻塞时发生调度,否则内核其它线程是不能抢占这个线程的。

在抢占式内核中,即使一个线程在运行,没有主动调度 schedule() 或者睡眠以及阻塞,当一个更高优先级的线程被唤醒之后,也可以抢占当前这个线程。

1 线程调度次数

linux 中,每个进程在 /proc 文件夹下都有一个进程对应的文件夹,文件夹以进程 id(pid) 命名,如下图所示。每个进程的文件夹下包括这个进程的很多信息,其中 status 文件中保存着这个进程的基础信息,比如 pid, ppid,进程使用了多少内存,进程的调度次数。

如下截图,是进程 18414 的 status 文件的显示,使用 switch 进行了过滤。其中 voluntary_ctxt_switchs 是自愿调度,nonvoluntary_ctxt_switches 是非自愿调度。

自愿调度

① 调用 sleep() 的时候

② 读写文件或者网络收发包时阻塞

③ 使用互斥体加锁时,如果不能立即得到锁,那么线程会睡眠,属于自愿调度

非自愿调度非自愿调度,意思是当线程还在运行,没有主动触发调度。比如,对于普通调度策略来说,时间片用完时,可以被抢占,这样就会统计一次非自愿调度。

自愿调度次数和非自愿调度次数,在进程控制块 struct task_struct 中用两个成员属性来表示,分别是 nvcsw 和 nivcsw。

struct task_struct {.../* Context switch counts: */unsigned long			nvcsw;unsigned long			nivcsw;...
};

在调度函数 __schedule() 中对自愿调度统计和非自愿调度统计进行递增。如果不是抢占调度,并且进程的状态不是 TASK_RUNNING 的话,就是自愿调度;否则,为非自愿调度。

怎么上一个线程不是 TASK_RUNNING 呢,其实在切换的时候,线程还是处于运行状态的,只不过在调用 schedule() 之前,线程会将自己设置为其它状态。比如在使用 mutex_lock() 加锁的时候,会先将自己设置为 TASK_UNINTERRUPTIBLE 状态,然后再调用 schedule() 进行等待。

static void __sched notrace __schedule(bool preempt)
{struct task_struct *prev, *next;unsigned long *switch_count;switch_count = &prev->nivcsw;if (!preempt && prev_state) {...switch_count = &prev->nvcsw;}if (likely(prev != next)) {...++*switch_count;} else {rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);rq_unlock_irq(rq, &rf);}
}

1.1 用户线程,自愿调度

如下代码,主线程中是一个死循环,每次循环 sleep 1s,每次 sleep 的时候会增加自愿调度计数。这是线程主动睡眠的,而不是时间片用完了被动调度走的,所以是自愿调度。

#include <iostream>
#include <string>
#include <thread>
#include <unistd.h>int main() {while (1) {sleep(1);}return 0;
}

程序运行之后,查看调度次数统计,可以看到自愿调度次数一直在增长,线程没有发生过非自愿调度。

1.2 用户线程,非自愿调度

如下代码,是一个单纯的死循环,在循环中什么都没做。程序运行之后,因为会一直占用 cpu,所以当线程的时间片用完时,线程就会被调度,这种情况下的调度被统计为非自愿调度。

#include <iostream>
#include <string>
#include <thread>
#include <unistd.h>int main() {while (1) {}return 0;
}

程序运行之后,查看调度统计,可以看到非自愿调度计数一直在增长,自愿调度计数是 0。

1.3 非抢占内核,内核线程不会被抢占

如果内核是非抢占内核,那么内核线程在运行的时候就不会被抢占,即使线程一直占用着 cpu,物理时间片和虚拟时间片一直在增长,也不会被抢占。

如下是一个内核模块,在内核模块中使用 kthread_run() 创建了一个内核线程,线程中是一个死循环。在线程中打印了线程的 id。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/sched.h>static struct task_struct *my_thread;// 内核线程函数
static int my_thread_func(void *data)
{// 内核线程的逻辑处理代码printk(KERN_INFO "My kernel thread is running, pid = %d\n", current->pid);while (1);return 0;
}// 模块初始化函数
static int __init my_module_init(void)
{// 创建内核线程my_thread = kthread_run(my_thread_func, NULL, "my_thread");if (IS_ERR(my_thread)) {printk(KERN_ERR "Failed to create kernel thread!\n");return PTR_ERR(my_thread);}printk(KERN_INFO "Module loaded!\n");return 0;
}// 模块清理函数
static void __exit my_module_exit(void)
{// 停止内核线程kthread_stop(my_thread);printk(KERN_INFO "Module unloaded!\n");
}MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample kernel module with a kernel thread");module_init(my_module_init);
module_exit(my_module_exit);

编译脚本:

obj-m += hello.o
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

线程一致在死循环,没有看到非自愿调度次数增长。

1.4 用户抢占和内核抢占

用户抢占,是指用户态的线程被抢占;内核抢占,是指内核态的线程被抢占。

linux 系统,默认情况下是支持用户抢占的。而是否支持内核抢占,需要看具体的内核配置,在一些嵌入式系统或者桌面系统,对实时性要求高,会打开内核抢占;而在服务器系统中,一般不会打开内核抢占。打开内核抢占的系统,使用 uname -a 可以看到 PREEMPT 标志,没有 PREEMPT 标志,说明没有打开内核抢占。如下是我笔记本上安装的 ubuntu 系统,没有打开内核抢占。

2 CONFIG_PREEMPTION 宏定义了什么内容

当打开内核抢占时,也就是定义了 CONFIG_PREEMPTION 这个宏。那么打开这个宏的时候,具体定义了那些内容呢 ?本人使用的源码版本是 5.10.186。

2.1 中断返回时

中断返回的时候,如果需要抢占调度,那么会调用函数 preempt_schedule_irq()。这段代码一般是使用汇编指令来实现的。如下是 arm 中的实现,下边这段代码,只有定义了 CONFIG_PREEMPTION 时,才会生效。

arch/arm/kernel/entry-armv.S

#ifdef CONFIG_PREEMPTION
svc_preempt:mov	r8, lr
1:	bl	preempt_schedule_irq		@ irq en/disable is done insideldr	r0, [tsk, #TI_FLAGS]		@ get new tasks TI_FLAGStst	r0, #_TIF_NEED_RESCHEDreteq	r8				@ go againb	1b
#endif

2.2 抢占计数器操作函数

preempt 是抢占的意思。linux 内核中有两个宏 preempt_enable() 和 preempt_disable() 分别时使能抢占和禁止抢占。当定义 CONFIG_PREEMOPTION 宏的时候,在 preempt_enable() 中会进行判断,如果当前条件满足,并且有更高优先级的线程需要抢占的话,那么就会进行抢占调度。如果没有定义 COFIG_PREEMPTION 宏,那么 preempt_enable() 中就不会做抢占调度的工作。


#ifdef CONFIG_PREEMPTION
#define preempt_enable() \
do { \barrier(); \if (unlikely(preempt_count_dec_and_test())) \__preempt_schedule(); \
} while (0)#define preempt_enable_notrace() \
do { \barrier(); \if (unlikely(__preempt_count_dec_and_test())) \__preempt_schedule_notrace(); \
} while (0)#define preempt_check_resched() \
do { \if (should_resched(0)) \__preempt_schedule(); \
} while (0)#else /* !CONFIG_PREEMPTION */
#define preempt_enable() \
do { \barrier(); \preempt_count_dec(); \
} while (0)#define preempt_enable_notrace() \
do { \barrier(); \__preempt_count_dec(); \
} while (0)#define preempt_check_resched() do { } while (0)
#endif /* CONFIG_PREEMPTION */

2.3 _cond_resched

从 _cond_resched 的定义来看,当没有定义 CONFIG_PREEMPTION 的时候,_cond_resched() 才会生效;当定义 CONFIG_PREEMPTION 的时候,直接返回 0。

_cond_resched() 主要是在非抢占内核中起作用,在一些消耗 cpu 的场景主动调用 _cond_resched() 来防止线程占用 cpu 太多。

#ifndef CONFIG_PREEMPTION
extern int _cond_resched(void);
#else
static inline int _cond_resched(void) { return 0; }
#endif#ifndef CONFIG_PREEMPTION
int __sched _cond_resched(void)
{if (should_resched(0)) {preempt_schedule_common();return 1;}rcu_all_qs();return 0;
}
EXPORT_SYMBOL(_cond_resched);
#endif

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

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

相关文章

区块链技术下的新篇章:DAPP与消费增值的深度融合

随着区块链技术的持续演进&#xff0c;去中心化应用&#xff08;DAPP&#xff09;正逐渐受到人们的瞩目。DAPP&#xff0c;这种在分布式网络上运行的应用&#xff0c;以其去中心化、安全可靠、透明公开的特性&#xff0c;为用户提供了更为便捷和安全的消费体验。近年来&#xf…

生成式AI有哪些优越性

生成式人工智能&#xff08;AI&#xff09;近年来取得了显著的进展&#xff0c;其优势主要体现在以下几个方面&#xff1a; 创造性和创新能力&#xff1a;生成式AI能够产生全新的内容&#xff0c;包括文本、图像、音乐等&#xff0c;这些内容在某种程度上是创新的。它可以帮助艺…

springboot+vue考试管理系统

基于springboot和vue的考试管理系统 001 springboot vue前后端分离项目 本文设计了一个基于Springbootvue的前后端分离的在线考试管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

Centos7部署单节点MongoDB(V4.2.25)

&#x1f388; 作者&#xff1a;互联网-小啊宇 &#x1f388; 简介&#xff1a; CSDN 运维领域创作者、阿里云专家博主。目前从事 Kubernetes运维相关工作&#xff0c;擅长Linux系统运维、开源监控软件维护、Kubernetes容器技术、CI/CD持续集成、自动化运维、开源软件部署维护…

Swagger3/2+Spring boot 使用小结

一&#xff1a;前言 Swagger 是一个 RESTful API 的开源框架&#xff0c;它的主要目的是帮助开发者设计、构建、文档化和测试 Web API。Swagger 的核心思想是通过定义和描述 API 的规范、结构和交互方式&#xff0c;以提高 API 的可读性、可靠性和易用性&#xff0c;同时降低 A…

ETCD跨城容灾与异地多活网络故障的相关表现分析

ETCD跨城容灾与异地多活网络故障的相关表现分析 1. 网络架构2. 单个网络中断-跟leader区中断2.1. 网络中断2.2. 网络恢复 3. 单个网络中断-跟非leader区中断4. 两个网络中断-leader区中断5. 两个网络中断-非leader区中断6. 两个网络中断-非leader区中断7. 总结8. 参考文档 etcd…

CRC计算流程详解和FPGA实现

一、概念 CRC校验&#xff0c;中文翻译过来是&#xff1a;循环冗余校验&#xff0c;英文全称是&#xff1a;Cyclic Redundancy Check。是一种通过对数据产生固定位数的校验码&#xff0c;以检验数据是否存在错误的技术。 其主要特点是检错能力强、开销小&#xff0c;易于电路实…

记录开发STM32遇到的卡死问题-串口

背景&#xff1a;以STM32作为主控&#xff0c;广州大彩显示屏显示&#xff0c;主控实时采集数据&#xff0c;串口波特率115200.设置收发频率为50Hz&#xff0c;即单片机每秒发送50帧数据&#xff0c;每秒接收50帧数据&#xff0c;每帧数据大概14字节。 问题&#xff1a;系统长…

智能优化算法 | Matlab实现牛顿-拉夫逊优化算法Newton-Raphson-based optimize(内含完整源码)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 智能优化算法 | Matlab实现牛顿-拉夫逊优化算法Newton-Raphson-based optimize(内含完整源码) 源码设计 % ------------------------------------------------------------------------------------------------…

鸿蒙一次开发,多端部署(十五)常见问题

如何查询设备类型 设备类型分为default&#xff08;默认设备&#xff09;、tablet、tv、wearable、2in1等&#xff0c;有多种查询设备类型的方式。 通过命令行的方式查询设备类型。 通过命令行查询指定系统参数&#xff08;const.product.devicetype&#xff09;进而确定设备…

软件设计师笔记

计算机 运算器组成&#xff1a;算术逻辑单元(ALU)、累加寄存器(AC)、数据缓冲寄存器(DR)、状态条件寄存器()等组成。 控制器组成&#xff1a;指令寄存器(IR)、程序计数器(PC)、地址寄存器(AR)、指令译码器(ID)。 最小数据单位&#xff1a;bit 最小存储单位: byte n进制 转 1…

力扣爆刷第103天之CodeTop100五连刷1-5

力扣爆刷第103天之CodeTop100五连刷1-5 文章目录 力扣爆刷第103天之CodeTop100五连刷1-5一、3. 无重复字符的最长子串二、206. 反转链表三、146. LRU 缓存四、215. 数组中的第K个最大元素五、25. K 个一组翻转链表 一、3. 无重复字符的最长子串 题目链接&#xff1a;https://l…

使用Intellij idea编写Spark应用程序(Scala+Maven)

使用Intellij idea编写Spark应用程序(ScalaMaven) 对Scala代码进行打包编译时&#xff0c;可以采用Maven&#xff0c;也可以采用sbt&#xff0c;相对而言&#xff0c;业界更多使用sbt。这里介绍IntelliJ IDEA和Maven的组合使用方法。IntelliJ IDEA和SBT的组合使用方法&#xf…

docker实践教程,mysql中使用自定义目录实现数据挂载(二)

有一些知识点在docker实践教程&#xff0c;nginx中使用数据卷映射修改前端网页&#xff08;一&#xff09;&#xff0c;就不累述了。 下载mysql的镜像 docker pull mysql创建容器 先去Docker Hub看看mysql是怎么使用的 可知&#xff0c;运行命令为&#xff1a;&#xff08;…

SpringCloud之网关组件Gateway学习

SpringCloud之网关组件Gateway学习 GateWay简介 Spring Cloud Gateway是Spring Cloud的⼀个全新项目&#xff0c;目标是取代Netflix Zuul&#xff0c;它基于Spring5.0SpringBoot2.0WebFlux&#xff08;基于高性能的Reactor模式响应式通信框架Netty&#xff0c;异步⾮阻塞模型…

2024 用CleanMyMac X为您的MAC清理提速吧

CleanMyMac X 是由 MacPaw 公司开发的一款针对 macOS 操作系统的电脑清理工具。它可以帮助用户清理电脑中的垃圾文件、卸载不需要的软件、优化电脑性能等。它的界面简洁明了&#xff0c;操作简单易懂&#xff0c;非常适合普通用户使用。 链接: https://pan.baidu.com/s/1_TFnrI…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

Linux安装Nginx及配置TCP负载均衡

目录 1、安装编译工具及库文件2、下载解压Nginx压缩包3、Ngnix配置Tcp负载均衡4、配置Ngnix的文件5、Nginx启动 1、安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel pcre-devel2、下载解压Nginx压缩包 wget https://nginx.o…

【创作纪念日】1024回忆录

不知不觉中&#xff0c;从创作第一篇文章到现在&#xff0c;已经1024天了&#xff0c;两年多的时间里&#xff0c;已经从硕士到博士了&#xff0c;1024&#xff0c;对于程序员来说&#xff0c;是个特别的数字吧&#xff0c;在此回忆与记录一下这些美好的经历吧。 缘起 很早以前…

MySQL面试题--开发(最全,涵盖SQL基础、架构、事务)

MySQL面试题--事务https://mp.csdn.net/mp_blog/creation/editor/136947072 MySQL面试题--MySQL内部技术架构https://blog.csdn.net/Timebro/article/details/136946046?spm1001.2014.3001.5501 MySQL面试题--最全面-索引https://blog.csdn.net/Timebro/article/details/136…