深入理解linux物理内存

目录

物理内存热插拔

从 CPU 角度看物理内存架构

内核如何管理 NUMA 节点

 NUMA  节点物理内存区域的划分

NUMA 节点的状态 node_states

 物理内存区域中的水位线

物理内存区域中的冷热页 

 内核如何描述物理内存页

匿名页的反向映射


物理内存热插拔

  • 物理热插拔阶段:这个阶段主要是从物理上将内存硬件插入(hot-add),拔出(hot-remove)主板的过程,其中涉及到硬件和内核的支持。

  • 逻辑热插拔阶段:这一阶段主要是由内核中的内存管理子系统来负责,涉及到的主要工作为:如何动态的上线启用(online)刚刚 hot-add 的内存,如何动态下线(offline)刚刚 hot-remove 的内存。

  • 但是这里会有一个问题,就是并非所有的物理页都可以迁移,因为迁移意味着物理内存地址的变化,而内存的热插拔应该对进程来说是透明的,所以这些迁移后的物理页映射的虚拟内存地址是不能变化的。

    这一点在进程的用户空间是没有问题的,因为进程在用户空间访问内存都是根据虚拟内存地址通过页表找到对应的物理内存地址,这些迁移之后的物理页,虽然物理内存地址发生变化,但是内核通过修改相应页表中虚拟内存地址与物理内存地址之间的映射关系,可以保证虚拟内存地址不会改变

    既然是这些不可迁移的物理页导致内存无法拔出,那么我们可以把内存分一下类,将内存按照物理页是否可迁移,划分为不可迁移页,可回收页,可迁移页。

    大家这里需要记住一点,内核会将物理内存按照页面是否可迁移的特性进行分类,笔者后面在介绍内核如何避免内存碎片的时候还会在提到

    然后在这些可能会被拔出的内存中只分配那些可迁移的内存页,这些信息会在内存初始化的时候被设置,这样一来那些不可迁移的页就不会包含在可能会拔出的内存中,当我们需要将这块内存热拔出时, 因为里边的内存页全部是可迁移的, 从而使内存可以被拔除。

从 CPU 角度看物理内存架构

UMA架构:

  1. 总线的带宽压力会越来越大,随着 CPU 个数的增多导致每个 CPU 可用带宽会减少

  2. 总线的长度也会因此而增加,进而增加访问延迟

在 NUMA 架构下,内存就不是一整片的了,而是被划分成了一个一个的内存节点 (NUMA 节点),每个 CPU 都有属于自己的本地内存节点,CPU 访问自己的本地内存不需要经过总线,因此访问速度是最快的。当 CPU 自己的本地内存不足时,CPU 就需要跨节点去访问其他内存节点,这种情况下 CPU 访问内存就会慢很多。

在 NUMA 架构下,任意一个 CPU 都可以访问全部的内存节点,访问自己的本地内存节点是最快的,但访问其他内存节点就会慢很多,这就导致了 CPU 访问内存的速度不一致,所以叫做非一致性内存访问架构。

 CPU 和它的本地内存组成了 NUMA 节点,CPU 与 CPU 之间通过 QPI(Intel QuickPath Interconnect)点对点完成互联,在 CPU  的本地内存不足的情况下,CPU 需要通过 QPI 访问远程 NUMA 节点上的内存控制器从而在远程内存节点上分配内存,这就导致了远程访问比本地访问多了额外的延迟开销(需要通过 QPI 遍历远程 NUMA 节点)。

内核如何管理 NUMA 节点

在内核中是如何将这些 NUMA 节点统一管理起来的?struct pglist_data 这样的一个数据结构来描述 NUMA 节点,在内核 2.4 版本之前,内核是使用一个 pgdat_list 单链表将这些 NUMA 节点串联起来的。在内核 2.4 之后的版本中,内核移除了 struct pglist_data 结构中的 pgdat_next 之指针, 同时也删除了 pgdat_list 单链表。取而代之的是,内核使用了一个大小为 MAX_NUMNODES ,类型为 struct pglist_data 的全局数组 node_data[] 来管理所有的 NUMA 节点。

