Linux SWAP内存交换机制基本概念

2024-06-06 12:32

本文主要是介绍Linux SWAP内存交换机制基本概念,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux SWAP内存交换机制基本概念

tags: Linux源码

  • Linux SWAP内存交换机制基本概念
    • 摘要
    • 前序知识
      • 内存交换要做什么
      • 硬件上给予的支持
        • 下面假定场景更好的叙述
    • Linux中的实现
      • 数据什么时候跑到磁盘上面去的
      • 什么时候换出内存
      • 交换与回写什么关系
      • 小小的总结
    • 引用


摘要

本文旨在以较容易理解的水平讨论Linux的内存交换机制。文中尽量不涉及具体的代码,不涉及一些边边角角的情况,通过一些图示和比喻更好的让初学者理解内存交换机制的工作原理和作用,但其具体的实现请参考深入Linux内核架构深入理解Linux内核Linux内核情景分析。本文讨论的Linux内核版本为2.6.24
文中的前半部分提及了很多老生常谈的东西,如果对此有所了解可以借助目录直接跳转到关键的部分。


前序知识

内存交换要做什么

不考虑一些大型机和专用计算机,当今通用计算机结构中存储器一般设计为多层,从最靠近CPU的cache一直到磁盘,如下图示:
存储器金字塔
可以看到从上到下速度越来越慢,但价钱却越来越便宜。所以现在通用计算机存储结构之所以搞得这么复杂从上到下分了这么多层,根本原因还是成本。如果制作cache的成本特别低,那也就不会有内存卡,磁盘(磁带等)的存在了,直接来1T的chache好了,又快又好。所以在这样大的约束下,计算机(操作)系统设计人员费劲脑汁设计了缓存交换机制来充分利用计算机硬件,发挥最大的性能。

  1. 缓存机制是一种用空间换时间的机制,由于内存的速度基本上是磁盘速度的 105 ,操作系统将一些特别常用的磁盘上的数据存在内存中,在用户要访问的时候直接从内存中找出数据展示给用户,这样虽然占用了一些内存的空间,却很大程度的加速了数据的访问;
  2. 交换机制是使用时间换空间的。相比磁盘,内存真的小的可怜,我们日常使用的民用计算机一般就是 4G8G 的内存,磁盘的却有 500G1T 之大。交换机制就是把一些不常用的内存的数据暂时存储到磁盘上面上去,腾出来的内存空间留作他用,在需要的时候再从磁盘中将数据读入内存中。这样做虽然从真正需要数据到数据展示在户面前的过程变慢了,但却让整个系统拥有了看似更多的内存

硬件上给予的支持

上述工作看似简单,但要想真正实现着实有些困难,第一个比较关键的问题就是在访问内存的时候如何知道这个目的内存数据是真的在内存中还是在磁盘上呢。这里的访问内存不仅仅是指的从高级程序设计角度理解的读写内存中的一个变量值,譬如int a;a=10;,还包含根据内存地址读取一条指令的情况。(如果缺乏相关的知识请参照wiki_PC指令寄存器这篇文章。) CPU中有一个专门的组件叫做MMU(Memory management unit),无论是执行汇编中类似于load、xchange这样操作数据的指令,还是根据 PC 值取下一条指令的时候,CPU都会把要读取内存的地址交给MMU,让其做一次转换,完成从线性地址->物理地址转化。也就是说,CPU执行的汇编代码中的地址和 PC 寄存器中保存的指令地址并不是物理地址。

(下面为了叙述的方便,用“数据”来统一代表要去内存中读的“二进制串”,不管这个二进制串是常规意义上的数据,还是一条指令)

简单说一下和这篇文章相关的内存映射的部分。物理地址就是真正到内存中取数据时用的地址,每个内存单元(字节)拥有一个唯一的地址,而线性地址存在的一大原因就是为了完成这篇文章讨论的交换机制(还有其他一些原因)。由于交换机制的存在,给定一个线性地址不一定立马就能够找到一个对应的物理地址,因为数据可能被交换到磁盘上去了。那么如何标记一个数据是不是在内存中呢,以及如何建立一个线性地址到物理地址的映射呢?请耐心往下看。
具体的数据结构请搜索逻辑地址、线性地址与物理地址–无视分段机制即可,Linux不像Windows,在实现中也没有利用分段机制。要干的事情其实很简单,只不过和硬件相关感觉起来有点绕。

下面假定场景,更好的叙述

在内存中

  1. 现在假设CPU要根据线性地址 L1 读内存了,CPU在①处直接把 L1 给MMU后说“MMU你去把内存里面的数据给我读出来,我不管什么映射、交换这些乱七八糟的机制,我只关心我的数据”。MMU接到命令后就在②处查看一个存储在内存中的页表,页表简单来说就是用来记录线性地址->物理地址映射关系的一个表,同时页表中还记录了一个线性地址对应的数据是在内存中还是被交换了出去。现在假定是在内存中的物理地址 p1 处,这样的话就万事大吉了。在步骤③中物理地址 p1 通过总线发送给内存模块然后再步骤④中将数据返回给CPU。这些操作的大部分都是有硬件自动完成的,对应用程序猿来说是透明的,而且速度是很快的。这里还要说明一下,页表是操作系统创建维护的,每个进程有着自己的页表,在进程切换的时候会进行页表的切换。这也是所谓的地址空间的由来,开启页式管理的情况下,每个进程能够拥有完整的地址空间。
  2. 稍微复杂一丢丢的是如果数据不在内存中而在磁盘上。
    在磁盘上
    这种情况下,步骤②的时候MMU发现数据不在内存中,它就会通知操作系统出现了“缺页”的情况。操作系统就会暂停之前程序的执行,寻找还没有用的内存(假设物理地址为 P2 ),在步骤③中将数据从磁盘读到内存中并且在步骤④中更新页表。具体更新页表的操作也就不外乎标记逻辑地址 L1 对应的数据已经在内存中了,然后建立从 L1 -> P2 的映射。这样一来,之前被暂停的指令或依照 PC 读指令的操作将会再次从步骤①执行,不过这次应该就可以像上面的情况一样顺利的把数据从内存中读到CPU中去了。实际的整个过程的细节比文章描述的要复杂的多,这里只是为了说明白大体上一个流程,具体的还是要参考上面推荐的书籍和一些更加细分的博客,这里再次声明

