当前位置: 首页 > article >正文

Linux --进程优先级

概念

  什么是进程优先级,为什么需要进程优先级,怎么做到进程优先级这是本文需要解释清楚的。

  优先级的本质其实就是排队,为了去争夺有限的资源,比如cpu的调度。cpu资源分配的先后性就是指进程的优先级。优先级高的进程有优先执行的,配置进程优先级对多任务环境的Linux很有用,可以改善系统的性能。在Linux中进程的PCB也就是task_struct中优先级属性是有几个int类型的数字来表示的,数字越小优先级越高。这里我们可以查看一个进程的优先级和修改。我们用ps -l指令可以看到以下几个数据

UID : 代表执⾏者的⾝份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍⽣⽽来的,亦即⽗进程的代号
PRI :代表这个进程可被执⾏的优先级,其值越⼩越早被执⾏
NI :代表这个进程的nice值
  
  PRI也还是⽐较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执⾏的先后顺序,此
值越⼩进程的优先级别越⾼ 。 那NI呢?就是我们所要说的nice值了,其表⽰进程可被执⾏的优先级的修正数值 PRI值越⼩越快被执⾏,那么加⼊nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice 这样,当nice值为负值的时候,那么该程序将会优先级值将变⼩,即其优先级会变⾼,则其越快被执⾏所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20⾄19,⼀共40个级别。这里的PRY(old)其实是默认初始PRI的意思。
  ⽤top命令更改已存在进程的nice: top,进⼊top后按“r”‒>输⼊进程PID‒>输⼊nice值

  

  这里可以看到,PRI为39 NI值为19,这是我修改以后的数值,原本是PRI 20 NI 0,即使我输入修改100但是最后值修改了19,说明修改NI值是有范围的 ,这是为了进程调度尽量公平,不会出现个别极端进程,为什么是在这40的范围内后续我们会讲到。

进程切换

  有了优先级就是为了在进程切换的时候进行排序,那么什么是进程切换?进程切换的核心其实就是保存上下文数据恢复上下文数据,那么什么是上下文数据,容我慢慢叙说。一个进程被被cpu调度的时候,此时它的PCB就是现在调度队列的当前节点,在CPU内部有一套寄存器来储存代码和数据,其中的eip来储存当前执行指令的下一地址,ir存储的是当前需要执行的指令,控制器先ir读指令执行,执行完毕又会反馈给eip寄存器。此时储存在寄存器中的代码和数据就是上下文数据。当前进程的时间片到了即将被切换的时候,它会将上下文数据储存到PCB中的一个tss_struct结构体中,方便下次切换回来的时候能够知道上一次运行到了哪里,恢复寄存器中的数据,每个程序在进程切换的时候都需要做这个事情,因为寄存器只有一套,这种行为是对进程代码数据运行的一种保护,如果不做保护就无法进行调度与切换。

  这里可以参考Linux0.11的内核代码发现上下文数据确实是保存在tss之中,但是在现在的内核中由于保护算法已经过于复杂了,没有十章八章难以叙述,所以而且不方便观察

Linux真实调度算法

  说了这么多优先级的本质还是为了更好的了解Lniux调度算法。这个算法并不是简单的FIFO调度,cpu在调度进程时会在一个runqueue结构体中选择需要调度的进程,这个runqueue主要由一个有两个元素queue结构体数组,两个指向queue结构体数组的指针,*active和*expored即活跃的和过期的,以下用示意图说明。

  

  我们可以看到这这两个数组的元素的结构体时由一个int 类型的nr_active,和一个有五个元素int类型的bitmap数组组成的。*active和*expored指针分别指向这两个数组元素,cpu调度时只会从活跃指针这个结构体中的queue队列来调度进程,这个队列通常前一百位是不用的,而后四十位就是对应我们上面所提到的优先级范围,40这个范围就是因为队列中只有四十个位置是储存进程的。这个队列每个元素下又挂着一个哈希桶,所以每个相同优先级的进程都会被挂在同一个桶中,调度的时候按照队列先进先出的规则被调度,当这个优先级桶的元素空了cpu才会调度下一个优先级的进程。其中这里的nr_active是一个计数器,记录当前队列中还有多少个进程,bitmap是为了提高cpu调度效率而存在的,它用5*32个比特位来表示当前调度queue队列中哪个优先级有进程再去这个地方调度进程,而不是进行遍历队列查找再调度。过期队列上放置的进程,都是时间⽚耗尽的进程和新插入的进程,当活动队列上的进程都被处理完毕之后,对过期队列的进程进⾏时间⽚重新计算。active指针永远指向活动队列, expired指针永远指向过期队列, 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间⽚到期时⼀直都存在的,所以在合适的时候交换active指针和expired指针的内容,就相当于有具有了⼀批新的活动进程。这个合适的时候就是当活跃进程nr_active计数器为0的时候,此时两个两个队列就会被调换开始新一轮的调度。注意结束的进程是不会进入过期队列的,而是会被父进程接管并释放。

   在系统当中查找⼀个最合适调度的进程的时间复杂度是⼀个常数,不随着进程增多⽽导致时间成 本增加,我们称之为进程调度O(1)算法!Linux中的内核调度算法就是进程调度O(1)算法。

  顺便提到一点Linux中的task_struct中的链式结构并不是和普通双链表那样将节点和属性放在一起,它的链表节点中只有next节点和prev节点,然后在task_struct中和其他属性一起储存。这样做的好处是能够让一个task_struct在多个队列(调度和阻塞)中存在,而不需要将相同的进程属性放在不同的链表之中再链接。如图所示

 

