Chinaunix首页 | 论坛 | 博客
  • 博客访问: 316722
  • 博文数量: 89
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 691
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-20 16:58
文章分类

全部博文(89)

文章存档

2017年(1)

2016年(35)

2015年(53)

我的朋友

分类: LINUX

2016-02-17 14:39:34

转载自:  http://www.cnblogs.com/zhaoyl/p/3695517.html

本文以32位机器为准,串讲一些内存管理的知识点。


1. 虚拟地址、物理地址、逻辑地址、线性地址

 虚拟地址又叫线性地址。linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻辑地址专指下文说的线性偏移前的地址)是一个概念。物理地址自不必提。内核的虚拟地址和物理地址,大部分只差一个线性偏移量。用户空间的虚拟地址和物理地址则采用了多级页表进行映射,但仍称之为线性地址。

2. DMA/HIGH_MEM/NROMAL 分区

在x86结构中,Linux内核虚拟地址空间划分0~3G为用户空间,3~4G为内核空间(注意,内核可以使用的线性地址只有1G)。内核虚拟空间(3G~4G)又划分为三种类型的区:
ZONE_DMA 3G之后起始的16MB
ZONE_NORMAL 16MB~896MB
ZONE_HIGHMEM 896MB ~1G

由于内核的虚拟和物理地址只差一个偏移量:物理地址 = 逻辑地址 – 0xC0000000。所以如果1G内核空间完全用来线性映射,显然物理内存也只能访问到1G区间,这显然是不合理的。HIGHMEM就是为了解决这个问题,专门开辟的一块不必线性映射,可以灵活定制映射,以便访问1G以上物理内存的区域。从网上扣来一图,

高端内存的划分,又如下图,

内核直接映射空间 PAGE_OFFSET~VMALLOC_START,kmalloc和__get_free_page()分配的是这里的页面。二者是借助slab分配器,直接分配物理页再转换为逻辑地址(物理地址连续)。适合分配小段内存。此区域 包含了内核镜像、物理页框表mem_map等资源。

内核动态映射空间 VMALLOC_START~VMALLOC_END,被vmalloc用到,可表示的空间大。

内核永久映射空间 PKMAP_BASE ~ FIXADDR_START,kmap

内核临时映射空间 FIXADDR_START~FIXADDR_TOP,kmap_atomic

3.伙伴算法和slab分配器

伙伴Buddy算法解决了外部碎片问题.内核在每个zone区管理着可用的页面,按2的幂级(order)大小排成链表队列,存放在free_area数组。

具体buddy管理基于位图,其分配回收页面的算法描述如下,

buddy算法举例描述:

假设我们的系统内存只有16个页面RAM。因为RAM只有16个页面,我们只需用四个级别(orders)的伙伴位图(因为最大连续内存大小为16个页面),如下图所示。

 

bit为1或0的意义:
    如order(0)的bit3为1, 由于4,5页面有一个空闲, 如果在本order(0)分配页面(1个page), 只要从bit3(为1)获取第4个页面就可以
    而order(0)的bit5为0, 由于页面8,9都虽然是空闲的,但优先被合并, 如果在本order(0)分配页面(1个page), 不要从bit5(为0)获取
    order(0)的bit1为0, 由于由于0,1页面都是busy, 如果在本order(0)分配页面(1个page), 不要从bit1获取

    总的来说, bit为1说明本order(k)的该bit所在的pages有符合2^k个page的空闲页面且没有更高级的合并, 能被有限分配使用.
                      bit为0, 说明本order(k)的该bit所在的pages没有符合要求的page.(要么这些page已经被用, 要么这些page有更高级的合并)

