Chinaunix首页 | 论坛 | 博客
  • 博客访问: 39920
  • 博文数量: 3
  • 博客积分: 605
  • 博客等级: 一等列兵
  • 技术积分: 110
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-03 20:14
个人简介

打算换工作了,求职北京地区内核职位..

文章分类
文章存档

2014年(2)

2013年(1)

分类: LINUX

2014-06-19 20:34:17

linux内存管理分析 (by chishanmingshen)
http://chishanmingshen.blog.chinaunix.net

第一部分 内存区(zone)
内核用page结构表示所有的物理页.
由于2个硬件缺陷问题导致分为3种类型的内存区.
2个缺陷是
1.一些硬件只能使用<16M的内存做DMA
2.一些硬件的物理地址范围超过内核的虚拟地址范围
3种类型内存区是(以物理内存大于896M为例,看完全文会明白为何这个是依赖于实际物理内存)
1.ZONE_DMA 0..16M
2.ZONE_NORMAL 16M..896M
3.ZONE_HIGHMEM >896M
ZONE_DMA和ZONE_NORMAL也称为低端内存
ZONE_HIGHMEM即传说中的高端内存

第二部分 
申请函数
内核申请内存的函数有3个
1.alloc_pages()返回的是page的指针,故支持高端内存.
2.__get_free_pages() 返回的是线性地址,故只用于低端内存.
3.kmalloc()以字节为单位,内部也是调用了__get_free_pages(),
故也只支持低端内存.注意的是可能分配的内存比请求地多,
因为它是用的slab实现的.
分配器标志有3类:行为/区/类型修饰符.
1.行为描述符,比如__GFP_WAIT标识可以睡眠.(GFP就是get free page的缩写)
2.区描述符,
__GFP_DMA标志标识仅从ZONE_DMA区中申请.
__GFP_HIGHMEM标志标识从ZONE_HIGHMEM(优先)或ZONE_NORMAL中申请.
不指定标志表示从ZONE_NORMAL或ZONE_DMA中申请.
对于__GFP_HIGHMEM标志,其实只能用于alloc_pages(),因为仅它支持高端内存.
3.类型是前2个的组合.
kmalloc()中使用的GFP_KERNEL,可以睡眠,只从低端内存中申请.
故只能用在进程上下文中.
而kmalloc()中使用的GFP_ATOMIC,不能睡眠,只从低端申请.
在不能睡眠时(中断/软中断/tasklet),只能用此标志.
可见kmalloc()只从低端申请.
GFP_DMA标志,表示只能从ZONE_DMA中申请,用于设备驱动程序中.
附:
GFP_ATOMIC的含义
The allocation is high priority and must not sleep. 
This is the flag to use in interrupt handlers, 
in bottom halves, while holding a spinlock, and in other situations where you cannot sleep
GFP_KERNEL的含义
This is a normal allocation and might block. 
This is the flag to use in process context code when it is safe to sleep. 
The kernel will do whatever it has to in order to obtain the memory requested by the caller. 
This flag should be your first choice.

第三部分 高端内存
使用高端内存的2个途径
1.进程中调用alloc_pages(GFP_HIGHUSER)为用户空间分配内存.
2.进程上下文中,故可能会睡眠,常调vmalloc(),最终还是调用alloc_pages(),故
常用高端内存.
3.而在内核中,因为它的睡眠性极少用vmalloc(),但是当装载新模块需要申请大内存时会用到.
高端内存的映射有3种形式
1.永久,使用主内核页表的一页页表.
调用kmap完成映射.永久映射的个数是有限的.故会睡眠(睡前设置为不可中断的sleep状态等待),
因此kmap()只能用在进程上下文中.
2.临时,不会睡眠,故可以用在中断上下文中, 但是数量很少.
是一组保留的映射,被申请用作新建的临时映射.
kmap_atomic()建立临时映射,
映射时会禁止本cpu的中断,因为每个映射对于本cpu都是唯一的,如果切换会错误地覆盖.
而释放临时映射是空操作,因为本来就是在直接覆盖.
3.非连续
vmalloc()有3种非连续类型:
VM_ALLOC物理内存和线性地址同时申请,注意此时指定__GFP_HIGHMEM,故
不限于HIGHMEM,也可以用NORMAL;VM_MAP仅申请线性地址;
VM_IOREMAP仅申请线性地址,其物理内存一般都是超高的.