补充 

  那么有的同学会问了只有链表节点如何访问数据,每个数据类型的偏移量相对于结构体指针是固定的,我们可以通过链表节点的地址和偏移量来找到当前结构体指针再访问其他的数据。我们可以通过offseto这个宏来获得该成员相对于0地址的偏移。

#define offsetof(type, member) ((size_t)&(((type *)0)->member))

这里有一个代码可以方便大家理解具体过程

#include <iostream>#define offsetof(type, member) ((size_t)&(((type *)0)->member))
using namespace std;int main()
{struct A{int a;int b;float c;double d;};A instanceA; // Renamed variable to avoid conflict with struct namecout << " 结构体A的地址 : " << &instanceA << endl;cout << " 成员a的地址  :" << &instanceA.a << " 偏移量 :" << offsetof(A, a) << endl;cout << " 成员b的地址  :" << &instanceA.b << " 偏移量 :" << offsetof(A, b) << endl;cout << " 成员c的地址  :" << &instanceA.c << " 偏移量 :" << offsetof(A, c) << endl;cout << " 成员d的地址  :" << &instanceA.d << " 偏移量 :" << offsetof(A, d) << endl;return 0;
}

  我们可以看到两次运行的各个成员地址都不一样,但是偏移量都是相同的 

  然后我们就可以通过这样的方法来找到对象本身从而访问其他的数据

int main() {A instanceA;int* pB = &instanceA.b;A* pStruct = (A*)((char*)pB - offsetof(A, b));cout << "instanceA 地址: " << &instanceA << endl;cout << "通过成员b地址和偏移量得到的结构体地址: " << pStruct << endl;return 0;
}

http://www.lryc.cn/news/2398239.html

相关文章:

  • 安装和配置 Nginx 和 Mysql —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录6
  • Linux 测试本机与192.168.1.130 主机161/udp端口连通性
  • OpenCV 滑动条调整图像亮度
  • 图解gpt之注意力机制原理与应用
  • 硬件学习笔记--65 MCU的RAM及FLash简介
  • 【Oracle】视图
  • 数据库 MongoDB (NoSQL) 与 MySQL (SQL) 的写法对比
  • 基于粒子滤波的PSK信号解调实现
  • 更强劲,更高效:智源研究院开源轻量级超长视频理解模型Video-XL-2
  • 2025.6.3学习日记 Nginx 基本概念 配置 指令 文件
  • 【连接器专题】案例:产品测试顺序表解读与应用
  • 星动纪元的机器人大模型 VPP,泛化能力效果如何?与 VLA 技术的区别是什么?
  • 4000万日订单背后,饿了么再掀即时零售的“效率革命”
  • 入门AJAX——XMLHttpRequest(Get)
  • 5分钟申请edu邮箱【方案本周有效】
  • 闲谈PMIC和SBC
  • Java垃圾回收机制深度解析:从理论到实践的全方位指南
  • Ubuntu系统 | 本地部署ollama+deepseek
  • 论文阅读:CLIP:Learning Transferable Visual Models From Natural Language Supervision
  • 在图像分析算法部署中应对流行趋势的变化|文献速递-深度学习医疗AI最新文献
  • CAMEL-AI开源自动化任务执行助手OWL一键整合包下载
  • Selenium 中 JavaScript 点击的优势及使用场景
  • Linux系统-基本指令(5)
  • C++ set数据插入、set数据查找、set数据删除、set数据统计、set排序规则、代码练习1、2
  • [android]MT6835 Android 指令启动MT6631 wifi操作说明
  • C# winform教程(二)
  • Java详解LeetCode 热题 100(25):LeetCode 141. 环形链表(Linked List Cycle)详解
  • 【仿生机器人】刀剑神域计划——仿生机器人.亚丝娜
  • ARM架构推理Stable Diffusiond
  • 仓颉项目调试配置与多文件场景下的问题解析