order(0)bimap有8个bit位(页面最多16个页面,所以16/2

order(1)bimap有4个bit位(order0bimap8bit,所以8/2);

也就是order(1)第一块由两个页框page1 page2组成与order(1)第2块由两个页框page3 page4组成,这两个块之间有一个bit

order(2)bimap有2个bit位(order1bimap4bit,所以4/2)

order(3)bimap有1个bit位(order2bimap4bit,所以2/2)

在order(0),第一个bit表示开始2个页面,第二个bit表示接下来的2个页面,以此类推。因为页面4已分配,而页面5空闲,故第三个bit为1。

同样在order(1)中,bit3是1的原因是一个伙伴完全空闲(页面8和9),和它对应的伙伴(页面10和11)却并非如此,故以后回收页面时,可以合并。

分配过程

当我们需要order1)的空闲页面块时,则执行以下步骤:

1、初始空闲链表为:

order(0): 5, 10

order(1): 8 [8,9]

order(2): 12 [12,13,14,15]

order(3):

2、从上面空闲链表中,我们可以看出,order(1)链表上,有一个空闲的页面块,把它分配给用户,并从该链表中删除。

3、当我们再需要一个order(1)的块时,同样我们从order(1)空闲链表上开始扫描。

4、若在order1)上没有空闲页面块,那么我们就到更高的级别(order)上找,order(2)。

5、此时(order1)上没有空闲页面块)有一个空闲页面块,该块是从页面12开始。该页面块被分割成两个稍微小一些order(1)的页面块,[12,13]和[14,15]。[14,15]页面块加到order1)空闲链表中,同时[1213]页面块返回给用户。

6、最终空闲链表为:

order(0): 5, 10

order(1): 14 [14,15]

order(2):

order(3):

回收过程

当我们回收页面11order 0)时,则执行以下步骤:

1找到在order0)伙伴位图中代表页面11的位,计算使用下面公示:

index = page_idx >> (order + 1)

= 11 >> (0 + 1)

= 5

2、检查上面一步计算位图中相应bit的值。若该bit值为1,则和我们临近的,有一个空闲伙伴。Bit5的值为1(注意是从bit0开始的,Bit5即为第6bit),因为它的伙伴页面10是空闲的。

3、现在我们重新设置该bit的值为0,因为此时两个伙伴(页面10和页面11)完全空闲。

4、我们将页面10,从order(0)空闲链表中摘除。

5、此时,我们对2个空闲页面(页面10和11,order(1))进行进一步操作。

6、新的空闲页面是从页面10开始的,于是我们在order(1)的伙伴位图中找到它的索引,看是否有空闲的伙伴,以进一步进行合并操作。使用第一步中的计算公司,我们得到bit 2(第3位)。

7、Bit 2(order(1)位图)同样也是1,因为它的伙伴页面块(页面8和9)是空闲的。

8、重新设置bit2(order(1)位图)的值,然后在order(1)链表中删除该空闲页面块。

9、现在我们合并成了4页面大小(从页面8开始)的空闲块,从而进入另外的级别。在order(2)中找到伙伴位图对应的bit值,是bit1,且值为1,需进一步合并(原因同上)。

10、从oder(2)链表中摘除空闲页面块(从页面12开始),进而将该页面块和前面合并得到的页面块进一步合并。现在我们得到从页面8开始,大小为8个页面的空闲页面块。

11、我们进入另外一个级别,order(3)。它的位索引为0,它的值同样为0。这意味着对应的伙伴不是全部空闲的,所以没有再进一步合并的可能。我们仅设置该bit为1,然后将合并得到的空闲页面块放入order(3)空闲链表中。

12、最终我们得到大小为8个页面的空闲块,


buddy避免内部碎片的努力

物理内存的碎片化一直是Linux操作系统的弱点之一,尽管已经有人提出了很多解决方法,但是没有哪个方法能够彻底的解决,memory buddy分配就是解决方法之一。 我们知道磁盘文件也有碎片化问题,但是磁盘文件的碎片化只会减慢系统的读写速度,并不会导致功能性错误,而且我们还可 以在不影响磁盘功能的前提的下,进行磁盘碎片整理。而物理内存碎片则截然不同,物理内存和操作系统结合的太过于紧密,以至于我们很难在运行时,进行物理内 存的搬移(这一点上,磁盘碎片要容易的多;实际上mel gorman已经提交了内存紧缩的patch,只是还没有被主线内核接收)。 因此解决的方向主要放在预防碎片上。在2.6.24内核开发期间,防止碎片 的内核功能加入了主线内核。在了解反碎片的基本原理前,先对内存页面做个归类:

