一文让你彻底理解Linux内核调度器进程优先级

news/2024/4/26 7:15:19/文章来源:https://blog.csdn.net/m0_50662680/article/details/129221186

一、前言

本文主要描述的是进程优先级这个概念。从用户空间来看,进程优先级就是nice value和scheduling priority,对应到内核,有静态优先级、realtime优先级、归一化优先级和动态优先级等概念。我们希望能在第二章将这些相关的概念描述清楚。为了加深理解,在第三章我们给出了几个典型数据流过程的分析。

二、overview

1、蓝图

2、用户空间的视角

在用户空间,进程优先级有两种含义:nice value和scheduling priority。对于普通进程而言,进程优先级就是nice value,从-20(优先级最高)~19(优先级最低),通过修改nice value可以改变普通进程获取cpu资源的比例。随着实时需求的提出,进程又被赋予了另外一种属性scheduling priority,而这些进程被称为实时进程。实时进程的优先级的范围可以通过sched_get_priority_min和sched_get_priority_max,对于linux而言,实时进程的scheduling priority的范围是1(优先级最低)~99(优先级最高)。当然,普通进程也有scheduling priority,被设定为0。

 

3、内核中的实现

内核中,task struct中有若干和进程优先级有个的成员,如下:

struct task_struct { 
...... int prio, static_prio, normal_prio; unsigned int rt_priority; 
...... unsigned int policy; 
...... 
}

policy成员记录了该线程的调度策略,而其他的成员表示了各种类型的优先级,下面的小节我们会一一描述。

4、静态优先级

task struct中的static_prio成员。我们称之静态优先级,其特点如下:

(1)值越小,进程优先级越高

(2)0 – 99用于real-time processes(没有实际的意义),100 – 139用于普通进程

(3)缺省值是 120

(4)用户空间可以通过nice()或者setpriority对该值进行修改。通过getpriority可以获取该值。

(5)新创建的进程会继承父进程的static priority。

静态优先级是所有相关优先级的计算的起点,要么继承自父进程,要么用户空间自行设定。一旦修改了静态优先级,那么normal priority和动态优先级都需要重新计算。

5、实时优先级

task struct中的rt_priority成员表示该线程的实时优先级,也就是从用户空间的视角来看的scheduling priority。0是普通进程,1~99是实时进程,99的优先级最高。

6、归一化优先级

task struct中的normal_prio成员。我们称之归一化优先级(normalized priority),它是根据静态优先级、scheduling priority和调度策略来计算得到,代码如下:

static inline int normal_prio(struct task_struct *p) 
{ int prio;if (task_has_dl_policy(p)) prio = MAX_DL_PRIO-1; else if (task_has_rt_policy(p)) prio = MAX_RT_PRIO-1 - p->rt_priority; else prio = __normal_prio(p); return prio; 
}

这里我们先聊聊归一化(Normalization)这个看起来稍微有点晦涩的术语。如果你做过音视频定点算法的优化,应该对这个词不陌生。不同的定点数据有不同的表示,有Q31的,有Q15,这些数据的小数点的位置不同,无法进行比较、加减等操作,因此需要归一化,全部转换成某个特定的数据格式(其实就是确定小数点的位置)。在数学上,1米和1mm在进行操作的时候也需要归一化,全部转换成同一个量纲就OK了。对于这里的优先级,调度器需要综合考虑各种因素,例如调度策略,nice value、scheduling priority等,把这些factor全部考虑进来,归一化成一个数轴上的number,以此来表示其优先级,这就是normalized priority。对于一个线程,其normalized priority的number越小,其优先级越大。

调度策略是deadline的进程比RT进程和normal进程的优先级还要高,因此它的归一化优先级是负数:-1。如果采用实时调度策略,那么该线程的normalized priority和rt_priority相关。task struct中的rt_priority成员是用户空间视角的实时优先级(scheduling priority),MAX_RT_PRIO-1是99,MAX_RT_PRIO-1 - p->rt_priority则翻转了实时进程的scheduling priority,最高优先级是0,最低是98。顺便说一句,normalized priority是99的情况是没有意义的。对于普通进程,normalized priority就是其静态优先级。

