关于 Linux 内存管理的原理 (综合网上资料)

HIGHLIGHT:

内存管理

准备知识:SLAB分配策略,伙伴策略

伙伴策略原理


Linux的伙伴算法把所有的空闲页面分为10个块组,每组中块的大小是2的幂次方个页面,例如,第0组中块的大小都为201个页面),第1组中块的大小为都为212个页面),第9组中块的大小都为29512个页面)。也就是说,每一组中块的大小是相同的,且这同样大小的块形成一个链表。

我们通过一个简单的例子来说明该算法的工作原理。

假设要求分配的块其大小为128个页面(由多个页面组成的块我们就叫做页面块)。该算法先在块大小为128个页面的链表中查找,看是否有这样一个空闲块。如果有,就直接分配;如果没有,该算法会查找下一个更大的块,具体地说,就是在块大小为256个页面的链表中查找一个空闲块。如果存在这样的空闲块,内核就把这256个页面分为两等份,一份分配出去,另一份插入到块大小为128个页面的链表中。如果在块大小为256个页面的链表中也没有找到空闲页块,就继续找更大的块,即512个页面的块。如果存在这样的块,内核就从512个页面的块中分出128个页面满足请求,然后从384个页面中取出256个页面插入到块大小为256个页面的链表中。然后把剩余的128个页面插入到块大小为128个页面的链表中。如果512个页面的链表中还没有空闲块,该算法就放弃分配,并发出出错信号。

以上过程的逆过程就是块的释放过程,这也是该算法名字的来由。满足以下条件的两个块称为伙伴:

·
两个块的大小相同
·
两个块的物理地址连续

伙伴算法把满足以上条件的两个块合并为一个块,该算法是迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。


SLAB:基本原理

前面我们分析过了大内存分配的实现机制,事实上,若为小块内存而请求整个页面,这样对于内存来说是一种极度的浪费。因此linux采用了slab来管理小块内存的分配与释放。Slab最早是由sun的工程师提出。它的提出是基于以下因素考虑的:

1:内核函数经常倾向于反复请求相同的数据类型。比如:创建进程时,会请求一块内存来存放mm结构。

2:不同的结构使用不同的分配方法可以提高效率。同样,如果进程在撤消的时候,内核不把mm结构释放掉,而是存放到一个缓冲区里,以后若有请求mm存储空间的行为就可以直接从缓冲区中取得,而不需重新分配内存.

3:前面我们曾分析过,如果伙伴系统频繁分配,释放内存会影响系统的效率,以此,可以把要释放到的内存放到缓冲区中,直至超过一个阀值才把它释放至伙伴系统,这样可以在一定程度上缓减减伙伴系统的压力

4:为了缓减“内碎片”的产生,通常可以把小内存块按照2的倍数组织在一起,这一点和伙伴系统类似

二:slab分配器概貌:

Slab将缓存分为两种:一种是专用高速缓存,另外一种是普通高速缓存。请注意,这里所说的高速缓存和硬件没有必然的关系,它只是slab分配器中的一个软件概念。

专用高速缓存中用来存放内核使用的数据结构,例如:mm,skb,vm等等

普通高速缓存是指存放一般的数据,比如内核为指针分配一段内存

所有的高速缓存区都通过链表的方式组织在一起,它的首结点是cache_chain

另外,普通高速缓存将分配区分为32*(2^0),32*(2^1),32*(2^2)….32*(2^12)大小,共13个区域大小,另外,每个大小均有两个高速缓存,一个为DMA高速缓存,一个是常规高速缓存。它们都存放在一个名这cache_size的表中.

Slab分配器把每一个请求的内存称之为对象(和oop设计方法中的对象类似,都有初始化与析构).对象又存放在slab中.slab又按照空,末满,全满全部链接至高速缓存中.如下图所示:


内存管理:

学习 Linux 内核,应该是首先建立清晰的概念,把握整体,然后才是深入细节。

内存管理看上去比较复杂,但我认为,掌握以下几个基本概念,在学习时,就能做到成竹在胸
欢迎补充、讨论!

