【Linux内核】内存管理——malloc的内存分配

转载请注明: https://www.cnblogs.com/Ethan-Code/p/16618880.html

malloc分配内存的方式

学C语言时我们知道,malloc是动态内存分配,也就是在程序运行执行到malloc函数时才会从堆上分配指定的内存大小。

首先要知道Linux的把内存分为6个区:栈区、文件映射区、堆区、BSS区、数据区、代码区。

分区只在虚拟内存上进行逻辑划分,物理内存上不存在逻辑分区的概念。

使用malloc时,会从两个区分配内存,分别是文件映射区堆区

  • 当申请的内存小于 128K 时,会通过 brk() 系统调用,从堆区分配内存。
  • 当申请的内存大于 128K 时,会通过 mmap() 系统调用,从文件映射区分配内存。

下面讲两种分配方式的区别。

brk分配内存

使用 brk() 分配内存时,内核只是把 brk 堆顶指针向上移动,也就是扩大堆空间。并且不会立即建立物理内存到虚拟内存堆区的映射,只有在访问新分配的内存时,发生缺页异常才会进行映射。

使用 free() 释放堆内存时,若要释放的内存不在堆顶,则内核不会立即释放内存,而是进行内存回收,标记这部分内存为空闲,也不会取消已经建立好的内存映射,这样做的目的是下次 malloc 时可以直接使用这块空闲内存,从而减少系统调用次数。但是这种方式就会存在一个问题,这部分回收但没有释放的内存会产生内存碎片

因此才会设置一个阈值 128K,申请低于128K时才会采用 brk 系统调用,以免产生太大的内存碎片。

当申请的内存比较小(<128K)时,例如 malloc(1),内核会把 brk 堆顶指针移动一段大于1的空间。一方面,需要在这部分内存的前面添加大小为0x10(16Bytes)的内存作为控制信息(可以提供给free要释放的内存范围);另一方面,因为 brk() 的机制,操作系统会预留更多的内存提供给后续的 malloc 申请使用,从而减少系统调用的次数,提高效率。

在32位Linux环境下malloc小于128K的内存总是会划分出 132K 的heap空间。132K的由来如下:132K = 128K + 4K 组成。原因是:当堆上已有的空闲内存不足以提供malloc申请的大小时,brk系统调用会将brk堆顶指针一次性上移128K,同时还要留出保存控制信息的内存大小,因此会略大于128K,再加上内存分页按4K对齐,因此brk堆顶指针是向上移动了132K。

除非malloc时堆上已经有足够的空闲内存,才不会移动brk堆顶指针,而是直接根据申请的内存大小将空闲内存的首地址返回,剩余的内存则继续作为空闲内存留在堆上。

另外,在 free 时,如果堆顶存在大于 128K 的空闲内存,则会触发内存紧缩(trim),将超过 128K 的空闲内存直接释放掉,剩余128K留给下次malloc分配使用。

上述的“空闲内存”,其实在内核中是由ptmalloc内存池管理的,使用边界标记法,将这些缓存在内存池的内存分为很多个块,每个块叫做一个chunk,并且每个chunk都有16Bytes的控制信息,用来描述chunk的大小以及前后的chunk信息,ptmalloc根据chunk的大小插入到不同的bin链表上,后续有时间专门写一篇malloc底层调用的流程。

可以参考:https://blog.nowcoder.net/n/de03980b2ab746ccad65fcb38bee59c3

mmap分配内存

当申请较大内存时,会使用 mmap() 分配内存,不管申请的内存多大,只要在其可用虚拟地址空间内,内核都会分配给进程。由于mmap()分配方式没有内存池缓存机制,因此每次分配的内存,当进程进行读写时都会触发缺页中断,内核会从文件映射区划分一块物理内存映射到虚拟内存上。并且,mmap()申请的内存在free时会完全的归还给操作系统,取消已建立的内存映射。

所以,频繁的使用 mmap 进行动态内存分配,对于CPU性能影响是比较大的,因为没有内存池缓存,每一次访问申请的内存时都会触发缺页中断,频繁的使用系统调用从用户态进入内核态对CPU来说开销比较大的。

因此,设置128K的阈值,低于128K的内存分配采用具有内存池缓存机制的 brk 方式,可以减少缺页中断、系统调用的次数。高于128K时采用匿名内存映射区的mmap方式, 避免产生太大的内存碎片。

mmap 系统调用的另一个用途就是进行文件映射。

文件一般都是通过一系列文件IO操作系统调用来访问,用open打开文件,讲磁盘文件加载到内存中,然后通过read,write进行读写,最后close关闭文件。

如果读写次数比较多的话会频繁的发生系统调用,因此为了提升性能,可以在open打开文件后使用mmap将磁盘文件直接映射到虚拟内存,然后只要对这段映射的虚拟内存进行读写访问,CPU就会触发缺页中断,把磁盘文件加载到主存上并与虚拟内存建立映射。之后,无论文件是否已被close,只要读写这段内存,系统会自动把脏页回写到磁盘中,相当于完成了read,write等文件操作,不再需要通过系统调用的状态切换这个过程。

可以参考:https://blog.nowcoder.net/n/850befe7c86d446ab73c89c5f18d8dcb?from=nowcoder_improve

访问free后的内存

当使用了 free 掉的内存后会发生什么?

  • 若 free 的是 brk 堆内存,由于堆上的内存在 free 后不会立即归还给操作系统,程序会继续执行,因此会造成潜在的 UAF(Use After Free) 问题。
  • 若 free 的是堆顶的内存,并且堆顶的空闲内存大于128K,使用超过128K的内存则会直接引发 段错误(Segment Fault)。
  • 若 free 的是 mmap 映射区内存,由于文件映射区的内存在 free 后会立即归还并且取消内存映射,再次访问该内存会引发 段错误(Segment Fault) 问题。

好文推荐:https://www.cnblogs.com/sky-heaven/p/10005642.html
https://xiaolincoding.com/os/3_memory/malloc.html#malloc-%E6%98%AF%E5%A6%82%E4%BD%95%E5%88%86%E9%85%8D%E5%86%85%E5%AD%98%E7%9A%84

猜你喜欢

转载自blog.csdn.net/weixin_45636061/article/details/127184771