7、动态优先级

task struct中的prio成员表示了该线程的动态优先级,也就是调度器在进行调度时候使用的那个优先级。动态优先级在运行时可以被修改,例如在处理优先级翻转问题的时候,系统可能会临时调升一个普通进程的优先级。一般设定动态优先级的代码是这样的:p->prio = effective_prio(p),具体计算动态优先级的代码如下:

static int effective_prio(struct task_struct *p) 
{ p->normal_prio = normal_prio(p); if (!rt_prio(p->prio)) return p->normal_prio; return p->prio; 
}

rt_prio是一个根据当前优先级来确定是否是实时进程的函数,包括两种情况,一种情况是该进程是实时进程,调度策略是SCHED_FIFO或者SCHED_RR。另外一种情况是人为的将该进程提升到RT priority的区域(例如在使用优先级继承的方法解决系统中优先级翻转问题的时候)。在这两种情况下,我们都不改变其动态优先级,即effective_prio返回当前动态优先级p->prio。其他情况,进程的动态优先级跟随归一化的优先级。

三、典型数据流程分析

1、用户空间设定nice value

用户空间设定nice value的操作,在内核中主要是set_user_nice函数实现的,无论是sys_nice或者sys_setpriority,在参数检查和权限检查之后都会调用set_user_nice函数,完成具体的设定。

代码如下:

void set_user_nice(struct task_struct *p, long nice) 
{ int old_prio, delta, queued; unsigned long flags; struct rq *rq;  rq = task_rq_lock(p, &flags); if (task_has_dl_policy(p) || task_has_rt_policy(p)) {-----------(1) p->static_prio = NICE_TO_PRIO(nice); goto out_unlock; } queued = task_on_rq_queued(p);-------------------(2) if (queued) dequeue_task(rq, p, DEQUEUE_SAVE);p->static_prio = NICE_TO_PRIO(nice);----------------(3) set_load_weight(p); old_prio = p->prio; p->prio = effective_prio(p); delta = p->prio - old_prio;if (queued) { enqueue_task(rq, p, ENQUEUE_RESTORE);------------(2) if (delta < 0 || (delta > 0 && task_running(rq, p)))------------(4) resched_curr(rq); } 
out_unlock: task_rq_unlock(rq, p, &flags); 
}

(1)如果是实时进程或者deadline类型的进程,那么nice value其实是没有什么实际意义的,不过我们还是设定其静态优先级,当然,这样的设定其实不会起到什么作用的,也不会实际改变调度器行为,因此直接返回,没有dequeue和enqueue的动作。

(2)在step中已经处理了调度策略是RT类和DEADLINE类的进程,因此,执行到这里,只可能是普通进程了,使用CFS算法。如果该task在run queue上(queued 等于true),那么由于我们修改了nice value,调度器需要重新审视当前runqueue中的task。因此,我们需要将该task从rq中摘下,在重新计算优先级之后,再次插入该runqueue对应的runable task的红黑树中。

(3)最核心的代码就是p->static_prio = NICE_TO_PRIO(nice);这一句了,其他的都是side effect。比如说load weight。当cpu一刻不停的运算的时候,其load是100%,没有机会调度到idle进程休息一下。当系统中没有实时进程或者deadline进程的时候,所有的runnable的进程一起来瓜分cpu资源,以此不同的进程分享一个特定比例的cpu资源,我们称之load weight。不同的nice value对应不同的cpu load weight,因此,当更改nice value的时候,也必须通过set_load_weight来更新该进程的cpu load weight。除了load weight,该线程的动态优先级也需要更新,这是通过p->prio = effective_prio(p);来完成的。

(4)delta 记录了新旧线程的动态优先级的差值,当调试了该线程的优先级(delta < 0),那么有可能产生一个调度点,因此,调用resched_curr,给当前正在运行的task做一个标记,以便在返回用户空间的时候进行调度。此外,如果修改当前running状态的task的动态优先级,那么调降(delta > 0)意味着该进程有可能需要让出cpu,因此也需要resched_curr标记当前running状态的task需要reschedule。

资料直通车:最新Linux内核源码资料文档+视频资料

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

2、进程缺省的调度策略和调度参数

