linux的内存管理方法是采用经典的什么算法,Linux 内存管理总结

Linux

内存管理总结

1

地址类型

物理地址:简单的理解为地址总线上的地址,直接的硬件地址;

线性地址:段式转换后,但是未经过页式转换前的地址,称为线性地址;

逻辑地址:程序代码中的地址未经过段式转换前的地址称为逻辑地址;

下面我们通过一个图形象的理解一下:

a4c26d1e5885305701be709a3d33442f.png

图1 地址间的转换关系(以x86为例)

2 什么是段式管理?

2.1 段式管理的由来(以16位CPU为例)

计算机的内存以‘字节’为最小单位进行线性编址,字节也是80X86对内存管理的最小元素。16位的CPU内部拥有20根地址线,它的寻址范围就是2的20次方,也就是1M的内存空间。但是16位CPU用于存放存储单元的地址的寄存器只有16位,因此只能访问65536个存储单元,64K。

为了能够访问1M的内存空间,CPU就采用了内存分段的管理模式,并在CPU中加入了段寄存器:

16位CPU把1M内存空间分为若干个逻辑段,每个逻辑段的要求如下:

1

逻辑段的起始地址必须是16的倍数,即最后4个二进制必须全为0;

2

逻辑段的最大容量64K,这是由16位寄存器的饿寻址空间锁决定。

转化关系:

逻辑地址 =

【段标示符:段内偏移量】

物理地址 =

段标示符*16 + 偏移量

问题

:16位的CPU只有4个段寄存器,结合相应的段式管理怎么访问1M的地址空间?

2.1 32位CPU

32位的CPU

内存管理仍然采用“分段”的管理模式,存储器的逻辑地址同样由段基址和偏移量两部分组成,32位于16位既有相同的地方,也有不同的地方。因为32位的PC采用了两种不同的工作模式:实模式和保护模式。

1

实模式

在实模式下,32位微机的内存管理与16位微机是一致的。

2

保护模式

段基地址可以长达32位,每个段的最大容量可达4G,段寄存器的值是表示段地址的选择器,用该选择器可以从内存中得到一个32位的段地址,物理地址

= 段地址 + 段内偏移;

a4c26d1e5885305701be709a3d33442f.png

图2 32段式管理

3 什么是分页管理?

CPU的 页式内存管理单元,负责将一个线性地址,最终翻译为一个物理地址。

从管理和效率的角度讲,线性地址被分为以固定长度为单位的组,称为页(page)。

例如32位的机器:线性地址最大4G,可以以4k来划分,这样整个线性地址就被划分为一个tatol_page[2^20]的大数组,这个大数组我们称为页目录。

另一类“页”,称之为物理页,或者页框,页帧。分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与线性地址页是相同的。

a4c26d1e5885305701be709a3d33442f.png

图三 分页管理

这里注意到,这个total_page数组有2^20个成员,每个成员一个地址,这个数组就要占掉4MB的内存空间。为了节省内存,引入了二级管理模式的机器组织分页单元。

a4c26d1e5885305701be709a3d33442f.png

CPU的Cr3保存着进程的页目录基地址+directory+Table+offset = 具体的页。

4 linux的内存管理

Linux内核的设计并没有全部采用Intel所提出的段机制,仅仅是有限度的使用了分段机制。

段的基地址全部为0x00000000

由此得出,每个段的逻辑地址空间空间为0~4GB。因为每个段的基地址为0,因此逻辑地址到线性地址映射保持不变,也就是说,偏移量就是线性地址。Linux巧妙的绕过了段式管理机制,直接采用页式管理机制。

Linux 2.4

内核为了每种CPU提供统一的界面,采用了三级页式管理架构,来兼容二级,三级管理架构的CPU。Linux

2.6内核采用了四级页管理架构。

页全局目录(PGD) + 页中间目录(PMD) + 页表条目(PTE)

a4c26d1e5885305701be709a3d33442f.png

