Linux内存管理优化

简介: Linux内存管理优化

文件映射


文件映射通过映射虚拟内存的方式实现,在映射的时候进程访问的实际是文件对应的副本虚拟内存地址,既然访问虚拟内存位置可以完成文件的修改映射,那么直接访问物理内存也就是实际内存修改内容也是可行的。

通用:如果知道文件的具体地址,甚至可以直接定位到内存地址对于内容进行覆盖。


请求分页


进程向内核申请内存的通过请求分页的方式完成,之前提到过通过mmap的方式申请内存的方式虽然很方便,并且通常的内存分配方式有下面两种:

  • 物理内存的直接申请和分配,高效
  • 句柄分配的方式,也就是页表对于虚拟内存和实际内存映射之后再给进程

这两种分配方式都存在两个比较明显的问题,那就是分配的时候如果申请了却没有使用,会大量浪费,另外一次glibc一次需要超过进程的内存,可能出现一个很大的进程管理大量被申请未使用内存。

为了更好理解请求分页需要先理解分页的三种状态

  • 未分配页表和物理内存给进程。
  • 已分配页表但是未分配物理内存。
  • 已分配页表和物理内存。

为了解决分配浪费的问题,分配进程的内存仅使用一次分配方式,请求分页的核心是利用内核缺页中断的机制,当进程初次访问到已分配但是没有没有分配物理内存的空间,对于此时内核会进行缺页中断处理,同时给进程真正申请物理内存进行分配动作,这样可以保证每次分配内存的动作都是有效的。

这种方式也类似懒加载的方式,即可以保证分配动作运行,进程无感知缺页中断的情况,依然可以正常运行。

如果使用C语言按照请求分页的特点进行实验可以发现当内存没有使用的时候即使显示已经分配内存,但是实际可用物理内存没有变动。另外分配内存失败分为虚拟内存分配失败,物理内存分配失败,这是因为懒加载的设计导致的,另外虚拟内存不足不一定会导致物理内存不足,因为只要可用物理在分配时刻小于虚拟内存,那就是没法分配。


写时复制


写时复制是利用fork的函数提高虚拟内存分配效率,在文件系统的体现是update或者delete不会动原数据,而是用副本完成操作,当操作完成再更新引用,如果中间宕机断电,则用日志恢复状态即可。

在 Linux 系统的内存管理中调用 fork 系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是首先与父进程共用相同的页表,而当子进程或者父进程对内存页进行修改时才会进行复制 —— 这就是著名的 写时复制 机制。

写时复制的流程如下:

  • 在父进程调用fork的时候,并不是把所有内存复制给子进程 而是递交自己的页表给子进程。完成这一步如果子父进程只进行只读操作双方都会共享页表,但是一旦一方要改变数据,就会立马解除共享。
  • 在解除共享的时候会有如下操作
  • 由于写入失去权限,所以会出发缺页中断
  • 内核干预,执行缺页中断
  • 写入方的数据复制到另一处,并且把写入方的页表全部更新为新复制的内存并且赋予写入方写入权限,同时把之前共享的页表更新。而另一方则把这个刷新之后的页表重新连接即可。
  • 最后父子进程彻底写入权限和页表独立。但是之前解除共享的页表依然可以自由读写。

注意:fork调用的时候:并不会复制页表和内容,而是真正写入的时候会触发复制动作,这也是写时复制名字由来


交换内存


交换内存是Linux内核一种oom情况下的补偿机制,作用也是为了缓解内存溢出和不足的问题,交换内存的实现依靠的是虚拟内存的机制。简单理解就是在物理内存虽然不够,但是虚拟内存可以借用外部存储器也就是硬盘的一部分空间来充当物理内存使用,这一块分区叫做交换区,由于是借物理存储空间,这个操作也叫做换出。 另外如果借用的空间被释放则归还,这部分操作叫做换入,由于交换内存以页为单位,部分资料也叫页面调入和调出,都是一个意思。

成这一步如果只进行只读操作双方都会共享页表,但是一旦一方要改变数据,就会立马解除共享。

  • 在解除共享的时候会有如下操作
  • 由于写入失去权限,所以会出发缺页中断
  • 内核干预,执行缺页中断
  • 写入方的数据复制到另一处,并且把写入方的页表全部更新为新复制的内存并且赋予写入方写入权限,同时把之前共享的页表更新。而另一方则把这个刷新之后的页表重新连接即可。
  • 最后父子进程彻底写入权限和页表独立。但是之前解除共享的页表依然可以自由读写。

注意:fork调用的时候:并不会复制页表和内容,而是真正写入的时候会触发复制动作,这也是写时复制名字由来

另外交换内存很容易认为是一种扩充物理内存的美好方式,但是这里有一个本质的问题,那就是硬盘的访问速度和内存相比差的次方级别的差距。另外如果长期内存不足很容易导致交换内存不断的换入换出出现明显的性能抖动。

