linux内存管理教程,Linux内存管理学习笔记

一.内存

内存又称为主存,是CPU能够直接寻址的存储空间,由半导体制成。内存的特点是存取速度快。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。

二.物理内存和虚拟内存

物理内存:在应用中,真实地插在主板内存槽上的内存条。从本质上来说,物理内存是代码和数据在其中运行的窗口。

虚拟内存:使程序认为它拥有的连续的可用的内存(一个连续完整的内存空间),而实际上,它通常是被分割成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

三. 为什么要使用虚拟内存

在早期的计算机中,要运行一个程序,是会将这些程序全部装入内存,程序都是在内存上运行的,也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运行多个程序时,必须保证这些程序应用到的内存总量要小于计算机实际物理内存的大小。但是这种简单的内存分配策略问题很多:

问题1:进程地址空间不隔离,没有权限保护。由于程序都是直接访问物理内存,所以一个进程可以修改其他进程的内存数据,甚至修改内存地址空间中的数据;

问题2:内存使用效率低。当内存空间不足时,要将其他程序暂时拷贝到硬盘,然后将新的程序装入到内存中运行。由于大量的数据装入装出,内存使用效率会十分低下;

问题3:程序运行的地址不确定。因为内存地址是随机分配的,所以程序运行的地址也是不确定的。

2.为了解决上述问题,人们想到了一种变通的方法,就会增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,让由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样只要操作系统处理虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有成蝶,就可以达到内存地址空间隔离的效果。

四. 虚拟内存的实现

每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址。

虚拟地址可通过每个进程上的页表(在每个进程的内存虚拟地址空间)与物理地址进行映射,获得真正物理地址。

如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果物理此时物理内存耗尽,则根据内存替换算法淘汰部分页面至物理内存中。

当进程申请内存时,linux内核实际上只分配了一个虚拟内存(线性地址),并没有分配实际的物理内存。只要当程序正在使用这块内存时,才会分配物理内存。这就叫做延迟分配和请页机制。释放内存时,先释放线性区对应的物理内存,然后释放线性区;请页机制,将物理内存的分配延后了,这样才是充分利用了程序的局部性原理,节约内存空间,提高系统吞吐;就是说一个函数可能在物理内存中呆了一会,用完就被清除出去了,虽然其虚拟地址空间还在。

虚拟内存是将系统硬盘空间和系统实际内存联系在一起供进程使用,给进程提供一个比内存大得多的虚拟空间。当程序运行时,只要把虚拟地址空间的一小部分映射到内存,其余都存储在硬盘上。当被访问的虚拟地址不在内存时,则说明该地址未被映射到内存,而是被存储在硬盘上,因为将这部分地址调入到内存;;同时,当系统内存紧张时,也可以把当前不用的内存与磁盘空间交换。这部分交换空间就是swap分区。

五.虚拟内存空间布局

32位系统有4G的虚拟地址空间,其中高1G:0xc0000000-0xffffffff是内核空间,低3G:0x08048000-0xbfffffff是用户空间。(0x00000000-0x08048000是未用区域,是为了拓展使用?)。其空间布局如下:

8f4bbec24310cf85fa9b34fe7fefc6e2.png

从地址到高地址分别为:

1. 只读段:该部分空间只能读,不能写;(包括代码段、rodata段(C常量字符串和#define定义的常量))

2. 数据段:保存全局变量,静态变量的空间;

3. 堆:就是平时说的动态内存,malloc/new大部分来源于此。其中堆顶的位置可通过函数brk和sbrk进行动态调整;

4. 文件映射区域:如动态库、共享内存等映射物理内存空间的内存,一般是mmap函数所分配的虚拟地址空间;

5. 栈:用于维护函数调用的上下文空间,一般是8M,可通过ulimit -s查看;

6. 内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。

其中%esp执行栈顶,往低地址方向变化;;brk/sbrk函数控制堆顶_data往高地址方向变化。

64位linux一般使用48位来辨识虚拟地址空间,40位表示物理地址,可通过 cat 、proc/cpuinfo来查看:

7f7631ec80836c699afa8e991140a58d.png

其中,0x0000000000000000~0x00007fffffffffff表示用户空间,0xFFFF800000000000~0xFFFFFFFFFFFFFFFFF表示内核空间,共提供256TB(2^48)

六. 内存分配原理

从操作系统角度来看,进程分配内存有两种方式,分别有两个系统调用完成,brk和mmap(匿名文件映射,不考虑共享内存)

1. brk是将数据段(.data)的最高地址_data往高地址上堆;

2. mmap是在进程的虚拟地址空间中(堆和栈中间,成为文件映射区域的地方)找一块空闲的虚拟内存;

这两种方式都是分配虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

在标准C库中,提供了malloc和free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

1. malloc小于128K的内存,是由brk分配内存,将_edata往高地址上堆(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后再虚拟地址空间建立映射关系);

2. malloc大于128K的内存,使用mmap分配,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0);

3. brk分配的内存要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放;

4. 当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作。

既然堆内内存brk和sbrk不能直接释放,为什么不全部使用mmap来分配,munmap直接释放呢?

进程向OS申请和释放地址空间的接口brk/mmap/munmap都是系统调用(sbrk是C库地址) ,频繁使用系统调用都比较消耗系统资源。并且,mmap申请的内存内munmap后,重新申请会产生更多的缺页中断。例如使用mmap分配1M空间,第一次调用产生了大量的缺页中断(1M/4K次),当munmap后再次分配1M空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用mmap分配小内存,会导致地址空间的分配更多,内核的管理负担更大。同时,堆是一个连续空间,并且堆内碎片由于没有归还OS,如果可重用碎片,再次访问该内存很可能就不需要产生系统调用和缺页中断了,这将大大降低CPU的消耗。

Slab分配器

在LINUX中,以页为最小单位分配内存对于内核管理系统来说还是比较方便的,但内核自身最常使用的内存却往往是很小的(远远小于一页)的内存块,因为大都是一些描述符。一个整页中可以聚集多个这种小块内存,如果一样按页分配,那么就会被频繁的创建/销毁,性能消耗也是十分大的。

为了满足内核对这种小内存块的需要,Linux系统采用了SLAB分配器。Slab分配器的实现相当复杂,但原理不难,其核心思想就是memory pool。内存片段(小块内存)被看做对象,当被使用完后,并不直接释放,而是被缓存到memory pool里,留作下次使用,这就避免了频繁创建与销毁对象所带来的额外负载。

来源:https://www.cnblogs.com/godV-15/p/8241777.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值