1

内核为MMU

设置好所有的页面表,MMU用线性地址中的PT字段作为下表找到相应的PTE,该表项中存放的就是物理页面的指针。

2

线性地址中的最后位段位物理页面内的相对偏移量,MMU将此偏移与目标页面的起始地址相加就得到相应的物理地址。

上面我们从整体的角度分析了一下linux内存管理,那么具体到程序的角度我们如何来分配内存呢?

5

linux内核内存分配函数

1.

原理说明

Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中和linux2.6内核以上版本,用到了四级页表,如图2-1所示。四级页表分别为:

l 页全局目录(Page Global

Directory)

l 页上级目录(Page Upper

Directory)

l 页中间目录(Page Middle

Directory)

l 页表(Page Table)

页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指向一个页框。Linux中采用4KB大小的页框作为标准的内存分配单元。

在实际应用中,经常需要分配一组连续的页框,而频繁地申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框。这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。

为了避免出现这种情况,Linux内核中引入了伙伴系统算法(buddy system)。把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大可以申请1024个连续页框,对应4MB大小的连续内存。每个页框块的第一个页框的物理地址是该块大小的整数倍。

假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,如果没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另外一个移到256个页框的链表中。如果512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,如果仍然没有,则返回错误。

页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。

slab分配器源于 Solaris 2.4

的分配算法,工作于物理内存页框分配器之上,管理特定大小对象的缓存,进行快速而高效的内存分配。

slab分配器为每种使用的内核对象建立单独的缓冲区。Linux 内核已经采用了伙伴系统管理物理内存页框,因此 slab分配器直接工作于伙伴系统之上。每种缓冲区由多个

slab 组成,每个 slab就是一组连续的物理内存页框,被划分成了固定数目的对象。根据对象大小的不同,缺省情况下一个

slab 最多可以由 1024个页框构成。出于对齐等其它方面的要求,slab

中分配给对象的内存可能大于用户要求的对象实际大小,这会造成一定的内存浪费。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int

order)

__get_free_pages函数是最原始的内存分配方式,直接从伙伴系统中获取原始页框,返回值为第一个页框的起始地址。__get_free_pages在实现上只是封装了alloc_pages函数,从代码分析,alloc_pages函数会分配长度为1<

2.6.18内核版本中,该宏定义为10。也就是说在理论上__get_free_pages函数一次最多能申请1<<10 *

4KB也就是4MB的连续物理内存。但是在实际应用中,很可能因为不存在这么大量的连续空闲页框而导致分配失败。在测试中,order为10时分配成功,order为11则返回错误。

struct kmem_cache *kmem_cache_create(const char *name,

size_t size,

size_t align, unsigned long flags,

void (*ctor)(void*, struct kmem_cache *, unsigned long),

void (*dtor)(void*, struct kmem_cache *, unsigned long))

void *kmem_cache_alloc(struct kmem_cache *c, gfp_t

flags)

kmem_cache_create/

kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存块的场合。首先用kmem_cache_create创建一个高速缓存区域,然后用kmem_cache_alloc从该高速缓存区域中获取新的内存块。

kmem_cache_alloc一次能分配的最大内存由mm/slab.c文件中的MAX_OBJ_ORDER宏定义,在默认的2.6.18内核版本中,该宏定义为5,于是一次最多能申请1<<5 *

4KB也就是128KB的连续物理内存。分析内核源码发现,kmem_cache_create函数的size参数大于128KB时会调用BUG()。测试结果验证了分析结果,用kmem_cache_create分配超过128KB的内存时使内核崩溃。

void *kmalloc(size_t size, gfp_t flags)

kmalloc是内核中最常用的一种内存分配方式,它通过调用kmem_cache_alloc函数来实现。kmalloc一次最多能申请的内存大小由include/linux/Kmalloc_size.h的内容来决定,在默认的2.6.18内核版本中,kmalloc一次最多能申请大小为131702B也就是128KB字节的连续物理内存。测试结果表明,如果试图用kmalloc函数分配大于128KB的内存,编译不能通过。

