linux内存管理概览

参考来源:

首先有个宏观的认识:
内存是cpu的内存,在多cpu中使用的NUMA架构,一个或多个cpu就是一个node,再将node中包含的一部份内存中区分出zone,在zone中又使用多种策略进行内存页分配。

注意和inode区分,文件系统的inode是存在固态上的,而这里的node是cpu的内存

zone

由于一些硬件的缺陷

  • 有些硬件DMA必须对应特殊的地址
  • 一些架构内存的物理寻址比虚拟寻址范围大得多,存在永久不能映射的地址

这样,linux将每个node又会划分出多个zone管理多个页,用来在逻辑上进行一些分组(没有任何物理意义).

  • ZONE_DMA:其中的页可以使用DMA
  • ZONE_DMA32:类似于ZONE_DMA,但是只有32位的设备可以访问
  • ZONE_NORMAL:正常映射的页
  • ZONE_HIGHEM:并不能永久的映射到内核地址的空间
  • ZONE_MOVALBE:内部的页都是可以迁移的
  • ZONE_DEVICE:支持热插拔设备的非易失性内存

图中的ZONE分布不是物理意义上的分布

其中每个zone有可能会有自己的伙伴管理系统等方式管理着页。

同样先给一个页的大致概念:
每个页都会有 struct page结构描述。并且page与物理页有关,并不是虚拟页。通过这个结构来查看是否空闲,以及所有者是谁(进程、内核数据、静态代码、高速缓存等)。

一般来说页是4k,这是经过权衡和优化的结果,页太大会导致碎片化,页太小会导致页表寻址TLB的负担加重。但其实可以使用更大的内存页,只不过4k从上世纪默认延续下来了。

可能这时候会疑惑,为什么页的所有者还有可能是数据或者代码或者缓存,而不仅仅是某个进程。其实每个页在分配后,会由伙伴系统或者slab或者其他的方式统一管理,这时候这些页就存着不同的内容,而不仅仅是某个进程独有,有可能是每个进程的pcb在某一页中被slab统一的管理

为了后续更好的理解,这里先大致了解一下每个进程所占用的空间以及对应的内存管理的关系。
对于每个进程,其虚拟地址都是通过MMU向物理地址转换,如果发现进程所使用的物理地址不够了,那么就会触发 page fault ,从而向内核申请出物理页,然后做好映射后放回到MMU中,供进程使用。所申请的物理页可以被伙伴算法或者其他的算法管理,总之就是由内核做统一的管理。

内存分配管理方法

伙伴算法

处理页外碎片(大碎片), 每个页是4k,将页面数目凑成(4k 8k 16k...4M)大页分别用链表链接存放, 这样如果有页外碎片(连续n个页),就可以将这些碎片合成某一个大页然后放入对应链表等待分配

slab机制

处理内部碎片(小碎片), 基于高速缓存中字节缓存池(快 频繁小空间), 可以将小的空间优先塞到已经分配还没有填充的页中,其次是空的页中,最后如果没有空间了,就从物理页再取空间分配到缓存中,而且如果释放空间,也是会将分配了的页保留不返还回去

只有当内存紧缺或者显示的撤销高速缓存才会调用释放函数。

像pcb这种经常被使用的内存一般会用slab来做统一的管理。

分配方式

说了那么多概念上的东西,下面来具体看看内存分配的函数以及其作用。

分配页

获得页

下面这几个都是获得页

  • alloc_page(gfp_mask) 分配一页,返回页指针(page)
  • alloc_pages(gfp_mask, order) 分配2order个页,返回第一个页指针(连续分配的)
  • __get_free_page(gfp_mask) 分配一页,返回逻辑地址指针(addr)
  • __get_free_pages(gfp_mask, order) 分配2order个页,,返回第一个页逻辑地址指针(addr)

释放页

  • __free_pages
  • free_pages
  • free_page

内核完全信赖自己,如果传入错误的page或者地址,用了错误的order,那么系统就会崩溃。

以字节为单位分配

kmalloc

对于大页面会调用 alloc_page这种函数,但是对于字节为单位的分配,就会使用 kmalloc 这种函数来分配。

函数原型kmalloc(size_t, gfp_t) 返回的是分配内存块的指针,并且分配的内存区在物理上是连续的。

kmalloc会向slab申请,如果内存太大会向伙伴系统申请。如果slab和伙伴系统都缺页了,会进行OOM(out of memory),释放一些内存。或者调用 alloc_page 来获得新的页,然后再去分配

对应的释放函数 kfree

vmalloc

虚拟地址连续,物理地址不一定连续。

缺点:每个页都要映射,并且有可能造成TLB抖动

对应的释放函数 vfree

slab分配

创建高速缓存,slab内部都是同一种类型

  • kmem_cache_create(name, size, align, flags, 构造函数) 创建高速缓存(高速缓存名字,大小,用来对齐的偏移,选项,当新页追加时触发的构造函数)
  • kmem_cache_destory 撤销高速缓存

从创建的高速缓存中获得空间或者释放

  • kmem_cache_alloc(kmem_cache, gfp_t) 从高速缓存kmem_cache中返回一个对象的指针

如果没有页,会调用 kmem_getpages -> alloc_pages 并且gfp_t会依次传递,用来获得新页

  • kmem_cache_free 释放

gfp_mask标志

从上面几个函数可以看到会使用到 gfp_mask 标识来只是在哪里分配。可以分为三类

  • 行为标志:如何分配(是否睡眠、重新分配、启动IO等)
  • 区标志:在哪个区(zone)分配
  • 类型标志:(行为标志+区标志)的组合简化

对于malloc来说:常用的类型标志有

  • GFP_ATOMIC 不能睡眠
  • GFP_KERNEL 睡眠+运行刷新到硬盘

几种分配的选择

gfp_mask的选择:

  • 睡眠->GFP_KERNEL
  • 不睡眠->GFP_ATOMIC

分配函数的选择

分配方式 时机 注意事项
alloc_page 需要多页
想通高端内存分配
返回的是页指针
高端内存大概率没有映射需要kmap
kmalloc 物理地址连续
比较小
返回的是内存体的指针
vmalloc 虚拟地址连续 性能比kmalloc弱
TLB抖动
slab 创建和撤销很多大的数据结构
高速缓存
并不是创建和释放而是从链表里拿|放

__EOF__

本文作者alan
本文链接https://www.cnblogs.com/alanli07/p/18173952.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   LIalan  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示