对于linux内存管理来说,都是管理内核的代码和数据段以外的内存空间。
bootmem:
bootmem是内核中使用的一种较简单的内存分配策略,它用于在系统启动时使用,在buddy等内存分配系统初始化完成后将不再使用。其基本思想是将SDRAM的可用存储空间分成许多页,每页的大小为4K,在分配时以页为单位分配,分配方法是从低往高找直到找到一块或连续多块满足大小要求的空闲页面为止。
页申请的核心调用是: __alloc_pages(). 页申请顺序如下:
首先找最大能符合的块, 如果一个空闲块不能满足, 更高一级的块将分割成2个buddy, 一个被占用, 一个放入低一级的freelist.
当块被释放时, 检查每对buddy, 如果两者都空闲, 把他们合并到更高一级的块数组里去, 同时放入更高一级的freelist. 如果一个buddy还在被用, 那此块将加入到当前级的freelist.
如果一个zone已经没有足够空闲页,而且又需要分配,那申请将退到下一级,一般顺序是:ZONE_HIGHMEM --> ZONE_NORMAL --> ZONE_DMA. 如果空闲页击中pages_low门限,kswapd将开始释放页。
http://hi.baidu.com/zengzhaonong/blog/item/bd4b15465d4104096b63e584.html
页面管理机制的初步建立
http://hi.baidu.com/mudgao/blog/item/57ebc03ebf065b3771cf6c73.html
每个pg_data_t数据结构代表着一片均匀的、连续的内存空间。在连续空间UMA结构中,只有一个节点contig_page_data,而在NUMA结构或不连续空间UMA结构中,有多个这样的数据结构。
计算三个管理区的大小,并存放在zones_size数组中。三个管理区是:
ZONE_DMA: 0-16MB (由于计算机系统的发展原因,老的ISA设备的DMA只能使用前16M的内存)
ZONE_NORMAL: 16MB-896MB (余下的就是ZONE_NORMAL了)
ZONE_HIGHMEM: 896MB以上(由于内核空间默认配置是1GB.高于1GB的物理地址要通过特殊的方式才能供内核使用.这就是ZONE_HIGHMEM)
arch/xxx/kernel/head.S
init/main.c: start_kernel()->setup_arch()
arch/xxx/kernel/setup.c->init_bootmem_node()
free_bootmem()
reserve_bootmem()
paging_init()
析armlinux-paig_init()->free_area_init_core()函数5-4
http://blog.chinaunix.net/u1/38994/showart_354178.html
从硬件角度看内存系统,有2种主流的体系结构,不一致的内存访问系统(NUMA),我不知道什么系统在用这样模式,这种系统将内存系统分割成2块区域(BANK),一块是专门给CPU去访问,一块是给外围设备板卡的DMA去访问。另外一种体系结构,是一致的内存访问系统(UMA),PC都是用的这种结构,这种结构的对于CPU和其他外围设备访问的内存在一块内存条上,没有任何不同。
LINUX内核需要支持这2种体系结构。它引入了一个概念称为node,一个node对应一个bank,对于UMA体系的,系统中只有一个node。在LINUX中引入一个数据结构“struct pglist_data”,来描述一个node,定义在include/linux/mmzone.h文件中。(这个结构被typedef pg_data_t)
对于NUMA系统来讲, 整个系统的内存由一个node_data的pg_data_t指针数组来管理。(因为可能有多个node)对于PC这样的UMA系统,使用struct pglist_data contig_page_data,作为系统唯一的node管理所有的内存区域。(UMA系统中中只有一个node)
每个node又被分成多个zone,它们各自描述在内存中的范围。zone由struct zone_struct数据结构来描述。zone的类型由zone_t表示,有ZONE_DMA, ZONE_NORMAL,ZONE_HIGHMEM这三种类型。它们之间的用途是不一样的,ZONE_DMA类型的内存区域在物理内存的低端,主要是ISA设备只能用低端的地址做DMA操作。ZONE_NORMAL类型的内存区域直接被内核映射到线性地址空间上面的区域(line addressspace),以后的章节将详细描述。ZONE_HIGHMEM将保留给系统使用。
在PC系统中,内存区域类型如下分布:
ZONE_DMA 0-16MB
ZONE_NORMAL 16MB-896MB
ZONE_HIGHMEM 896MB-物理内存结束
大多数kernel的操作只使用ZONE_NORMAL区域,
系统内存由很多固定大小的内存块组成的,这样的内存块称作为“页”(PAGE),x86体系结构中,page的大小为4096个字节。每个物理的页由一个structpage的数据结构对象来描述。页的数据结构对象都保存在mem_map全局数组中。从载入内核的低地址内存区域的后面内存区域,也就是ZONE_NORMAL开始的地方的内存的页的数据结构对象,都保存在这个全局数组中。
因为ZONE_NORMAL区域的内存空间也是有限的,所以LINUX也支持High memory的访问,这个下面章节会描述,这个章节,将主要描述
node,zone,page及它们之间的关联。
http://blog.csdn.net/yrj
http://blog.csdn.net/yrj/archive/2008/05/06/2401235.aspx
三读bootmem
http://blog.csdn.net/lights_joy/archive/2008/07/24/2704788.aspx
尽管不是很必要,Linux2.4同时支持NUMA及UMA模型。
在NUMA模型中,由于CPU对不同存储器单元的访问时间可能不一样,系统的物理内存被分成不同的节点,并假设每个节点有相同的访问时间。
节点的类型描述符pg_data_t有以下两个重要的对象node_zones及node_zonelists需要重点了解,node_zones数组中保存的是该节点所拥有的管理区描述符,对Linux而言是DMA、NORMAL、HIGHMEM。
node_zonelists是比较今人困惑的,它表示
对管理区的一种分配策略,也就是说node_zonelists的每一项代表着管理区描述符的不同组合,该数组有16个元素,但实际上只使用了以下四个:
__GFP_DMA __GFP_HIGHMEM 管理区列表
0 0 ZONE_NORMAL + ZONE_DMA
0 1 ZONE_HIGHMEM + ZONE_NORMAL + ZONE_DMA
1 0 ZONE_DMA
1 1 ZONE_DMA
可以看出,当node_zonelists不同时,管理区的分配策略也是不同的,函数build_zonelists对node_zonelists进行初始化,结果如下:
0 ZONE_NORMAL + ZONE_DMA
1 ZONE_DMA
2 ZONE_HIGHMEN + ZONE_NORMAL + ZONE_DMA
3 ZONE_DMA
举例说明:
如下所示,Linux页分配机制的核心是_alloc_pages,它实际上是__alloc_pages的封装函数,__alloc_pages的第三个参数zonelist代表着管理区分配策略。
struct page *_alloc_pages(unsigned int gfp_mask, unsigned int order)
{
return __alloc_pages(gfp_mask, order,
contig_page_data.node_zonelists+(gfp_mask & GFP_ZONEMASK));
}
VM管理与页面周转
http://hi.baidu.com/liu_bin0101/blog/item/b13cb0d9b2d6342b11df9b9f.html
函数原型:
alloc_pages(unsigned int gfp_mask, unsigned int order);
struct page *alloc_page(unsigned int gfp_mask);
其中alloc_pages()来了分配多页内存,而alloc_page()只用来分配单页内存.
在内核中,alloc_page就是调用了alloc_pages(gfp_mask,0)来实现的
gfp_mask表示分配的标志.
http://blog.chinaunix.net/space.php?uid=20543183&do=blog&id=1930772
http://blog.chinaunix.net/space.php?uid=20543183&do=blog&cuid=471183
关于kmalloc,vmalloc及kmap函数讲解:
1. kmalloc()是内核中最常见的内存分配方式,它最终调用伙伴系统的__get_free_pages()函数分配,根据传递给这个函数的flags参数,决定这个函数的分配适合什么场合,如果标志是GFP_KERNEL则仅仅可以用于进程上下文中,如果标志GFP_ATOMIC则可以用于中断上下文或者持有锁的代码段中。
kmalloc返回的线形地址是直接映射的,而且用连续物理页满足分配请求,且内置了最大请求数(2**5=32页)。
2. kmap()是主要用在高端存储器页框的内核映射中,一般是这么使用的:
使用alloc_pages()在高端存储器区得到struct page结构,然后调用kmap(struct *page)在内核地址空间PAGE_OFFSET+896M之后的地址空间中(PKMAP_BASE到FIXADDR_STAR)建立永久映射(如果page结构对应的是低端物理内存的页,该函数仅仅返回该页对应的虚拟地址)
kmap()也可能引起睡眠,所以不能用在中断和持有锁的代码中
不过kmap 只能对一个物 理页进行分配,所以尽量少用。
3. vmalloc()优先使用高端物理内存,但性能上会打些折扣。
vmalloc分配的物理页不会被交换出去;
vmalloc返回的虚地址大于(PAGE_OFFSET + SIZEOF(phys memory) + GAP),为VMALLOC_START----VMALLOC_END之间的线形地址;
vmalloc使用的是vmlist链表,与管理用户进程的vm_area_struct要区别,而后者会swapped;
4、 使用kmap的原因:
对于高端物理内存(896M之后),并没有和内核地址空间建立一一对应的关系(即虚拟地址=物理地址+PAGE_OFFSET这 样的关系),所以不能使用get_free_pages()这样的页分配器进行内存的分配,而必须使用alloc_pages()这样的伙伴系统算 法的接口得到struct *page结构,然后将其映射到内核地址空间,注意这个时候映射后的地址并非和物理地址相差PAGE_OFFSET.
高端内存映射有三种方式:
1、映射到“内核动态映射空间”
这种方式很简单,因为通过 vmalloc() ,在"内核动态映射空间"(上图的VMALLOC_START到VMALLOC_END)申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到"内核动态映射空间" 中。
2、永久内核映射
如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从PKMAP_BASE到FIXADDR_START (上图的倒数第二块区域),用于映射高端内存。在2.4内核上,这个地址范围是4G-8M到4G-4M之间。这个空间起叫“内核永久映射空间”或者“永久内核映射空间”。
这个空间和其它空间使用同样的页目录表,对于内核来说,就是swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。
通常情况下,这个空间是4M大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(), 可以把一个 page 映射到这个空间来。由于这个空间是4M大小,最多能同时映射1024 个 page。因此,对于不使用的的page,及应该时从这个空间释放掉(也就是解除映射关系),通过kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。
3、临时映射
内核在FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间”
在这个空间中,有一部分用于高端内存的临时映射。
这块空间具有如下特点:
1、每个CPU 占用一块空间
2、在每个CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是1 个 page,每个小空间用于一个目的,这些目的定义在kmap_types.h 中的 km_type 中。
当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。
通过 kmap_atomic() 可实现临时映射。
kmem_cache_create,kmem_cache_alloc,kmem_cache_free
在内核编程中,可能经常会有一些数据结构需要反复使用和释放,按照通常的思路,可能是使用kmalloc和kfree来实现。
通过先使用kmem_cache_create函数创建一个高速缓存的头指针——在内核中是struct kmem_cache结构,具体用法可以这样:
struct kmem_cache * cachep = NULL ;
cachep = kmem_cache_create( "cache_name" , sizeof ( struct yourstruct) , 0, SLAB_HWCACHE_ALIGN, NULL , NULL ) ;
这样我们就获得了一个可用的cachep头指针。
当需要分配一个struct yourstruct的结构体空间时,我们只需要调用kmem_cache_alloc函数,就可以获得一个足够我们使用的空间的指针(为什么我要说足够 呢?因为刚才的声明中我使用了一个标志——SLAB_HWCACHE_ALIGN,这个标志会让分配的空间对于硬件来说是对齐的,而不一定恰好等于 sizeof(struct yourstruct)的结果)。范例代码如下:
struct yourstruct * bodyp = NULL ;
bodyp = ( struct yourstruct * ) kmem_cache_alloc( cachep, GFP_ATOMIC & ~ __GFP_DMA) ;
这样就可以使用bodyp指针所对应的空间存贮你需要的结构体信息了。
当用完结束后,我们需要释放空间,如何操作呢?代码很简单:
kmem_cache_free( cachep, bodyp) ;
但是这种方式效率不高,Linux为我们提供了更加高效的方法——Slab高速缓存管理器