1、内存管理的首要任务
很简单,就是把可用的物理内存管理好。
怎么管理?提供一套统一的接口,用于分配内存和释放内存。
这套接口就是最基本的物理内存分配方式,采用的分配方法是伙伴策略。

更高一级的内存分配策略
 基本的内存分配策略,可以避免外部因素导致的分片(外分片?)
为了避免因为小于 1 page 的请求导致的分片(内分片),又在基本分配基础之上,采用了 SLAB 分配策略。当然,也可以采用其它的分配策略。

记住,无论是内核代码,还是用户空间代码,都必须首先申请内存,然后才能使用。
 而无论什么分配方式,最终都是由基本的内存分配器负责分配分配内存。


2、理解 x86 的映射机制
我们想直接访问一个物理内存的地址,如果那样,没有逻辑地址、虚拟地址、物理地址、段、页。。。的概念,世界多美好,
但是,现实是残酷的。 X86 ,或者说现代的处理器,不允许你直接访问物理内存。(所有讲 Linux 操作系统的书都解释了原因)。

我们必须使用逻辑地址,通过段映射,页映射,才能访问到物理内存。


Linux在X86环境下从用户向内核看,所使用的内存表象形式会依次经历

“逻辑地址”等于“线形地址”

最高10 Directory 页目录表偏移量

中间10 Table是页表偏移量

最低12Offset是物理页内的字节偏移量

“逻辑地址”——“线形地址”——“物理地址”

几种形式(关于几种地址的解释在前面已经讲述了)。逻辑地址经段机制转化成线性地址;线性地址又经过页机制转化为物理地址。(但是我们要知道Linux系统虽然保留了段机制,但是将所有程序的段地址都定死为0-4G,所以虽然逻辑地址和线性地址是两种不同的地址空间,但在Linux中逻辑地址就等于线性地址,它们的值是一样的)。沿着这条线索,我们所研究的主要问题也就集中在下面几个问题。


如何理解段映射和页映射
段映射和页映射,是 CPU 干的事情。内核所能做的事情,就是根据可用的物理内存,配置好页目录表、页表,然后告诉 CPU。
对于同样一块物理内存,内核有内核的映射方式,每个进程有自己的映射方式。(正因为如此,才能在进程之间共享内存;也才能使得内核能访问进程的内存)
内核对所有的物理内存(896M 以内)都进行了映射;但是映射并不表示分配!!!内核需要内存的时候,也得先申请,后使用。
进程则只对必须使用的内存进行映射,并在需要的时候动态建立新的映射关系。

3、从用户空间进入内核空间,以及从内核空间返回用户空间时,页目录表、页表是如何发生切换的

在这三个概念基础之上,我们进一步可以理解:

4、进程空间如何管理内存
进程虽然理论上拥有 3G 空间,但实际只使用很少的内存,因此进程是动态的建立映射关系的。
此外,进程采用“内存区间”来管理属于自己的内存。
当需要内存的时候,
首先建立一个“内存区间”(线性地址)
然后通过内存分配器,申请一块物理内存。

最后,在“内存区间”与物理内存之间建立映射关系

allocate_mm()是内核创建进程时使用的一个函数,它用来分配mm_struct结构,此结构用来管理进程的用户空间,注意它只是一个内核结构,不是返回给用户使用的内存空间。

再说说slab,这是内核用来管理内存碎片的机制,只要不是整页分配的内存都由它来管理。用户态发出的内存分配请求最终也需要内核来分配,因为内核管理着整个内存,但是这时候,因为是用户的请求,内核分配出来的内存不会自己使用,而是将其物理地址映射到用户空间的一个线性地址,然后返回给用户。

“__get_free_page是不是只能分配内核空间的物理内存”
物理内存没有内核或者用户之分,只不过只有内核才有权去管理页表,有了页表才能正确访问到物理地址。用户无法申请也没有必要知道物理地址,内核会将物理地址映射好了返回给用户。

综合自网络

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值