这就可以看出,想要实现完整的交换机制需要硬件和操作系统的支持,缺一不可。

Linux中的实现

数据什么时候跑到磁盘上面去的

上面描述的读取操作的第二中情况基于这样一种假设“目的数据在磁盘上而不在内存中”。那么有哪些情况下页表中有对应的项,而且项被标记为“不在内存中”呢?从大的角度上讲有以下两种情况

  • 数据来源于磁盘上的一个文件。之前以缓存的目的读入到内存中来过,但是内存紧张了,为了节省内存所以把数据回写到磁盘上去了;数据来源于磁盘上的一个文件,从来还没有读入到内存中来过,只是设置了页表项,这种情况一般出现在内存映射时。
  • 数据由进程动态产生的,譬如堆栈的内存空间,由于内存紧张被换出到磁盘上去了。

什么时候换出内存

内核联合使用了如下两种机制
1. 一个周期性的守护进程(kswapd)在后台运行,该进程不断检查当前的内存使用情况,以使在达到特定的阀值时发起页的交换操作。使用该方法,确保了不会出现突然需要换出大量页的情况。这种情况将导致系统出现很长的等待时间,必须不惜一切代价防止。
2. 但内核在某些情况下,必须能够预期可能突然出现的严重内存不足,例如在通过伙伴系统(Linux分配内存的一种机制)分配一大块内存时,或创建缓冲区时。如果没有足够的物理内存可用来满足对内存的请求,内核必须尽快换出页,以期释放一些内存空间。在紧急情况下的换出操作,属于直接回收(direct reclamin)的一部分。

上面这两种方式的底层实现是相似的,只不过对于第二种方式来说如果内存不足的时候采取的措施更强硬一些。毕竟第一种方式只是例行检查,尽量保证内存使用量在一个健康的水平;而第二种方式则是在“有内存需要的时候采取的紧急措施“,所以方式会更强硬一些,譬如函数会一直等待内存数据写入到了磁盘上之后才返回、回收一些并不是那个“不经常使用”的内存。

如果内核无法满足对内存的请求,甚至在换出页以后也是如此,内么虚拟内存子系统(默认的选择)将通过OOM(out of memory,内存不足)killer了结束一个进程。虽然OOM killer有时候可能导致严重的损失,但比系统完全崩溃要好。如果在内存不足的情况下不采取措施,很可能导致系统崩溃。

交换与回写什么关系

上面说的叙述中可以看出交换回写(writeback,又做写回)其实是差不多的,都是在操作系统感受到内存空间不足的时候,腾出来内存空间的一种方式。区别的地方在于:
交换是针对那些由进程动态产生的、不存在对应的磁盘文件的数据;而回写则是有对应的磁盘文件的数据,还需要说明一下上面提到的根据 PC 中存放的地址去内存中取指令(也就是数据,也就是数据了)的操作过程中,如果指令不在内存中,那么指令一定是被写回到磁盘上去了。因为,一个进程的指令总是在创建进程的时候通过分析加载可执行文件读入到内存中的。(不考虑一些特殊的情况,譬如堆栈溢出攻击)

所以交换机制要做的很重要的一个事情就是图三步骤③中的,建立原来在内存中的数据到磁盘中的数据的映射。即给定一个进程的线性地址 L1 能够找到与之对应的磁盘逻辑地址 D1 (如果数据不再内存中的话)。从操作系统程序猿的角度去看,一个磁盘就相当于非常非常大的数组,磁盘的逻辑地址 D1 就相当于该数组的索引值,每个数组元素都是一个扇区的大小,这是磁盘寻址的最小单元,通常为 512byte
那么,步骤③更详细的叙述为 : 操作系统通过交换机制建立起来的映射,找到 L1 对应的 D1 。然后分配一块至少能够放得下一个扇区的内存,内存的物理地址为 P1 。下一步将二元组 (D1P1) 交给下层的虚拟文件层。虚拟文件层需要上层传递的这两个主要的参数,即可将磁盘逻辑地址 D1 中的数据读入到内存物理地址 P1 去。到此为止内核就完成了步骤③的工作。不了解虚拟文件层没有关系,只需要知道其上述功能即可;而且,也就是说交换机制不会涉及数据是如何写入到磁盘中去的,又是如何从磁盘中读出来的,这些都是文件系统和驱动的工作。

内核本身使用的内存页绝不会换出。如果这样做的话将会显著增加内核代码的复杂度。由于和其他用户进程相比内核不需要太多的内存,而且与付出的额外工作量相比,将内核页换出的潜在收益实在是太低了。

小小的总结

通过上面的叙述,应该基本上说清楚了换出机制是什么、在内核中的作用、属于哪个子系统。具体的实现请参照Linux内核三本经典书籍。


引用

文章的引用都已超链接的形式给出,此处就不一一罗列出来了,还有一部分是引自文章开始处推荐的书籍。侵删

这篇关于Linux SWAP内存交换机制基本概念的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036117

相关文章

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语