我们先思考这样的一个问题:在用户空间设定调度策略和调度参数之前,一个线程的default scheduling policy是什么呢?这需要追溯到fork的时候(具体代码在sched_fork函数中),这个和task struct中sched_reset_on_fork设定相关。如果没有设定这个flag,那么说明在fork的时候,子进程跟随父进程的调度策略,如果设定了这个flag,则说明子进程的调度策略和调度参数不能继承自父进程,而是需要设定为default。代码片段如下:

int sched_fork(unsigned long clone_flags, struct task_struct *p) 
{
…… p->prio = current->normal_prio; -------------------(1) if (unlikely(p->sched_reset_on_fork)) { if (task_has_dl_policy(p) || task_has_rt_policy(p)) {----------(2) p->policy = SCHED_NORMAL; p->static_prio = NICE_TO_PRIO(0); p->rt_priority = 0; } else if (PRIO_TO_NICE(p->static_prio) < 0) p->static_prio = NICE_TO_PRIO(0);p->prio = p->normal_prio = __normal_prio(p); ------------(3) set_load_weight(p);  p->sched_reset_on_fork = 0; }
……
}

(1)sched_fork只是fork过程中的一个片段,在fork一开始,dup_task_struct已经复制了一个和父进程完全一个的进程描述符(task struct),因此,如果没有步骤2中的重置,那么子进程是跟随父进程的调度策略和调度参数(各种优先级),当然,有时候为了解决PI问题而临时调升父进程的动态优先级,在fork的时候不宜传递到子进程中,因此这里重置了动态优先级。

(2)缺省的调度策略是SCHED_NORMAL,静态优先级等于120(也就是说nice value等于0),rt priority等于0(普通进程)。不管父进程如何,即便是deadline的进程,其fork的子进程也需要恢复到缺省参数。

(3)既然调度策略和静态优先级已经修改了,那么也需要更新动态优先级和归一化优先级。此外,load weight也需要更新。一旦子进程中恢复到了缺省的调度策略和优先级,那么sched_reset_on_fork这个flag已经完成了历史使命,可以clear掉了。

OK,至此,我们了解了在fork过程中对调度策略和调度参数的处理,这里还是要追加一个问题:为何不一切继承父进程的调度策略和参数呢?为何要在fork的时候reset to default呢?在linux中,对于每一个进程,我们都会进行资源限制。例如对于那些实时进程,如果它持续消耗cpu资源而没有发起一次可以引起阻塞的系统调用,那么我们猜测这个realtime进程跑飞了,从而锁住了系统。对于这种情况,我们要进行干预,因此引入了RLIMIT_RTTIME这个per-process的资源限制项。但是,如果用户空间的realtime进程通过fork其实也可以绕开RLIMIT_RTTIME这个限制,从而肆意的攫取cpu资源。然而,机智的内核开发人员早已经看穿了这一切,为了防止实时进程“泄露”到其子进程中,sched_reset_on_fork这个flag被提出来。

3、用户空间设定调度策略和调度参数

通过sched_setparam接口函数可以修改rt priority的调度参数,而通过sched_setscheduler功能会更强一些,不但可以设定rt priority,还可以设定调度策略。而sched_setattr是一个集大成之接口,可以设定一个线程的调度策略以及该调度策略下的调度参数。当然,对于内核,这些接口都通过__sched_setscheduler这个内核函数来完成对指定线程调度策略和调度参数的修改。

__sched_setscheduler分成两个部分,首先进行安全性检查和参数检查,其次进行具体的设定。

我们先看看安全性检查。如果用户空间可以自由的修改调度策略和调度优先级,那么世界就乱套了,每个进程可能都想把自己的调度策略和优先级提升上去,从而获取足够的CPU 资源。因此用户空间设定调度策略和调度参数要遵守一定的规则:如果没有CAP_SYS_NICE的能力,那么基本上该线程能被允许的操作只是降级而已。例如从SCHED_FIFO修改成SCHED_NORMAL,异或不修改scheduling policy,而是降低静态优先级(nice value)或者实时优先级(scheduling priority)。这里例外的是SCHED_DEADLINE的设定,按理说如果进程本身的调度策略就是SCHED_DEADLINE,那么应该允许“优先级”降低的操作(这里用优先级不是那么合适,其实就是减小run time,或者加大period,这样可以放松对cpu资源的获取),但是目前的4.4.6内核不允许(也许以后版本的内核会允许)。此外,如果没有CAP_SYS_NICE的能力,那么设定调度策略和调度参数的操作只能是限于属于同一个登录用户的线程。如果拥有CAP_SYS_NICE的能力,那么就没有那么多限制了,可以从普通进程提升成实时进程(修改policy),也可以提升静态优先级或者实时优先级。