typedef struct pglist_data {// NUMA 节点idint node_id;// 指向 NUMA 节点内管理所有物理页 page 的数组struct page *node_mem_map;// NUMA 节点内第一个物理页的 pfnunsigned long node_start_pfn;// NUMA 节点内所有可用的物理页个数(不包含内存空洞)unsigned long node_present_pages;// NUMA 节点内所有的物理页个数(包含内存空洞)unsigned long node_spanned_pages; // 保证多进程可以并发安全的访问 NUMA 节点spinlock_t node_size_lock;.............
}

 

 NUMA  节点物理内存区域的划分

NUMA 节点的状态 node_states

如果系统中的 NUMA 节点多于一个,内核会维护一个位图 node_states,用于维护各个 NUMA 节点的状态信息。 表示 NUMA 节点在某个时刻可以变为 online 状态,N_ONLINE 表示 NUMA 节点当前的状态为 online 状态。

 物理内存区域中的水位线

物理内存区域中的冷热页 

 内核如何描述物理内存页

  1. 一种是匿名页,匿名页背后并没有一个磁盘中的文件作为数据来源,匿名页中的数据都是通过进程运行过程中产生的,匿名页直接和进程虚拟地址空间建立映射供进程使用。

  2. 另外一种是文件页,文件页中的数据来自于磁盘中的文件,文件页需要先关联一个磁盘中的文件,然后再和进程虚拟地址空间建立映射供进程使用,使得进程可以通过操作虚拟内存实现对文件的操作,这就是我们常说的内存文件映射。

匿名页的反向映射

我们通常所说的内存映射是正向映射,即从虚拟内存到物理内存的映射。而反向映射则是从物理内存到虚拟内存的映射,用于当某个物理内存页需要进行回收或迁移时,此时需要去找到这个物理页被映射到了哪些进程的虚拟地址空间中,并断开它们之间的映射。

struct anon_vma_chain 结构通过其中的 vma 指针和 anon_vma 指针将相关的匿名页与其映射的进程虚拟内存空间关联了起来。从目前来看匿名页 struct page 算是与 anon_vma 建立了关系,又通过 anon_vma_chain 将 anon_vma 与 vm_area_struct 建立了关系。那么就剩下最后一道关系需要打通了,就是如何通过 anon_vma 找到 anon_vma_chain 进而找到 vm_area_struct 呢?这就需要我们将 anon_vma 与 anon_vma_chain 之间的关系也打通。 

我们知道每个匿名页对应唯一的 anon_vma 结构,但是一个匿名物理页可以映射到不同进程的虚拟内存空间中,每个进程的虚拟内存空间都是独立的,也就是说不同的进程就会有不同的 VMA。

不同的 VMA 意味着同一个匿名页 anon_vma 就会对应多个 anon_vma_chain。那么如何通过一个 anon_vma 找到和他关联的所有 anon_vma_chain 呢?找到了这些 anon_vma_chain 也就意味着 struct page 找到了与它关联的所有进程虚拟内存空间 VMA。

我们重点来看 struct anon_vma 结构中的 rb_root 字段,struct anon_vma 结构中管理了一颗红黑树,这颗红黑树上管理的全部都是与该 anon_vma 关联的 anon_vma_chain。我们可以通过 struct page 中的 mapping 指针找到 anon_vma,然后遍历 anon_vma 中的这颗红黑树 rb_root ,从而找到与其关联的所有 anon_vma_chain。

到目前为止,物理内存页 page 到与其映射的进程虚拟内存空间 VMA,这样一种一对多的映射关系现在就算建立起来了。 

 vm_area_struct 表示的只是进程虚拟内存空间中的一段虚拟内存区域,这块虚拟内存区域中可能会包含多个匿名页,所以 VMA 与物理内存页 page 也是有一对多的映射关系存在。而这个映射关系在哪里保存呢?

大家注意 struct anon_vma_chain 结构中还有一个列表结构 same_vma,从这个名字上我们很容易就能猜到这个列表 same_vma 中存储的 anon_vma_chain 对应的 VMA 全都是一样的,而列表元素 anon_vma_chain 中的 anon_vma 却是不一样的。内核用这样一个链表结构 same_vma 存储了进程相应虚拟内存区域 VMA 中所包含的所有匿名页。

