在内核里分配内存可不像在其他地方分配内存那么容易,从根本上讲是内核本身不能像用户那样奢侈的使用内存. 内核与用户空间不同,它不具备这种能力,它不支持简单便捷的内存分配方法,比如内核一般不能睡眠.
接下来让看看内核是内核获取内存的方法,哈哈,在深入了解实际分配接口之前,让我们先了解一下内核是如何管理内存的:
1)页:内核把物理页作为内存管理的基本单元.尽管处理器的最小可寻址单位通常是字(甚至是字节),但是MMU(内存管理单元,管理内存并把虚拟地址映射到物理地址的硬件),通常以页为单位来进行处理.正如所说的,MMU以页大小为单位来管理系统中的大小,(这也是页表的由来).从虚拟内存的角度来看,页就是最小单位...体系结构的不同,页的大小也是不同的,大多数32为体系架构支持4KB的页,而64位的体系架构一般会支持8KB的页.
内核中用 struct page 结构体表示系统中的每个物理页,该结构位于内核目录的<linux/mm_types.h>中, 下面是我简化的一个 struct page结构体,仅供参考
struct page{
unsigned long flags;
atomic_t _count;
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;
}
flags :存放页的状态,包括页是不是脏的或是锁定到内存中的,flags的每一个单独表示一种状态,页就是说它至少可以表示32中状态.标志定义在<linux/page-flags.h>
_count:存放页的引用计数,页就是这个页被引用的多少次,当计数为-1时,表示当前内核并没有引用这一页,于是新的分配中就可以使用它了,内核代码一般不直接检查该域,而是使用一个函数page_count()进行检查,该函数的参数就是struct page 结构,返回0表示页是空闲的,返回一个正整数表示页正在使用,
一个页可以由页缓存使用(这时mapping域指向和这个页关联的address_space对象)或者作为私有结构(由private指向) 或者作为进程页表中的映射.
virtual 域是页的虚拟地址,它就是页在虚拟地址中的地址,
必须要理解的一点是page结构是与物理页相关,而并非与虚拟页相关
2)区:
由于硬件的限制,内核不能对所有的页一视同仁,由于有些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务,由于这些所谓的限制,所以内核将不同的页划分为不同的区,内核使用区对具有相似特性的页进行分组,linux必须处理如下两种由于硬件存在缺陷而引起的内存寻址问题:
一, 一些硬件只能用特定的内存地址来执行DMA(直接内存访问).
二,一些体系结构的内存的物理地址比虚拟寻址范围大的多.这样就有一些内存不能永久的映射到内核空间.
因为存在这些制约条件,Linux主要使用了四个区:(到底哪四个区呢?,哈哈,详见下面)
(1) ZONE_DMA -这个区包含的页能够用来执行DAM操作.
(2) ZONE_DAM32 -这个区包含的页与ZONE_DAM类似,都可用来执行DMA操作; 不同的地方在于,这些页面只能被32位的设备访问,在某些体系结构中,该区将比ZONE_DMA更大.
(3) ZONE_NORMAL - 这个区包含的都是能正常映射的页.
(4) ZONE_HIGHEM -这个区包含"高端内存",其中的页并不能永久的映射到内核地址空间,这些区在<linux/mmzone.h>中定义
区的实际使用与分布是与体系结构相关的,例如在某些体系结构中在内存的任何地方执行DMA都没有问题,在这些体系结构中ZONE_DMA为空,ZONE_NORMAL就可以直接用于分配,在x86体系结构中,ZONE_DMA包含的页都是在0~16M的内存范围里.
ZONE_HIGEM的工作方式也差不多,能否直接映射取决与具体的体系结构,32位x86系统上,ZONE_HIGEM的为高于896MB的所有物理内存,但是在其他的体系结构中,由于内存的所有地址空间都能够被映射,所以ZONE_HIGEM为空,
ZONE_HIGEM所在的内存为高端地址空间,系统的其余内存就是所谓的低端地址空间,
x86-32上的区
区 描述 物理地址
ZONE_DMA DMA使用的页 <16M
ZONE_NORMAL 正常可寻址的页 16~896MB
ZONE_HIGHEM 动态映射的页 >896MB
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<一> 获得页的方法:
内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口,所有的这些接口都是以页为单位分配内存,定义在<linux/gfp.h>中,最核心的函数是:
1) struct page * alloc_pages(gfp_t gfp_mask, unsigned int order);
该函数是以2^order 次幂分配连续页的物理地址,返回的是指向第一个页的第一个page结构体;出错返回NULL
2) void* page_address(struct page *page)
返回参数struct page 的逻辑地址
3) unsigned long _get_free_pages( gfp_t gfp_mask, unsigned int order);
该函数与alloc_pages()作用相同,不过它直接返回的所请求的第一个页的逻辑地址,因为也是连续的,所以其他页也会紧随其后.
如果你只需要获得一页,可以使用如下函数进行
4) struct page* alloc_page(gfp_t gfp);
5) unsigned long _get_free_page(gfp_t gfp_mask);
获得填充为0 的页:
unsigned long get_zeroed_page(unsigned int gfp_mask);
<二> 释放页
1) void _free_pages(struct page *page , unsigned int order);
2) void free_pages(unsigned long addr,unsigned int order);
3)void free_page(unsigned long addr);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
看个例子吧(最直观)
unsigned long page;
page = _get_free_pages( GFP_KERNEL, 3);
if( !page){
/* 没有足够的内存,你必须处理这种错误*/
}
/* "page" 现在指向8个连续页中第一个页的逻辑地址*/
使用完成后释放这3个页:
free_pages( page, 3);
当你需要以页为单位的一簇连续的物理页时,尤其是你需要一两个页时,这些低级的页函数很实用,但是大多数情况在写驱动时我们都是以字节为单位分配的,内核提供的函数是kmalloc();
下节详细描述SLAB层..........................................