Linux内存管理系列之二-物理内存分配机制

内存是计算机的重要组成部分,它是一种物理介质,它的存取速度介于cpu与磁盘之间。它的主要作用是用来交换数据,即将磁盘组的数据通过内存读入cpu,同时将cpu操作后的数据通过内存写入磁盘。
在当下的Linux中,内存主要包括:内核空间内存与用户空间内存。这两部分的内存全部由Linux内核管理。我们首先来看内核是如何管理物理内存的。
一  页与区的概念
1:物理内存分页
在linux中,内核通常把页作为处理内存的基本单位,但是真正的物理内存中并没有做硬件上的分页操作,页只是逻辑上的概念。我们在内核中定义一个数据结构page用来表示内存中的内个物理页。其内部结构如图1-1所示:
  Linux内存管理系列之二-物理内存分配机制_第1张图片
图 1-1
其中flags域代表当前页的状态,通常表示页是否为脏,或该页是否被锁定在内存中。关于脏页是指在高速缓存中的页数据与磁盘文件中的物理数据不一致,则该页为脏页,且该页的内存不可以被内核回收。内存锁定某个页,是指进程访问内存时是通过page结构体,而在交换分区处理时有的page结构体会被换到磁盘中,进程访问内存时在内存中找不到page就得去磁盘中寻找,这样访问速度会下降。内核可以把固定的page结构体锁定到内存中,这样进程访问内存时就不必去访问磁盘。
_count域指页的引用计数,当计数值为-1时表示内核没有引用该页,通常情况下一个物理页可以被多个进程引用。一个页的拥有者可以使内核线程,用户态进程,动态分配的内核数据,静态内核代码或者页高速缓存。
Virtual域指的是页的虚拟地址,我们知道任何进程都是无法直接访问物理地址的,进程访问内存,首先是访问虚拟地址,然后通过页表转化成对应的物理地址才能访问到最终的数据单元。
在多进程中由于进程之间的切换,每一次重新获得的内存的物理地址都会不同,所以page与固定大小内存的映射也是短暂的。这种现象称为内存的重定位。通常情况下一个物理页大小的内存为4KB,而一个page的大小约为40B。
2:物理内存分区
在物理内存中,由于硬件的限制。内核访问内存页的速度也不一致,导致的结果是:硬件只能对部分内存区执行DMA操作,即硬件不经过CPU直接访问内存;某些体系结构中内存的物理寻址范围大于虚拟寻址范围,部分内存不能永久映射到内核空间上。
鉴于上述问题,Linux对内存采取分区策略,主要将内存分成3个区:
描述 物理内存
ZONE_DMA DMA使用的页 <16MB
ZONE_NORMAL 可以正常寻址的页 16~896MB
ZONE_HIGHMEM 动态映射的页 >896MB
在分配物理页时需要注意,一般情况下不允许跨区域分配内存,即一个页跨越DMA区和NORMAL区,这样会导致同一个页内部数据的访问速度不一致。与页相似,每个区也提供一种结构体zone用来表示,如下图1-2:
  Linux内存管理系列之二-物理内存分配机制_第2张图片
图 1-2
其中lock域表示自旋锁,用来做同步访问操作。
Watermark域表示该内存区的内存消耗基准。
Name是内存区的命名,包括:DMA,NORMAL,HIGHMEM。
二 内存的分配与释放
通常我们在C语言编程中往往使用malloc与free为进程分配与释放用户空间的内存,我们来研究内核底层是如何分配与释放内存空间的。
1 内存的分配
前面我们讲过内核对物理内存进行分页操作,将物理内存分为4KB大小的块,因此在起初的内存分配中,页作为最小的分配单元来操作。
struct page* alloc_pages(gfp_t gfp_mask,unsigned int order)
该函数用来分配2^ order大小个连续的物理页,同时返回一个指针指向第一个页的page结构体。这里的gfp_t参数通常指分配器标志,一般情况下包括:行为修饰符,区修饰符和类型。这里重点探讨类型标志中的GFP_KERNEL与GFP_ATOMIC。内核中常用的标志是GFP_KERNEL,它代表了在分配器分配内存的过程中会存在分配器睡眠的情况,会产生阻塞。这种标志通常用来为普通优先级的进程分配内存,在分配的过程中可以被阻塞,分配成功的几率比较大。而GFP_ATOMIC标志表示内存分配原子操作,即在分配的过程中,分配器不能睡眠,这种分配方式通常用于中断处理,因为正常情况下中断程序不允许内存分配器阻塞。即该操作有原子性,要么分配完成要么完全不分配。这种内存的分配方式成功率表较低。
重所周知,在4GB的内存中,内核内存只能占到1GB,所以在alloc_pages函数中这种按页分配内存的方式是非常奢侈的,在实际工作中会造成很大的内存浪费,随后Linux提出了一种新的按字节分配内存的方式该函数如下:
Void *kmalloc(size_t size,gfp_t flags)
Kmalloc函数提供一块大小为size字节且在物理地址上连续的内存块。在实际的内核内存中有时候不一定能够找到完全地址连续的整块内存于是内核有提出了一种按字节分配内存的方式:
Void *vmalloc(unsigned long size)
这种分配方式在虚拟地址上是连续的,不一定保持物理地址的连续。目前内核为了维持高性能,一般都采用kmalloc方式分配内存,只有当需要的内存块过大,内核才会转用vmalloc方式分配内存。
2  内存的释放
与内存的分配相对应,内存同样需要释放,内存释放函数与内存分配函数成对出现。目前通常用到的内存释放函数包括:_free_pages,kfree,vfree。我们重点来探讨一下_free_pages(struct page* page, unsigned int order)。
在程序释放内存时,我们需要谨慎处理,一般情况下我们不能越界释放内存正如不能越界访问一样。_free_pages函数的第一个参数page代表要释放连续物理块的首页地址,order代表要释放2^order个页。在释放页之前要首先进行错误检查,先保证要释放的页都在进程的地址范围之内,然后再执行_free_pages操作,否则直接报错,避免系统崩溃。

你可能感兴趣的:(内存)