到现在为止还缺关键的最后一步,就是打通匿名内存页 page 到 vm_area_struct 之间的关系,首先我们就需要调用 alloc_zeroed_user_highpage_movable 方法从伙伴系统中申请一个匿名页。当获取到 page 实例之后,通过 page_add_new_anon_rmap 最终建立起 page 到 vm_area_struct 的整条反向映射链路。 

如果当前物理内存页 struct page 是一个匿名页的话,那么 mapping 指针的最低位会被设置为 1 , 指向该匿名页在进程虚拟内存空间中的匿名映射区域 struct anon_vma 结构(每个匿名页对应唯一的 anon_vma 结构),用于物理内存到虚拟内存的反向映射。

如果当前物理内存页 struct page 是一个文件页的话,那么 mapping 指针的最低位会被设置为 0 ,指向该内存页关联文件的 struct address_space(页高速缓存)。pgoff_t index 字段表示该内存页 page 在页高速缓存中的 index 索引,也表示该内存页中的文件数据在文件内部的偏移 offset。偏移单位为 page size。

内核可以通过这个技巧直接检查 page 结构中的 mapping 指针的最低位来判断该物理内存页到底是匿名页还是文件页

struct page {struct address_space *mapping; pgoff_t index;  // 表示该 page 映射了多少个进程的虚拟内存空间,一个 page 可以被多个进程映射atomic_t _mapcount
}

经过本小节详细的介绍,我想大家现在已经猜到 _mapcount 字段的含义了,我们知道一个物理内存页可以映射到多个进程的虚拟内存空间中,比如:共享内存映射,父子进程的创建等。page 与 VMA 是一对多的关系,这里的 _mapcount 就表示该物理页映射到了多少个进程的虚拟内存空间中。

内存回收的关键是如何实现一个高效的页面替换算法 PFRA (Page Frame Replacement Algorithm) ,提到页面替换算法大家可能立马会想到  LRU (Least-Recently-Used) 算法。LRU 算法的核心思想就是那些最近最少使用的页面,在未来的一段时间内可能也不会再次被使用,所以在内存紧张的时候,会优先将这些最近最少使用的页面置换出去。在这种情况下其实一个 active 链表就可以满足我们的需求。

四种 LRU 链表(匿名页的 active 链表,inactive 链表和文件页的active 链表, inactive 链表)之外,内核还有一种链表,比如进程可以通过 mlock() 等系统调用把内存页锁定在内存里,保证该内存页无论如何不会被置换出去,比如出于安全或者性能的考虑,页面中可能会包含一些敏感的信息不想被 swap 到磁盘上导致泄密,或者一些频繁访问的内存页必须一直贮存在内存中。

工作原理如下:

  1. 首先 inactive 链表的尾部存放的是访问频率最低并且最少访问的页面,在内存紧张的时候,这些页面被置换出去的优先级是最大的。

  2. 对于文件页来说,当它被第一次读取的时候,内核会将它放置在 inactive 链表的头部,如果它继续被访问,则会提升至 active 链表的尾部。如果它没有继续被访问,则会随着新文件页的进入,内核会将它慢慢的推到  inactive 链表的尾部,如果此时再次被访问则会直接被提升到 active 链表的头部。大家可以看出此时页面的使用频率这个因素已经被考量了进来。

  3. 对于匿名页来说,当它被第一次读取的时候,内核会直接将它放置在 active 链表的尾部,注意不是 inactive 链表的头部,这里和文件页不同。因为匿名页的换出 Swap Out 成本会更大,内核会对匿名页更加优待。当匿名页再次被访问的时候就会被被提升到 active 链表的头部。

  4. 当遇到内存紧张的情况需要换页时,内核会从 active 链表的尾部开始扫描,将一定量的页面降级到  inactive 链表头部,这样一来原来位于 inactive 链表尾部的页面就会被置换出去。

内核在回收内存的时候,这两个列表中的回收优先级为:inactive 链表尾部 > inactive 链表头部 > active 链表尾部 > active 链表头部。