具体的修改比较简单,是通过__setscheduler_params函数完成,其实也就是是根据sched_attr中的参数设定到task struct相关成员中,大家可以自行阅读代码进行理解。

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

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

相关文章

超算中心、并行计算

现在超算中心已经迅速发展 合肥&#xff1a; 合肥先进中心 合肥曙光超算中心平台 合肥安徽大学超算中心 合肥中科大超算中心 合肥中科院超算中心 合肥大一点的公司都会有自己的集群&#xff0c; 超算中心又称为集群&#xff0c;一般集群是小型服务器组成&#xff0c;超…

【软件测试】从功能到自动化测试,测试人的进阶之路细节,这些必不可少......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 测试流程&#xff0…

RIP路由协议的更新(电子科技大学TCP/IP第二次实验)

一&#xff0e;实验目的 1、掌握 RIP 协议在路由更新时的发送信息和发送方式 2、掌握 RIP 协议的路由更新算法 二&#xff0e;预备知识 1、静态路由选择和动态路由选择 2、内部网关协议和外部网关协议 3、距离向量路由选择 三&#xff0e;实验原理 RIP 协议&#xff08…

【OC】块初识

Block简介 Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能&#xff1a;带有自动变量的匿名函数。 匿名函数 所谓匿名函数就是不带有名称的函数。C语言的标准不允许存在这样的函数。例&#xff1a; int func(int count);它声明了名称为func的函数。下面的源代…

C++---线性dp---传纸条(每日一道算法2023.2.26)

注意事项&#xff1a; 本题dp思路与 “线性dp–方格取数” 一致&#xff0c;下方思路仅证明为什么使用方格取数的思路是正确的。 题目&#xff1a; 小渊和小轩是好朋友也是同班同学&#xff0c;他们在一起总有谈不完的话题。 一次素质拓展活动中&#xff0c;班上同学安排坐成…

3.7寸按键翻页工牌