void *vmalloc(unsigned long size)

前面几种内存分配方式都是物理连续的,能保证较低的平均访问时间。但是在某些场合中,对内存区的请求不是很频繁,较高的内存访问时间也可以接受,这是就可以分配一段线性连续,物理不连续的地址,带来的好处是一次可以分配较大块的内存。图3-1表示的是vmalloc分配的内存使用的地址范围。vmalloc对一次能分配的内存大小没有明确限制。出于性能考虑,应谨慎使用vmalloc函数。在测试过程中,最大能一次分配1GB的空间。

a4c26d1e5885305701be709a3d33442f.png

void *dma_alloc_coherent(struct device *dev, size_t

size,

ma_addr_t *dma_handle, gfp_t gfp)

DMA是一种硬件机制,允许外围设备和主存之间直接传输IO数据,而不需要CPU的参与,使用DMA机制能大幅提高与设备通信的吞吐量。DMA操作中,涉及到CPU高速缓存和对应的内存数据一致性的问题,必须保证两者的数据一致,在x86_64体系结构中,硬件已经很好的解决了这个问题,

dma_alloc_coherent和__get_free_pages函数实现差别不大,前者实际是调用__alloc_pages函数来分配内存,因此一次分配内存的大小限制和后者一样。__get_free_pages分配的内存同样可以用于DMA操作。测试结果证明,dma_alloc_coherent函数一次能分配的最大内存也为4M。

void * ioremap (unsigned long offset, unsigned long

size)

ioremap是一种更直接的内存“分配”方式,使用时直接指定物理起始地址和需要分配内存的大小,然后将该段物理地址映射到内核地址空间。ioremap用到的物理地址空间都是事先确定的,和上面的几种内存分配方式并不太一样,并不是分配一段新的物理内存。ioremap多用于设备驱动,可以让CPU直接访问外部设备的IO空间。ioremap能映射的内存由原有的物理内存空间决定,所以没有进行测试。

如果要分配大量的连续物理内存,上述的分配函数都不能满足,就只能用比较特殊的方式,在Linux内核引导阶段来预留部分内存。

void*

alloc_bootmem(unsigned long size)

可以在Linux内核引导过程中绕过伙伴系统来分配大块内存。使用方法是在Linux内核引导时,调用mem_init函数之前用alloc_bootmem函数申请指定大小的内存。如果需要在其他地方调用这块内存,可以将alloc_bootmem返回的内存首地址通过EXPORT_SYMBOL导出,然后就可以使用这块内存了。这种内存分配方式的缺点是,申请内存的代码必须在链接到内核中的代码里才能使用,因此必须重新编译内核,而且内存管理系统看不到这部分内存,需要用户自行管理。测试结果表明,重新编译内核后重启,能够访问引导时分配的内存块。

在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。

分配原理

最大内存

其他

__get_free_pages

直接对页框进行操作

4MB

适用于分配较大量的连续物理内存

kmem_cache_alloc

基于slab机制实现

128KB

适合需要频繁申请释放相同大小内存块时使用

kmalloc

基于kmem_cache_alloc实现

128KB

最常见的分配方式,需要小于页框大小的内存时可以使用

vmalloc

建立非连续物理内存到虚拟地址的映射

物理不连续,适合需要大内存,但是对地址连续性没有要求的场合

dma_alloc_coherent

基于__alloc_pages实现

4MB

适用于DMA操作

ioremap

实现已知物理地址到虚拟地址的映射

适用于物理地址已知的场合,如设备驱动

alloc_bootmem

在启动kernel时,预留一段内存,内核看不见

小于物理内存大小,内存管理要求较高

注:表中提到的最大内存数据来自CentOS5.3

x86_64系统,其他系统和体系结构会有不同

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值