参考文献

一步一图带你深入理解 Linux 物理内存管理

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

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

相关文章

阿里云地域和可用区分布表

阿里云服务器地域和可用区有哪些?阿里云服务器地域节点遍布全球29个地域、88个可用区,包括中国大陆、中国香港、日本、美国、新加坡、孟买、泰国、首尔、迪拜等地域,同一个地域下有多个可用区可以选择,阿里云服务器网分享2023新版…

SpringCloud微服务(二)网关GateWay、Docker、Dockerfile、Linux操作超详细

目录 统一网关GateWay 搭建网关服务的步骤 1、引入依赖 2、编写路由配置及nacos地址 路由断言工厂Route Oredicate Factory 路由过滤器配置 全局过滤器GlobalFilter 过滤器执行顺序 跨域问题处理 Docker ​编辑 Docker与虚拟机 镜像和容器 Docker的安装 启动docke…

threejs入门

个人博客地址: https://cxx001.gitee.io 前言 随着HTML5的发布,我们可以通过WebGL在浏览器上直接使用显卡资源来创建高性能的二维和三维图形,但是直接使用WebGL编程来创建三维场景十分复杂而且还容易出问题。而使用Three.js库可以简化这个过程&#xff…

Qt QGraphicsScene、QGraphicsView类实现仪表盘

Qt QGraphicsScene、QGraphicsView类实现仪表盘 【1】UI界面设计【2】效果【3】QGraphicsScene简介【4】QGraphicsEllipseItem简介【5】QGraphicsPolygonItem简介【6】QGraphicsLineItem简介【7】QGraphicsView简介【8】仪表源码头文件源码 【1】UI界面设计 【2】效果 【3】QGr…

分布式系统消息通信技术:MOM与RPC

一、中间件 什么是中间件 中间件(Middleware)是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台…

uniapp 一键登录

官网文档地址https://uniapp.dcloud.net.cn/univerify.html 一、开发前准备 1、需要先开通uni一键登录服务 开通成功后会得到 apiKey、apiSecret。这2个信息,后续需要配置在uniCloud的云函数里。同时注意保密,这2个信息也是计费凭证 2、开通uniCloud服…

利用spleeter库实现人声和音乐分离(踩过的坑及解决方法)

0:起因 事情的起因是:想用一首歌的伴奏剪视频,但找遍各大平台,都只有原曲,没有伴奏。能找到的进行人声和背景音乐分离的软件都要收费,最后决定用spleeter库,尝试进行音频分离。 1:…

第四课—大学英语四六级备考—听力专项

Key Words 1.monarch n.君主政治 非常抱歉误解了您的问题。以下是关于"monarch"这两个意义的常见用法、造句和固定搭配的例子: 1. Monarch(君主): - 造句: - The monarch of the country made an…

Kubernetes Pod篇

Pod基础概念: Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的,例如,用于管理Pod运行的S…

RocketMQ_高级功能

目录 一、消息存储 1、存储介质以及性能对比 2、消息的存储和发送 3、消息存储结构 4、刷盘机制 二、高可用性机制 1、消息消费高可用 2、消息发送高可用 3、消息主从复制 三、负载均衡 1、Producer负载均衡 2、Consumer负载均衡 四、消息重试 1、顺序消息的重试…

串口通信-Modbus-rtu协议例程(51实现)

/*务必通过ISP通讯&#xff0c;烧录时&#xff0c;选11.0592Mhz进行烧录&#xff0c;否则数据会乱码modbus-rtu 处理过程主机数据接收(超时机制&#xff0c;不定长接收)->数据校验码判断-> */ #include <STC15F2K60S2.H> #include "string.h" typedef un…

CentOS 8安装Oracle 19c rpm包

一、环境 centos 8 oracle 19c rpm安装 二、安装前准备 1、创建用户组 groupadd oinstall groupadd dba useradd -g oinstall -G dba oracle passwd oracle2、安装依赖包 如果yum找不到安装包&#xff0c;可以去下载 https://centos.pkgs.org/ 点击跳转 缺少的依赖包可…