Linux内核中几个比较有意思的解释(进程调度算法,页面调度算法,非线性工作集)

简介:

1.O(1)调度器的时间计算公式与CFS调度器

Linux 2.6.23之前普遍采用了O(1)调度器,它是一种基于优先级的时间片调度算法,所谓的O(1)只是它的一些精巧的数据结构使然,在不考虑动态补偿/惩 罚的情况下,只要优先级确定,那么时间片就是固定的。2.6.23以后的CFS呢,它是一种基于权重的非时间片调度算法,进程每次执行的时间并不是固定 的,而是根据进程数在一个准固定周期内按照其权重比例的时间,依然以时间片为术语,CFS下,进程每次运行的时间与进程的总量有关。
       即便在不考虑动态补偿/惩罚的前提下,O(1)依然面临双斜率问题,为了解释这个问题,我先给出进程优先级公式:
prio=MAX_RT_PRIO+nice+20
其中,MAX_RT_PRIO为100,nice为-20到19闭区间内的任意整数。接下来时间片的计算体现了双斜率:
如果prio小于120:time_slice=20*(140-prio)
如果prio大于等于120:time_slice=5*(140-prio)
可 见,只要prio确定了,每个进程的时间片也就确定了,以120为分界,高优先级与低优先级的时间片计算是不同的,之所以这样是为了:既要体现高优先级的 优势,又不过于削弱低优先级。通过O(1)的逻辑,我们可以算出,所有进程必须完成一轮的调度,即每一个进程必须有机会运行一次,因此“一轮调度”的时间 随着进程数量的增加是增加了的。
       我们现在看看CFS是怎么逆转这个结局的。CFS调度非常简单,没有太多的计算公式。依然不考虑动态补偿/惩罚,CFS完全按照权重,Linux内核将 40个优先级映射了40个权重,为了简化讨论,我假设权重分别为1,1*1.2,1*1.2*1.2,1*1.2*1.2*1.2,....以1.2倍等 比例增加,然后定义一个固定的调度周期或以任意一段时间slice内,一个进程运行的时间就是slice*(进程权重/权重和),可见,如果进程数量增 加,所有的进程集体平滑变慢,意思是每次运行的时间减少(时间片不再固定),所谓的“完全公平”意味着权值大的进程其虚拟时钟步进比较慢,权值小的进程其 虚拟时钟步进比较快,CFS在每一个调度点(比如时钟tick,wake up,fork等)选择虚拟时钟最小的进程运行,这是相对于O(1)来讲更加平滑的一种方式,因此体现了一种延迟公平,至于吞吐,还是按照权重来的,而权 重映射到了优先级。而O(1)更多的是吞吐公平。
       总结来讲,就是O(1)为每个进程计算固定的时间片,而CFS则是在相同的时间段内计算每个进程运行的时间比例,可见二者基点不同,甚至是完全相反的。
       现在,我们给出评价。CFS更加平滑,非常适合交互式进程,因为交互进程是饥饿敏感的,但是它们不经常占有CPU,然而一旦需要CPU,必须马上让其予取 予求。对于有高吞吐需求的服务进程,CFS并不适合,这种进程的需求是一旦占据CPU,则尽可能让其运行久一些,固定时间片的O(1)更加适合。按照惯常 的分类法,I/O密集型的进程多属于交互(可能还有存储类)的,这种进程由I/O驱动,应该满足其任何时刻的CPU需求,因为它们不会占据太久,然而对于 CPU密集型进程,得到CPU的机会应该比I/O密集应用少,因为它们一旦获得CPU,就要长期占据。总的来讲,对于桌面客户端,CFS更适合,对于服务 器,O(1)更加适合。
       本文没有谈及另外两种调度器,也就是Windows调度器以及Linux BFS调度器,前者基于动态优先级提升/恢复,适合桌面应用,后者基于优先级分类O(n)算法,不考虑众核和NUMA扩展,更适合移动终端。

2.缺页中断的Major和Other

所 有进程的虚拟地址空间共享一个限量的物理内存,势必需要按需调页,这种做法之所以可行是因为每一个时间点,CPU们只需要少量的物理页面获得映射。现在的 问题是,考虑如果出现缺页-页表项中的“存在位”为0,从哪里获得新的page。答案很简单,当然是从代价最小的地方获取page。
       我们此时必须考虑缺页时所需page的类型,大致可以分为3类:
