linux内核系列--内存

本文探讨了x86架构下Linux内核的内存管理,包括虚拟内存管理、用户空间内存分配器和物理内存分配器的作用。通过内存管理架构的介绍,解答了为何需要虚拟内存、用户空间内存分配器和物理内存分配器的问题,并介绍了内存操作流程和实际应用场景,如内存水位线和OOM killer机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

​ 内存管理不是纯软件层面的东西,还要配合相关硬件,硬件如cpu架构,如cpu有x86、RAM、PowerCPU等。不同cpu架构下的内核管理架构也会存在差异。本文主要探讨的是x86架构下的linux内核的内存管理。这点需要明确。

​ 但有些知识点是超越cpu架构存在的,比如虚拟化内存管理的概念,因为虚拟化内存管理引入的页表映射,TLB等概念。这些是属于操作系统基本原理范畴。

​ 关于操作系统基本原理,可以看雷姆兹·H.阿帕希杜塞尔等写的《操作系统导论》。

​ 关于linux内核下的内存管理,可以看余华兵的《Linux内核深度解析》。

​ CSDN博主–bin的技术小屋–的系列文章也写的很漂亮,可以参考:

https://blog.csdn.net/weixin_47282449/category_12000400.html?spm=1001.2014.3001.5482

linux内存管理整体架构

在这里插入图片描述

​ 如图,整个内存管理的架构为:程序—>用户空间内存分配器----->虚拟内存管理------>物理内存分配器---->硬件(物理内存 + cpu中的内存管理单元等)。

解答几个问题

1、为什么需要虚拟内存管理

​ 这个是操作系统基本原理的范畴,详细可以看雷姆兹·H.阿帕希杜塞尔等写的《操作系统导论》。

​ 简单来说就是共享 、安全。

​ 计算机是多进程多用户的,每个进程都需要操作内存,如果每个进程都直接操作物理内存,那么可能发生A进程读取了B进程的数据(不隔离),甚至修改了B进程的数据,那我写个恶意程序到处修改内存,电脑直接就废了,什么都跑步起来。

​ 于是需要虚拟内存,物理内存只有内核可以操作,而程序看到的内存是内核虚拟内存模块虚拟出来的,这块内存在程序看来是独占的,程序可以自由用。而从内核层面在分配物理内存时也保证了A程序和B程序在使用物理内存时互相隔离。

2、为什么需要用户空间内存分配器

​ 详细信息可以参考:https://blog.csdn.net/tjcwt2011/article/details/85319371

​ 简单来说就是提高效率、降低内存碎片。

​ 内存分配器主要提供了一些分配释放算法,从而更好的满足程序对内存分配和释放需求。

​ 首先程序的内存需求大多是碎片化比较重的,比如计算a + b,定义变量的时候要申请小内存,结果存储也要申请小内存。如果每次申请都需要去内核申请,需要走系统调用,要从用户态切内核态,是花费比较大的。内存分配器一次性向内核申请一大块内存,当程序需要的时候直接从大块内存进行分配,就不要系统调用,从而提高了效率。

​ 另外就是回收,程序在释放内存的时候,是还给内存分配器,内存分配器通过自己的内部算法确定如何处理----回归自己管理的内存池子里还是返回给物理内存—如此便可以更好的降低内存碎片。

​ 应用程序可以向用户空间内存分配器申请内存,通过调用库函数malloc/free,也可以绕过用户空间内存分配器,直接向内核申请内存,通过内核级别的系统调用接口,如mmap/munmap。

3、为什么需要物理内存分配器

​ 参考:https://www.zhihu.com/question/391044480/answer/2970834148?utm_id=0 灵剑的回答。

​ 有几个场景是一定需要直接使用物理内存的,比如虚拟内存页表本身的管理,因为必需先有页表才能有虚拟内存,所以不能用虚拟内存来管理页表。又比如硬件直接使用的内存(典型如DMA)等,需要在物理连续的内存。此外内核的内存需求可能会多样化,有些需要申请页,有些需要申请块。