产品参数 产品型号 ESL_BWR3.7_BLE 产品尺寸 (mm) 62.51066.5 显示技术 E ink 显示区域 (mm) 47.32(H)81.12(V) 分辨率 (像素) 280480 像素尺寸(mm) 0.1690.169 150dpi 显示颜色 黑/白 视觉角度 180 工作温度 0℃ - 50℃ 电池 500mAh ( Type-C 充电…

Overleaf推广奖励:增加合作者的数量、解锁Dropbox同步和项目修改历史

Overleaf推广奖励 Overleaf是一个LaTeX\LaTeXLATE​X在线编译器&#xff0c;它可以让你与合作者共同在线编辑文档。但是默认的免费账号仅能邀请一个合作者。那么如何增加合作者的数量呢&#xff1f; Overleaf推出了一个奖励计划&#xff0c;你邀请其他人注册Overleaf&#xf…

java地图导出——添加经纬线

概述 前面的文章Node实现切片的拼接和地图的导出和Java实现地图的导出分别讲述可如何在node和java中实现切片的拼接以及地图的导出。本文&#xff0c;书接前文&#xff0c;实现java导出时经纬度的添加。 实现后效果 实现 完整的实现思路流程如下图&#xff1a; 1. 根据切片…

数据挖掘概述

目录1、数据挖掘概述2、数据挖掘常用库3、模型介绍3.1 分类3.2 聚类3.3 回归3.4 关联3.5 模型集成4、模型评估ROC 曲线5、模型应用1、数据挖掘概述 数据挖掘&#xff1a;寻找数据中隐含的知识并用于产生商业价值 数据挖掘产生原因&#xff1a;海量数据、维度众多、问题复杂 数…

macOS使用CodeRunner快速配置fortran环境

个人网站:xzajyjs.cn 由于一些项目的缘故&#xff0c;需要有fortran的需求&#xff0c;但由于是M1 mac的缘故&#xff0c;不能像windows那样直接使用vsivf这种经典配置。搜了一下网上主流的跨平台方案&#xff0c;主要是gfortran&#xff0c;最近用Coderunner&#xff08;主要…

MyBatis——增删改查操作的实现

开启mybatis sql日志打印 可以在日志中看到sql中执行的语句 在配置文件中加上下面这几条语句 mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl logging.level.com.example.demodebug查询操作 根据用户id查询用户 UserMapper&#xff1a; User…

Elasticsearch7.8.0版本进阶——自定义分析器

目录一、自定义分析器的概述二、自定义的分析器的测试示例一、自定义分析器的概述 Elasticsearch 带有一些现成的分析器&#xff0c;然而在分析器上 Elasticsearch 真正的强大之 处在于&#xff0c;你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单 …

Lighthouse组合Puppeteer检测页面

如上一篇文章lighthouse的介绍和基本使用方法结尾提到的一样&#xff0c;我们在实际使用Lighthouse检测页面性能时&#xff0c;通常需要一定的业务前置条件&#xff0c;比如最常见的登录操作、如果没有登录态就没有办法访问其他页面。再比如有一些页面是需要进行一系列的操作&a…

傻瓜式minio使用指南

傻瓜式minio使用指南1. docker部署minio1.1 docker拉取minio镜像1.2 创建docker容器1.3 查看docker容器是否启动正常2.登陆minio2.1 账户、密码为原先设置minioadmin2.2 创建桶2.3 设置桶属性3.Java客户端使用3.1引入依赖3.2 使用3.3 结果1. docker部署minio 1.1 docker拉取mi…

C语言几种判断语句简述

C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 fals…

Linux系统下搭建maven环境

文章目录前述从官网下载安装包安装 maven修改maven配置修改环境变量测试前述 安装 maven 环境前&#xff0c;需要先安装 java 环境&#xff0c;如果没有安装 java 环境&#xff0c;可以参考&#xff1a;https://blog.csdn.net/weixin_45583303/article/details/118631855 从官…

【力扣周赛#334】6369. 左右元素和的差值 + 6368. 找出字符串的可整除数组 + 6367. 求出最多标记下标

目录 6369. 左右元素和的差值 - 前缀后缀和 ac 6368. 找出字符串的可整除数组 - 操作余数ac 6367. 求出最多标记下标 - 二分答案 贪心 6369. 左右元素和的差值 - 前缀后缀和 ac class Solution {public int[] leftRigthDifference(int[] nums) {int nnums.length;int[] re…

【JavaWeb】复习重点内容

✅✅作者主页&#xff1a;&#x1f517;孙不坚1208的博客 &#x1f525;&#x1f525;精选专栏&#xff1a;&#x1f517;JavaWeb从入门到精通&#xff08;持续更新中&#xff09; &#x1f4cb;&#x1f4cb; 本文摘要&#xff1a;本篇文章主要分享JavaWeb的学习重点内容。 &a…

QT之OpenGL混合

QT之OpenGL混合1. 概述2. 实现2.1 丢弃片段2.1.1 Demo2.2 混合2.2.1 相关函数2.2.2 排序问题2.2.3 Demo1. 概述 OpenGL中&#xff0c;混合(Blending)通常是实现物体透明度(Transparency)的一种技术。 2. 实现 2.1 丢弃片段 在某些情况下&#xff0c;有些片段是只需要设置显…

ecology9-谷歌浏览器下-pdf.js在渲染时部分发票丢失文字 问题定位及解决

问题 问题描述 &#xff1a; 在谷歌浏览器下&#xff0c;pdf.js在渲染时部分发票丢失文字&#xff1b;360浏览器兼容模式不存在此问题 排查思路&#xff1a;1、对比谷歌浏览器的css样式和360浏览器兼容模式下的样式&#xff0c;没有发现关键差别 2、✔使用Fiddler修改网页js D…