第四部分 
slab
slab是一种通用小块数据结构缓存机制,使用它的原因是
1.频繁分配释放,故需要缓存
2.频繁的结果是导致内存碎片,即找不到大的连续内存.为了避免,需要将空闲内存连续存放.
可见,slab主要还是解决连续性这个问题的,至于内碎片的浪费,并没避免.
3.回收后即可立即使用,这样提高了效率.
4.对对象着色,这样防止多个对象映射到同一个高速缓存行时,导致切换对象访问引起的颠簸.
每种slab对应一个高速缓存.
申请slab,其实是调用__get_free_pages()得到内存的,
故slab申请的空间也是从低端内存得到的.
kmalloc()其实是基于slab的,使用了一组通用的高速缓存.

第五部分 结合代码分析
还是得附上code基础上聊聊高端内存,因为linux的内存管理中,理解高端内存很重要.
内核要管理4G空间,但是其线性空间仅为1G,这个是为了留出3G给用户空间的.
如果1G全部直接映射,则内核仅能寻址1G空间了,所以又留出128M给动态映射.
这样896M的直接映射+128M的动态映射(重复用)就够4G了.所有物理内存对应的
vm_page肯定都在直接映射区,但是如果访问的话就需要MMU准备映射了.
高端内存是指高于896M的部分,其有3种映射方式:
1.动态
通过vmalloc()申请,获得连续的线性空间.
2.永久
通过alloc_page()有可能是高端内存,此时需要获得PKMAP_BASE到FIXADDR_START
的4M(即pkmap_page_table代表一页页表)的永久映射,
通过kmap()即可将该page映射到该线性空间中.
特点是可能睡眠,因为数量有限.
3.临时
即FIXADDR_START到FIXADDR_TOP之间的空间,用kmap_atomic()获得.
每个cpu都有自己的临时映射空间,独立的.这部分空间又分为分page,每个page代表一个
功能.
特点就是禁止本cpu中断,不睡眠,可直接使用,其实就是直接覆盖上一个.
max_low_pfn = max_pfn;
#ifdef CONFIG_HIGHMEM
highstart_pfn = highend_pfn = max_pfn;
if (max_pfn > MAXMEM_PFN) {
  highstart_pfn = MAXMEM_PFN;
  printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
   pages_to_mb(highend_pfn - highstart_pfn));
}
#endif
可见:
刚开始把所有物理内存a都看做低端内存,
然后看a<=896M,则高端内存起始为a,终止为a.
如果a>896M,则高端起始为896M,终止为a.
所以高端内存是一个物理内存的概念.

#ifdef CONFIG_HIGHMEM
highmem_start_page = mem_map + highstart_pfn;
max_mapnr = num_physpages = highend_pfn;
#endif
high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
可见:
high_memory即高端内存, 是从max_low_pfn(16M<=max_low_pfn<=896M)开始的,
所以高端内存是从b(16M<=b<=896M)开始的,具体依赖于实际的内存大小.

#define VMALLOC_OFFSET (8*1024*1024)
#define VMALLOC_START (((unsigned long) high_memory + 2*VMALLOC_OFFSET-1) & \
      ~(VMALLOC_OFFSET-1))
可见:
VMALLOC_START是从high_memory+空洞(8M<空洞<16M)的地方开始的.
同样,在各个vmallco()分配得到的各个线性空间之间也有8k大小的空洞.
这些都是为了防止越界的.

第六部分 最后的结论
内核映像和mem_map[]开始于虚拟空间的3G+16M处,前16M用于DMA.
vmalloc()的作用就是将不连续物理内存映射到连续线性空间,
VMALLOC_RESERVE大小(128M)的空间就用于vmalloc()/永久/临时映射使用,
虽然在a<896M的情况下,内核其实可以有>128M的保留线性空间.
所以,
高端内存起址为b(896M>=b>=16M),但保留区间大小恒为128M,
而这就间接导致ZONE_NORMAL区的大小是0M到(896-b)M.

by chishanmingshen:x86已经很过时了,下次开始分析x64!
阅读(1339) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~