1. 不可移动页面 unmoveable:在内存中位置必须固定,无法移动到其他地方,核心内核分配的大部分页面都属于这一类。

2.  可回收页面 reclaimable:不能直接移动,但是可以回收,因为还可以从某些源重建页面,比如映射文件的数据属于这种类别,kswapd会按照一定的规则,周期性的回收这类页面。

3. 可移动页面 movable:可以随意的移动。属于用户空间应用程序的页属于此类页面,它们是通过页表映射的,因此我们只需要更新页表项,并把数据复制到新位置就可以了,当然要注意,一个页面可能被多个进程共享,对应着多个页表项。

防止碎片的方法就是把这三类page放在不同的链表上,避免不同类型页面相互干扰。考虑这样的情形,一个不可移动的页面位于可移动页面中间,那么我们移动或者回收这些页面后,这个不可移动的页面阻碍着我们获得更大的连续物理空闲空间。


另外,每个zone区都有一个自己的失活净页面队列,与此对应的是两个跨zone的全局队列,失活脏页队列 和 活跃队列。这些队列都是通过page结构的lru指针链入的。

思考:失活队列的意义是什么(见)?

slab分配器概述:

有了伙伴系统buddy,我们可以以页为单位获取连续的物理内存了,即4K为单位的获取,但如果需要频繁的获取/释放并不大的连续物理内存怎么办,如几十字节几百字节的获取/释放,这样的话用buddy就不太合适了,这就引出了slab

比如我需要一个100字节的连续物理内存,那么内核slab分配器会给我提供一个相应大小的连续物理内存单元,为128字节大小(不会是整好100字节,而是这个档的一个对齐值,如100字节对应128字节,30字节对应32字节,60字节对应64字节),这个物理内存实际上还是从伙伴系统获取的物理页;当我不再需要这个内存时应该释放它,释放它并非把它归还给伙伴系统,而是归还给slab分配器,这样等再需要获取时无需再从伙伴系统申请,这也就是为什么slab分配器往往会把最近释放的内存(即所谓“热”)分配给申请者,这样效率是比较高的

在内核中有一个通用的缓冲池, 并里面根据缓冲区的大小(Caches_size)分成若干队列。在内核linux-2.4.0中按照大小分配了32-66536大小的缓冲池。下面是caches_size的具体结构。

  1. /* Size description struct for general caches. */
  2. typedef struct cache_sizes {
  3.     size_t         cs_size;
  4.     kmem_cache_t    *cs_cachep;
  5.     kmem_cache_t    *cs_dmacachep;
  6. } cache_sizes_t;

  7. static cache_sizes_t cache_sizes[] = {
  8. #if PAGE_SIZE == 4096
  9.     { 32,    NULL, NULL},
  10. #endif
  11.     { 64,    NULL, NULL},
  12.     { 128,    NULL, NULL},
  13.     { 256,    NULL, NULL},
  14.     { 512,    NULL, NULL},
  15.     { 1024,    NULL, NULL},
  16.     { 2048,    NULL, NULL},
  17.     { 4096,    NULL, NULL},
  18.     { 8192,    NULL, NULL},
  19.     { 16384,    NULL, NULL},
  20.     { 32768,    NULL, NULL},
  21.     { 65536,    NULL, NULL},
  22.     {131072,    NULL, NULL},
  23.     { 0,    NULL, NULL}
  24. };

当分配100个字节的内存时, 会先将100个字节对应到128(比100个字节大,又最接近的cache size)的cache size所在的队列,分配一个128字节的内存,返回给用户.

阅读(3401) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~