4、虚拟内存管理是怎么运作的(内存映射)

​ 参考:https://blog.csdn.net/weixin_47282449/article/details/131856313

在这里插入图片描述

以下说明基于大方向,细节层面不严谨。

​ 1、每个程序视角下的虚拟内存都是独占的全部内存,比如物理内存7G,程序看到的虚拟内存就有7G空间。

​ 2、内存(物理 + 虚拟)是分页管理的。每页固定大小,如4K。

​ 3、每个程序有自己的页表,页表是一块连续的物理内存。

​ 4、每个虚拟页在页表里都有对应的页表项,不管这个虚拟页是否被使用,对于已经使用的虚拟页,其对应的页表项内容为其映射的物理地址,对于为使用的虚拟页,其页表项内容为空(但空间还是要占着)。

​ 5、每个页表项是固定的大小,页表项的顺序与逻辑页的顺序一致,及第一个虚拟页对应第一个页表项,因此虚拟页6对应的页表项可以通过公式 (页表初始地址 + 6 * 单个页表项大小) 直接得到。一个程序的页表大小也可以通过公式 **(物理内存大小 / 页大小 * 单个页表项大小)**得到。

​ 6、页表项到物理内存页的映射由内核完成,内核保证非共享的情况下,同一物理内存页不会映射到不同的程序的页表,从而实现了隔离。并且物理页的映射在真正使用的时候才完成,所以物理内存能被多个程序所共享。

5、MMU、TLB是做什么用的

​ 都是为了加快地址转化(虚拟地址–>物理地址)的速度设计的硬件。

​ MMU就是做虚拟地址转化成物理地址的硬件。程序每次申请虚拟地址,内核通过程序的页表找到对应的物理地址,这个过程计算简单,且使用频繁,为了加快速度,设计了在cpu里做了这个硬件模块。

​ TLB是页表项的缓存,因为每次转化都需要把页表项放到cpu,还是太慢了,根据局部原理,加入了这个缓存,让MMU做转化的时候,尽量的可以走缓存,就减少了内存的访问次数,从而加快地址转化的效率。

内存操作的几个流程

1、程序读取数据流程

在这里插入图片描述

2、申请内存

在这里插入图片描述

3、释放内存(不说了)

具体应用

1、常用用户空间分配器对比

ptmalloc: glibc默认提供的分配器,一般情况够用了。高并发情况下性能较差,碎片化也比较重。mysql默认使用这个。

jemalloc:facebook开源的分配器,高并发情况下性能最好,占用内存最高,线程数量固定,不会频繁创建退出的情况下使用。redis默认使用这个。

tcmalloc:google开源的分配器,高并发情况下性能较好,与jemalloc没太大差别,内存占用比jemalloc稍低,mongodb默认使用这个。

参考:https://blog.csdn.net/weixin_39094034/article/details/123465404

2、内存水位线

具体查看:https://blog.csdn.net/weixin_47282449/article/details/127980437

内核定义了三个水位线,如下:

WATERMARK_HIGH: 高水位线,回收内存时候使用。

WATERMARK_LOW: 当剩余可用内存小于LOW值时,表明系统内存已经很紧张了,当完成当前的分配任务后,就会发起异步内存回收,直到剩余内存大于WATERMARK_HIGH为止。

WATERMARK_MIN:当剩余可用内存小于MIN值时,表示系统内存已经很危急了,挂起当前分配任务,直接进行内存回收。

水位线的调整可以通过sysctl调整以下两个参数实现。

/proc/sys/vm/min_free_kbytes

/proc/sys/vm/watermark_scale_factor

3、OOM killer

当内存严重不足的时候,页分配器在多次尝试直接页回收失败以后,就会调用内存耗尽杀手(OOM killer, OOM是“Out of Memory”的缩写),选择进程杀死,释放内存。

OOM的行为受到如下参数控制,且不可关闭。

/proc/sys/vm/oom_kill_allocating_task

/proc/sys/vm/oom_dump_tasks

/proc/sys/vm/panic_on_oom

/proc//oom_score_adj

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值