看过很多讲解内存分配的书和博客,一直对linux内核的内存分配抱有敬畏之心,理解的也不是很透彻,今天又研究了一番,下面就把自己的对于linux内存分配的理解跟大家分享一下。
linux中目前是采用虚拟内存的技术来管理内存的,早期的时候因为内存的大小有限,所以进程在寻址的时候是直接寻址物理内存的,但是随着技术的发展,在linux中进程数量的大量增加,直接寻址物理内存的方法已经无法满足众多进程的需要了,所以诞生了虚拟内存的技术。
linux将地址分为了虚拟内存和物理内存两个概念来管理,在目前的32位地址总线的情况下,虚拟内存空间的大小是4G(2^32,而物理内存的大小则就是插在主板上的内存条的大小,具体多少就看你插了多大的内存条了。内核将4G大小的虚拟内存分成了两个部分,其中0-3G为用户空间使用,3G-4G则为内核空间来使用。在linux中的每个用户进程独享了3G的用户空间虚拟内存,所有的进程共享了剩下的1G的虚拟内存。这样做的好处就是每个进程看来,自己都是拥有4G的内存空间。但是真正的物理内存并不能满足每个进程都能独享4G的空间,这时候怎么办呢?就涉及到内核是如何管理这些虚拟内存空间和物理地址空间了。
我们在编写代码的时候涉及到的内存地址都是虚拟内存地址,比如在打印一个指针指向的内存的时候,这时候打印出来的就是虚拟内存地址。但是真正的代码或者数据必须是存储在物理内存空间的,也就是那根内存条上的,怎么办呢?这时候linux的段页式内存管理就开始起作用了。比如我们在用户代码中用malloc()申请一段内存的时候,首先会在虚拟地址空间申请一段内存,但是此时并不会去申请物理内存,当代码中真正要访问刚才申请的虚拟内存的时候,此时会检查虚拟内存地址是否合法,然后看虚拟内存对应的页是否在主存中(物理内存),如果没有,此时会产生一个缺页中断,意思就是我要访问的数据或者代码还没有加载到主存中。此时内核会在物理内存空间分配一段空间,利用段页式管理方法将分配的虚拟内存和物理内存映射起来。如果物理内存中页框使用完了,内核就会启动页面置换算法,将其中暂时不用的页框换出到磁盘的交换区中,将需要使用的代码或者数据装入到刚刚释放的主存页框中。这是用户空间申请内存的大致过程。
在内核中,我们之前提到过,内核使用的地址空间是在3G-4G这个区间,内核将这个1G的区间又划分为3个部分,其中0-16M为DMA区域,16-896M为NORMAL,896M-1G则为HIGHMEM。内核的这1G的虚拟内存是可以映射到整个物理地址空间的。其中0-896M这一段是采用线性映射的方法,也就是在物理地址和线性地址之间只是相差了3G的大小。内核中的kmalloc和alloc_pages等函数分配的内存就是处在直接映射区的。
内核将高端内存又分成了三个部分,分别为固定映射区、vmalloc区和特殊映射区。其中我们在关注下vmalloc区域,这一区域的内存分配和上文说的差不多,先是在虚拟内存空间分配一段连续的虚拟内存,然后通过伙伴系统分配获得物理页,最后使用页表将两个地址映射起来。只不过这一部分分配的物理内存是可以不连续的,但是通过kmalloc分配的物理内存是连续的。