另外这类需要外部存储器的缺页中断在术语中被称之为硬性页缺失,相对的不需要外部存储器的页缺失是软性页缺失,虽然本质都是内核在触发和完整操作,但是硬性的缺失总归比软性缺失后果严重很多。

这里也要吐槽一下M1的各种偷硬盘缓存来提高性能的操作.....


多级页表


多级页表的设计核心是:避免把全部页表一直保存在内存中是多级页表的关键所在。特别是那些不需要的页表就不应该保留。

在X86-64架构当中,虚拟地址的空间大小约为128T,一个页的大小为4KB,页表的项目大小为8个字节。

所以一个进程的页表至少需要256GB内存!(8 * 128T / 4KB),但是我们都知道现在的电脑一般都是16GB内存为主,而32GB的内存虽然但是个人用的比较少。 那么系统应该如何维护页表?这就引入了多层页表来进行管理,多集页表可以从最简单的角度当作一个多级的指针看待,当然实际的多级页表一两句话是说不完的,这里我们可以大致理解多级页表是如何提升性能的?

首先我们可以思考,一个进程是否需要整个页表来管理内存?很显然是不需要的,这是引入多级页表的理由之一,可以发现绝大多数都不需要,比如一个进程需要12M空间,顶端需要4M,数据部分占用4M,底部又是一些堆栈内容和记录信息,在数据顶端上方和堆栈之间是大量根本没有使用的空闲区。

所以多级页表实际上就是大目录套小目录,和我们的一本书一样,小目录负责小的进程,而遇到比较大的进程就放到空闲页比较大的目录中完成分配操作,多级页表既可以高效的利用内存的同时,可以最大限度的减少页表本身的数据结构在内存的占用,同时上面的例子也可以发现绝大多数的进程其实根本不需要太大的页表进行维护和管理。

最后从网上的资料翻阅中发现一张下面的图,对于多级页表的理解有一定帮助:


网络异常,图片无法展示
|


最后X86_64 使用了4层的页表结构,直当理解就是四级指针,复杂程度可见一般。


标准大页


随着进程虚拟内存和页表的使用,进程使用的物理内存也会增加。

我们根据请求分页和写时复制的概念,可以发现当进程使用虚拟内存量增加会有一个明显的问题那就是在调用fork函数的时候会对于父子进程共享的页表进行拷贝,虽然这个拷贝动作不会占用物理内存,但是为进程复制操作需要拷贝一份完整的页表,所以当页表很大的时候,也会造成性能浪费。

为了解决这个问题,Linux提供了标准大页的机制,和他名字一样,就是比普通的页表更大的页,利用这种页表可以减小进程页表所需的内存使用量。

另外通过二级页表和标准大表可以有效的减少整个页表项的数量,最后对于标准大页我们只需要知道标准大页可以减少大量的虚拟内存进程的页表开支即可。


用法


C语言中使用mmap函数参数赋予 MAP_HUGETLB 标志,表示可以获取大页,但是更加常用的方式是使用程序允许使用使用标准大爷而不是这种手动切换的方式。

标准大页对于虚拟机和数据库等需要使用大量内存的应用程序是很有必要的,根据实际情况决定是否使用标准大页,通过这种设置可以减少这一类软件内存占用,还能提高fork效率。

透明大页透明大页是随着标准大页带来的附带特性,主要的作用是在连续的4KB页面如果符合指定条件就可以通过透明大页的机制转为一个标准大页,以及在不满足条件的时候会出现大页拆分为多个4KB页面的情况,所以这个机制


小结


这部分从文件映射的内容引申了Linux两个重要的机制:请求分页写时复制,目的本质上都是尽量减少进程对于内存的浪费,但是需要注意的是这两种方式都是使用了内核模式的系统中断机制来进行处理的,所以对于内核的性能以及稳定性要求非常高。

在之后的内容介绍了交换内存以及多级页表和标准大页几个内容,其中多级页表内部的细节非常的复杂,通常需要对于操作系统底层有比较熟悉的认知才能完全的了解这个页表的细节。

相关文章
|
23天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
61 4
|
21天前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
104 48
|
13天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
57 31
|
5天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
12天前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
24 2
|
25天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
58 9
|
21天前
|
缓存 Linux
如何检查 Linux 内存使用量是否耗尽?
何检查 Linux 内存使用量是否耗尽?
|
25天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
28天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
45 6
|
28天前
|
机器学习/深度学习 负载均衡 算法
深入探索Linux内核调度机制的优化策略###
本文旨在为读者揭开Linux操作系统中至关重要的一环——CPU调度机制的神秘面纱。通过深入浅出地解析其工作原理,并探讨一系列创新优化策略,本文不仅增强了技术爱好者的理论知识,更为系统管理员和软件开发者提供了实用的性能调优指南,旨在促进系统的高效运行与资源利用最大化。 ###