一、内存寻址
1、cpu寻址
linux系统中,从cpu角度去访问的地址是虚拟地址;cpu将虚拟地址发送给MMU(内存管理单元,Memory Management unit),MMU去寻找实际内存的物理地址。虚拟地址到物理地址映射关系的实现可以称之为地址转换(Address Translation)。
用户空间中,进程A切换到进程B时,MMU会把物理会把进程B的页表的基地址加载到MMU。
2、页表项
页表的高20bit,表示虚拟地址的页码;低12位表示该页码的权限。
假设用户空间的进程要访问0x123456这个地址(这个地址就是虚拟地址)。那么0x123就是这个虚拟地址的页码,通过这个页码,MMU去找对应的内存物理页的地址,假设对应的是1M物理地址,那么0x123456对应的物理地址就是1M+0x456地址。
页表的低12位是权限,具体的描述如下:
------------------------------------------------------------------
|11-9| 8 | 7 | 6| 5 | 4 | 3 | 2 | 1 | 0 |
------------------------------------------------------
| OS | 0 | PSE | 0 | A | PCD | PWT | U/S | R/W | P |
------------------------------------------------------------------
0:存在位,如果P=1,表示页表地址指向的该页在内存中,如果P=0,表示不在内存中。
1:读/写位:
2:用户/管理员位,这两位为页目录项提供硬件保护。
当特权级为3的进程要想访问页面时,需要通过页保护检查,
而特权级为0的进程就可以绕过页保护。
3:PWT(Page Write-Through)位,表示是否采用写透方式,写透方式就是既写内存(RAM)也写高速缓存,
该位为1表示采用写透方式
4:PCD(Page Cache Disable)位,表示是否启用高速缓存,该位为1表示启用高速缓存。
5:访问位,当对页目录项进行访问时,A位=1。
7:Page Size标志,只适用于页目录项。如果置为1,页目录项指的
是4MB的页面,请看后面的扩展分页。
9-11:操作系统专用,Linux也没有做特殊之用
当进程访问0x123456这个虚拟地址是一个代码段,那么他就只有r+x权限,如果,你对这个地址进行w操作,mmu就会产生pagefault,内核就会给进程发送segmentfault。
如果0x123这个页码没有在MMU中找到,说明用户的程序在访问非法地址,这个时候MMU也会产生pagefault,内核会给进程发segmentfault.
分页机制更详细描述:
页目录项和页表项
深入Linux内核(内存篇)—页表映射分页
内存分页机制完全攻略
Linux内存寻址之分页机制
二、内存分zone(敬请期待)
三、buddy算法
1、buddy system简述
内存分zone后,每一个zone均是由buddy算法来进行管理的。
buddy算法管理内存的基本单位是页(p:page)。buddy算法,会为zone生成11个链表,这11链表分别管理1p、2p、4p、8p、…512p、1024p 的空闲页。最大可以申请1024个连续页,对应4MB大小的连续内存。
2、buddy算法优劣
Buddy算法牛逼就牛逼在运用了世界上任何正整数都可以由2^n的和组成。这也是Buddy算法管理空闲页表的本质。
可以通过执行cat /proc/buddyinfo
来查看buddy的信息。
buddy system能很快地分配和回收内存块,但有内碎片,因为它要向上取2的幂的块大小,有空间浪费,但这是所有内存分配算法都避免不了的。
3、成为buddy的条件:
1.buddy system允许一个被分配块单元平均拆分成两个大小是原来一半的块单元,这两个块单元互为伙伴。块B的伙伴必须满足大小跟块B一样大,并且内存地址相邻(才可以合并)。
2.另一个伙伴性质是所有块单元在内存中的地址必须能被它自己的大小整除。比如16字节的块的地址都是一些16的倍数,如64字节的块的地址都是一些64的倍数,等等。
3、低地址的“伙伴”必须在一个能它“父亲块大小”整除,简单的说,来自同一个大块的两个小块才是伙伴。
4、buddy算法内存分配、回收示例
buddy 算法内存分配示例:
目标:申请70页的内存
1.70p向上取整到2的倍数:128p
2.查询有128p空闲页吗?
3.没有,分配256p空闲页。
a.有256p的空闲页吗?
b.没有,分配512p的页。
i.有512p空闲页吗?
ii.没有,分配1024p的空闲页
i.有1024p的空闲页吗?
ii.有,从1024p的那个空闲链表中摘下,分配出去
iii.拆开一半挂在512p空闲链表上
iv.返回另一半512p空闲页
c.拆开一半挂在256p空闲链表上
d.返回另一半256p
4.将获得的256p的块拆两半,一半挂在128p空闲链表中
5.另一半128p块返回用户。
buddy 算法内存回收示例:
目标:回收刚才分配的128页内存
1.这个128p的伙伴也在128K的空闲链表上吗?
2.是的,移除它的128p伙伴空闲页。
3.合并成256p的空闲页
a.这个256p的空闲页的伙伴也在256p空闲链表上吗?
b.是的,移除该伙伴并合并成512p的空闲页。
c.这个512p的空闲页的伙伴也在512p空闲链表上吗?
d.是的,移除该伙伴并合并成1024p的空闲页。
e.这个1024p的空闲页的伙伴也在1024p空闲链表上吗?
f.没有,添加这个1024p的空闲页在1024p的空闲链表上。
四、CMA
细心的读者或许会发现当Buddy算法对内存拆拆合合的过程中会造成碎片化的现象,以至于内存后来没有了大块的连续内存,全是小块内存。当然这对应用程序是不影响的(前面我们讲过用页表可以把不连续的物理地址在虚拟地址上连续起来),但是内核态就没有办法获取大块连续的内存(比如DMA, Camera, GPU都需要大块物理地址连续的内存)。
在嵌入式设备中一般用CMA来解决上述的问题。CMA的全称是contiguous memory allocator, 其工作原理是:预留一段的内存给驱动使用,但当驱动不用的时候,CMA区域可以分配给用户进程用作匿名内存或者页缓存。而当驱动需要使用时,就将进程占用的内存通过回收或者迁移的方式将之前占用的预留内存腾出来,供驱动使用