1).完全的地址缺页,即该地址曾经没有映射过物理页面。
2).该地址曾经映射过页面,但是被换出到交换空间了。
3).该地址曾经映射的page属于一个文件系统的文件,但是已经解除了映射。
针对以上3种情况,所谓的“代价最小”拥有不同的策略。
       首先看1),这个很简单,直接从伙伴系统分配page即可,当然分配单独一个page所付出的代价相当小,因为伙伴系统之上有一个per cpu的page pool,这个pool的分配不需要任何lock。现在我们看看这个代价小是否足够小,看来是的,但是并不绝对。对于读操作来讲,假设之前有一个page 映射于该缺页虚拟地址,后来解除了映射,我们知道此时该page的部分数据已经cache到了CPU cache line中,当再次需要读该page但是缺页时,我们希望获得原先的那个page,愿景是好的,可是我们怎么追踪这个page呢?追踪这个page和缺页 进程的关系的代价是否抵消保持cache热度的收益呢?事实上,这很难,因为你要考虑到共享内存的情况,这是一个多对一的双向关系,也就是一个多对多的关 系。然而Linux的内存子系统并没有什么都不做,而是它基于一种概率行为将释放到per cpu的page pool的行为分为了cold release和hot release,hot release将page添加到pool的队头,反之到队尾,而per cpu page pool的分配行为是队头分配,如果足够幸运,也许进程可以获得刚刚被解除映射的那个做过读操作的page。内核是怎么保证一个进程是足够幸运的呢?这个 很形而上但却也实用,内核采用了一个准LRU算法防止了page在进程之间颠簸,局部性保证了在进程内部一个page被访问后的一段时间内再被访问的几率 很大。
       再看2),内核里面运行着一个page回收交换的守护内核线程,发现一个不常被访问要被回收的page是脏page时,内核线程并不是直接启动IO将其写 入交换空间,而是暂时先将其排入一个swap cache,也就是给了一个page一次不需要IO而被再次使用的机会,做这样的策略其背后还是局部性原理。当缺页发生时,首先会在swap cache里面寻找,如果找到就不需要进行IO了。做这个策略的现实意义是巨大的,在分级存储原理我们可以知道,内存访问和磁盘IO的时间差了几个数量 级,所以不到必须要做,是不会刷swap cache到swap分区的。
       最后我们看3),和2)类似,但是这个涉及到了filesystem的文件page cache,由radix树组织,这个树和page回收是无关的,所谓刷掉一个属于文件的page指的是仅仅将该page解除页表项映射,实际上它完全可 能还在文件的radix树中,在发生缺页的时候,如果在radix树中找到了该page,那么只需建立一个映射即可,无需再进行磁盘IO。
       综上,我们可以知道,只要不进行磁盘IO就尽量不要,只要不进行磁盘IO的缺页处理就是Minor,进行了IO的则是Major,一个名称而已。如今的内核将Minor进行了细分,但是这并不是重点,因此统一称为Other。
       在此不得不提的是LRU算法,一般而言,几乎所有的操作系统都采用了准LRU而不是标准的LRU,这是因为标准LRU只是理论上的,实际实现起来不现实, 并不是说硬件消耗巨大,更多是因为“它的效果并不比准LRU好甚至更糟糕”,标准的LRU是一个栈式管理系统,空间局部性诚然重要,然而考虑到循环的话, 在循环边界将会面对空间局部性的对立极端,这就是列维长跳!!列维短跳是符合空间局部性的,但是列维长跳是空间局部性的对立。顺便说一句,整个人类社会的 任何行为都符合列维长跳原则,如果把量变看做列维短跳,那么质变就是列维长跳,这是根本原则,马克思说过的。
       Linux内核采用双时钟二次机会算法模拟了LRU算法,效果非常好。

3.进程地址空间的非线性映射与工作集

Linux内核采用vma来表示进程地址空间中的一段,至于这一段映射了什么vma自己管理,对上层只是提供地址空间的一段连续的虚拟内存。
       一般而言,一个文件的一部分对应一个vma,如果需要映射一个文件的不同部分,就需要不同的vma,如果足够幸运,这几个vma可以紧紧挨在一起,但是在 两次映射之间,一些别的映射占据了hole,那么就不好玩了,因此需要一种针对文件“重新布局”的方式,下面的图示展示了这个想法:


wKioL1YGH6rBicJqAAG2uh7BWDg473.jpg


但 是仅仅针对文件做这个解释难免有点不尽兴。操作系统中有一个工作集的概念,这个概念也是依托局部性原理。工作集就是将不同的内容映射到一个固定的虚拟地址 空间窗口,如果CPU的cache line是依据虚拟地址寻址的,那么时间空间局部性将会发挥很大的作用,在这个过程中,TLB也会发挥作用。基于虚拟地址的工作集是虚拟地址空间和物理内 存之间的真正隔离。
       本质上来讲,非线性映射并不一定要针对文件,它要做的就是“将不同的内容映射到相同的虚拟地址区段”。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1698404

相关文章
|
10天前
|
自然语言处理 监控 Linux
Linux 内核源码分析---proc 文件系统
`proc`文件系统是Linux内核中一个灵活而强大的工具,提供了一个与内核数据结构交互的接口。通过本文的分析,我们深入探讨了 `proc`文件系统的实现原理,包括其初始化、文件的创建与操作、动态内容生成等方面。通过对这些内容的理解,开发者可以更好地利用 `proc`文件系统来监控和调试内核,同时也为系统管理提供了便利的工具。
47 16
|
12天前
|
算法 数据可视化 调度
基于NSGAII的的柔性作业调度优化算法MATLAB仿真,仿真输出甘特图
本程序基于NSGA-II算法实现柔性作业调度优化,适用于多目标优化场景(如最小化完工时间、延期、机器负载及能耗)。核心代码完成任务分配与甘特图绘制,支持MATLAB 2022A运行。算法通过初始化种群、遗传操作和选择策略迭代优化调度方案,最终输出包含完工时间、延期、机器负载和能耗等关键指标的可视化结果,为制造业生产计划提供科学依据。
|
2月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
112 16
|
2月前
|
安全 Linux 测试技术
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
|
2月前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
155 15
|
3月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
3月前
|
Java Linux API
[JavaEE]———进程、进程的数据结构、进程的调度
操作系统,进程任务,PCB,PID,内存指针,文件描述符表,进程的调度,并发编程,状态,优先级,记账信息,上下文
|
4月前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
146 4
|
4月前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
137 17
|
3月前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####