linux内存分配管理
一、前言
作为从事与C/C++程序开发人员,我们一直需要很好的管理内存,申请和释放;可能很多只知道使用malloc、new去申请,使用free、delete去释放,但是,去根究其内部的原理,可能就不是很清楚了;
这里主要对linux平台的内存管理进行一下总结和梳理;
二、内存申请释放
Linux虚拟内存管理相关概念请移步至详解linux虚拟内存原理;
这里主要说说的是我们的内存是怎么申请的:
在C/C++中,使用malloc
进行内存分配,C++使用的new
,但是其底层也是使用malloc
;在系统调用上面,一般使用的是brk()
和mmap()
函数;
我们说一说这两个函数的使用场景:
brk():
当内存分配使用小于
128K
对于小于
128K
的小块内存,系统会使用brk()
来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。**注:**使用
brk()
分配内存时,只分配虚拟空间,不对应物理内存,只有第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后在虚拟地址上建立映射关系;回收内存
申请内存使用完毕之后,内存并不是真正的释放掉了,而是只回推了**_edata**指针,内存可以被重复利用;
brk分配的内存需要等到高地址内存释放以后才能释放brk分配的内存需要等到高地址内存释放以后才能释放,所以会产生内存碎片;
但是当最高地址空间超过了
128k
,就会执行trim
(内存紧缩)操作;
mmap():
大于
128K
大块内存(大于
128K
),则直接使用内存映射mmap()
来分配,也就是在文件映射段找一块空闲内存分配出去。注意:这个和
brk()
是一样的,只有第一次使用的时候才会真正分配物理内存;回收内存
mmap
回收内存时是直接释放归还,不会进行重复利用,所以这种会导致每次mmap
都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。这也是malloc
只对大块内存使用mmap
的原因。
参考:Linux内存分配小结--malloc、brk、mmap
三、linux内存管理
伙伴算法
定义:由一个母实体分成的两个各方面属性一致的两个子实体,这两个子实体就处于伙伴关系。在操作系统分配内存的过程中,一个内存块常常被分成两个大小相等的内存块,这两个大小相等的内存块就处于伙伴关系
伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为
1M
大小,而允许的最小块为64K
,那么当我们申请一块200K
大小的内存时,就要先将1M
的块分裂成两等分,各为512K
,这两分之间的关系就称为伙伴,然后再将第一个512K
的内存块分裂成两等分,各位256K
,将第一个256K
的内存块分配给内存,这样就是一个分配的过程;伙伴系统特点
1)两个块大小相同;2)两个块地址连续;3)两个块必须是同一个大块中分离出来的;
作用
伙伴系统是以page为单位进行操作的,一般管理的是大内存分配
具体详细参考:Linux伙伴系统(一)--伙伴系统的概述
- slab 分配器
linux
内核中会采用伙伴算法进行内存分配管理,但是分配的内存区是以页框为基本单位的。对于内核中小块连续内存的请求,比 如说几个字节或者几百个字节,如果依然分配一个页框来来满足该请求,这样的话就会有内存碎片产生;
定义
slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象 放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分 配器的高速缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分 配器,内核将花费更多的时间去分配、初始化以及释放一个对象。
slab分配器有以下三个基本目标:
1.减少伙伴算法在分配小块连续内存时所产生的内部碎片;
2.将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销。
3.通过着色技术调整对象以更好的使用硬件高速缓存;
参考:Linux内存管理中的slab分配器
作用
- 高速缓存
- 分配小块内存
具体详细参考:【Linux 内核】内存管理(三)slab分配器
说明:这是linux中比较重要的两个内存管理的方式,我对这块也只是了解状态,并没有深入去研究,这里只是简单概括一下,以后有时间细细研究一下,现在大概知道这个原理和概念就行了;
四、回收内存
那么上面说了内存分配相关的内容,那么,如果内存出现了紧张
,那系统自己会怎么办?
其实,linux自己也做了一套机制,来进行内存回收;
回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;
原理
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
实现
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
新数据插入到链表头部;
每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
当链表满的时候,将链表尾部的数据丢弃。
分析
【命中率】
当存在热点数据时,
LRU
的效率很好,但偶发性的、周期性的批量操作会导致LRU
命中率急剧下降,缓存污染情况比较严重。【复杂度】
实现简单。
【代价】
命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。
Swap:回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;
回收不常访问的内存时,会用到交换分区,也称为Swap。
Swap 其实就是把一块磁盘空间当成内存来用。它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入),Swap 把系统的可用内存变大了。
通常只有在内存不足时,才会发生 Swap 交换。
相对于内存来说,磁盘读写的速度很慢,所以Swap 会导致严重的内存性能问题。
OOM:杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。
oom内核的一种保护机制,它表示“内存用完了”。它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:
- 一个进程消耗的内存越大,oom_score 就越大;
- 一个进程运行占用的 CPU 越多,oom_score 就越小。
这样,进程的 oom_score 越大,代表消耗的内存越多,也就越容易被 OOM 杀死,从而可以更好保护系统。
当然,为了实际工作的需要,管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。
oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
五、查看内存命令
一般我们会使用free
来查看内存,当然,我们还可以使用ps/top
等命令
先看一下free
命令吧
root@iZuf67on1pthsuih96udyfZ:~/# free
total used free shared buff/cache available
Mem: 953036 388052 65808 2860 499176 377200
Swap: 0 0 0
简单的看一下每个字段的意义:
total
是总内存大小;used
是已使用内存的大小,包含了共享内存;free
是未使用内存的大小;shared
是共享内存的大小;buff/cache
是缓存和缓冲区的大小;available
是新进程可用内存的大小。
这就是大概是linux内存相关的一些知识点,这里写的比较乱,相当于做个笔记了~~~
想了解学习更多C++后台服务器方面的知识,请关注:微信公众号:====CPP后台服务器开发====
扫码CPP后台服务器开发转载是一种动力 分享是一种美德