理解Linux内存的工作原理

踏踏实实积累,不要浮躁

内存是操作系统最核心的功能之一,内存主要用来存储系统和应用程序的指令,数据,缓存等

1:内存映射

物理内存:我们通常所说的8G内存指的是物理内存,也被称为主存大多数计算机的主存都是动态随机访问内存(DRAM)内核才能直接访问物理内存,进程访问内存是通过虚拟内存空间访问的。

虚拟内存:Linux内核为每个进程都提供了一个独立的虚拟地址空间,这个空间是连续的,为了方便进程可以很方便的访问虚拟内存

虚拟空间的内部被称为内核空间用户空间,不同字长的处理器,地址空间范围也不同比如常见的32位和64位系统。下图显示的是32位操作系统和64位操作系统对应的内核空间和用户空间大小示意图

理解Linux内存的工作原理

用户态和内核态:应用程序处理用户态,进程在用户态时只能访问用户空间内存,只有进入内核态之后才能访问内核态内存。虽然每个进程空间都包涵内核空间,但这些内核空间关联的都是相同的物理内存,这样当进程切换到内核态之后就可以很方便的访问内核空间内存。

内存映射:本质就是一张页表记录虚拟内存地址和物理内存地址的映射关系,将虚拟内存地址映射到物理内存地址

理解Linux内存的工作原理

缺页异常:当进程访问的虚拟地址在页表中查不到的时候,系统会产生一个缺页异常,进入内核空间分配物理内存更新进程页表最后返回用户空间,恢复进程的运行。

 

2:多级页和大页

因为页大小只有4kb,导致一个问题整个页表会变得非常大。32位系统就需要100多万个页表项,才可以实现整个地址空间的映射Linux引入多级页表和大页是为了解决页表项过多的问题。

多级页表:把内存分成区块来进行管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么多级页就只保存这些使用中的区块

Linux用的就是四级页表来管理内存页,虚拟地址被分为5个部分,前4个页项用于选择页,而最后一个索引表示页内偏移

理解Linux内存的工作原理

大页:就是比普通页更大的内存块,常见的有2MB和1GB大页通常用在使用大量内存的进程上比如Oracle等(工作中有听过使用大页内存)

通过页表的映射,进程就可以通过虚拟地址来访问物理内存了,

 

3:虚拟内存空间分布

理解Linux内存的工作原理

上图展示的是虚拟内存空间的分布情况,用户空间包括如下

1:栈,包括局部变量和函数调用的上下文(递归函数用的就是系统自带的这块内存)栈大小一般是固定的8M

2:文件映射断,包括动态库,共享内存等,

3:堆,包括动态分配的内存,从地地址开始向上增长

4:数据段,包括全局变量等

5:只读段,包括代码和变量等

 

4:内存的分配与回收

malloc()是c标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,brk()和mmap()

对于小内存(小于128k),C标准库使用brk()来分配 通过移动堆顶的位置来分配内存。这些内存释放之后并不会立刻归还系统,而是被缓存起来,这样就可以重复利用

对于大块内存(大于128k),直接使用内存映射mmap()来分配,也就是在文件映射段找一块空闲内存分配出去。

brk VS mmap

brk()方式的缓存,可以减少缺页异常的发生,提高内存访问效率。不过由于内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放容易造成内存碎片

mmap()方式分配的内存使用完之后会直接归还系统,所以每次mmap都会发生缺页异常,频繁的内存使用会造成大量缺页异常增大内核压力

内存回收方式:

1:回收缓存,比如使用LRU 算法回收最近最少使用的内存页面

2:回收不常使用的内存,把不常使用的内存直接写入磁盘中 swap

3:杀死进程,内存紧张时通过OOM异常来杀死进程

 

5:查看内存使用情况

free 命令:

理解Linux内存的工作原理

第一列:总内存大小

第二列:已经使用过的内存大小

第三列:空闲的内存大小

第四列:表示共享内存的大小

第五列:buff/cache 表示缓存和缓冲区的大小

第六列:available 表示新进程可用内存的大小  包括空闲内存和正在使用的缓存大小

 

top命令:

理解Linux内存的工作原理

VIRT:表示虚拟内存的大小

RES:表示常驻内存的大小

SHR:共享内存大小

%MEM:表示进程占用内存和总内存的比例

最后推荐一本书  《Operating System Concepts》