Linux内存管理机制
内存的表象层次依次为 逻辑地址------>线性地址----->物理地址
逻辑地址经过段机制转换成线性地址。
线性地址经过页机制转换成物理地址。
Linux将所有程序的段地址定为 0 - 4G(Linux中),所有虽然逻辑地址和线性地址不一样,但是在Linux中它们的值是一样的,可以认为是等价的。
问题:
-
进程空间地址如何管理?
-
进程地址如何映射到物理内存?
-
物理内存如何被管理?
进程内存空间
Linux使用虚拟内存管理技术,使得每个进程都有互不干涉的内存空间。该空间在32位操作系统上是4G的线性虚拟空间。用户所接触到的空间都是虚拟地址空间,无法看见实际的物理空间。使用这种技术不仅能保护操作系统,还可以获得比实际物理空间更大的地址。
-
4G虚拟地址空间被分为用户空间和内核空间。用户占0-3G,内核栈3G-4G。用户只能访问自己的虚拟地址空间,不能访问内核空间。之一用户进程进行系统调用(代表用户空间在内核态运行)才可以访问内核空间
-
用户空间对应进程,所以当进程切换时,用户空间就会变化。内核空间是内核负责映射的,并不会根据进程进行改变,是固定的。
内核空间有自己的页表(init_mm.pgd),用户进程有不同的页表
-
每个进程的用户空间都是完全独立、互不相干的。
进程内存管理
进程内存管理的对象是进程线性地址空间上的内存镜像,这些内存镜像起始就是进程使用的虚拟内存区域。统一管理这么大的空间非常不容易。为了方便管理,虚拟空间被划分程成许多大小可变的内存区域(一般是4K),这些区域在进程线性地址中像停车场一样有序排列。这些区域的划分原则是将访问的属性一致的(可读、可写、可执行等)放在一起。
查看进程占用的内存区域: cat /proc/ /maps
每行的数据格式是:
开始地址 终止地址 访问权限 偏移量 主设备号:次设备号 i节点 文件。
在Linux内核中,对应的进程内存区域是:vm_area_struct,内核将每个内存区域作为一个单独的内存对象管理,相应的操作也一致。采用面向对象的方法,可以使vm_area_struct 表示多种类型的内存区域,比如内存映射文件或进程的用户空间栈。
vm_area_struct使用链表来管理虚拟空间,但是为了方便查找,它又使用红黑树来组织内存区域。链表用于遍历全部节点时使用,红黑树适用于地址空间中特定内存区域使用。
进程内存的分配和回收
创建进程 fork()
程序装载 execve()
映射文件 mmap()
动态内存分配 malloc()
都需要分配内存给进程,不过这时申请的不是实际内存,是虚拟内存。准确的是内存区域。
进程对内存区域的分配都会归结于do_mmap()函数上brk()调用单独以系统调用实现,不用do_mmap)
内核使用do_mmap函数创建一个新的线性地址空间。或者说该函数创建了一个新的VMA不是很准确,如果创建的地址区域和一个已经存在的地址空间相邻,并且它们具有相同的权限,那么两个区域将合并。如果不能合并,就是新的VMA了。
同样,释放一个内存区域do_ummap()
如何转换成物理空间
上面所有的进程所需要的 空间都转换成了虚拟空间。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存,获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由 请求页机制 产生 缺页异常。
该异常是虚拟内存赖以存在的保障。它会告诉内核去真正的为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上。
这种请求页机制把页面的分配推迟到不能再推迟为止,并不急于把所有的事情都一次性做完。利用了内存的局部性原理,请求页的好处是节省了内存,提高了吞吐率。调用nopage函数。
系统物理内存管理
虽然应用程序操作的对象是虚拟内存,但是处理器操作的是物理内存。当一个应用访问虚拟内存时,首先必须将虚拟地址转化为物理地址,然后处理器才能解析地址访问请求。地址的转化工作需要通过查询页表才能完成,地址转换需要将虚拟内存分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一个级别的页表或者指向最终的物理页面。
每个进程都有自己的页表,进程描述符的pgd域指向的就是进程的页全局目录。
虚拟地址映射到页之前要分配物理页,并建立页表
物理内存管理(页管理)
Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k大小的页,从而分配和回收内存的基本单位便是内存页。
利用页表可以灵活的分配地址,不需要连续的内存。但是使用连续的内存可以减少对页表的索引,提高效率。
内核采用伙伴的关系来管理页面。分配最小单位只能是2的幂次方的大小。内核中分配空闲页面的基本函数是get_free_page / get_free_pages,它是分配单页或是指定的页面(2、4、8、。。。页)
注意:get_free_page是在内核中分配内存,如果当前内存不够容纳对空间,会以页面大小的倍数为单位,进行扩张或收缩对应的内存空间。
malloc是在用户空间分配内存。实际上是调用的brk()系统调用
brk是通过实际的请求修改,而不是页面大小。
因此,malloc在用户空间分配内存可以以字节为单位,但是在内部会以页为单位。
物理页由struct page描述,系统中所有的页面都存放在mem_map[]数组中,可以通过该数组找到系统中的每一页(空闲或者非空闲),空闲页面可以使用伙伴关系组织的空闲页链表(free_area[MAX_ORDER])来索引。
内核内存使用
Slab
以页为最小分配内存对于内核管理系统中物理内存比较方便,但是实际内存自身使用的内存还是很小的,比如:文件描述符、进程描述符、虚拟内存描述符等行为所需的内存都不到一页。
为了这种需求,Linux采用了一种叫slab分配器的技术,Slab分配器实现非常复杂,核心就是存储池的运用。内存片段被看作对象,当被使用完后,并不直接释放而被缓存到存储池中,留作下次使用。
减少伙伴系统分配算法的调用次数(放置内存碎片过多,找不到大的连续内存)
种叫slab分配器的技术,Slab分配器实现非常复杂,核心就是存储池的运用。内存片段被看作对象,当被使用完后,并不直接释放而被缓存到存储池中,留作下次使用。
减少伙伴系统分配算法的调用次数(放置内存碎片过多,找不到大的连续内存)
Slab并非是脱离伙伴关系而独立存在的一种内存分配方式,slab仍然是建立在页面基础之上,换句话说,Slab将页面(来自于伙伴关系管理的空闲页面链表)撕碎成众多小内存块以供分配,slab中的对象分配和销毁使用kmem_cache_alloc与kmem_cache_free。