内存管理分两类:
一、对物理内存的管理:对实际物理内存的管理;
二、对虚拟内存的管理:即对特定处理器体系架构上虚拟地址空间的管理;
linux系统为了使代码有最大程度的兼容性,在物理内存管理引入下面概念:
内存节点(node);
内存区域(zone);
内存页(page);
2.1对物理内存的管理:
对物理内存的管理分2部分:
内存节点:node
UMA系统:只有一个节点;
NUMA系统:多个节点的链表:struct pglist_data;
内存区域:struct zone;
是对单个内存节点中的概念,内存区域有不同的类型:
enum zone_type {
ZONE_DMA, /*arm体系上,大小可变;*/
ZONE_DMA32, /*x86体系用*/
ZONE_NORMAL, /*常规区域*/
ZONE_HIGHMEM, /*高端区域,该区域无法从内核虚拟地址直接作线性映射,必须做页映射。
ZONE_MOVABLE,
__MAX_NR_ZONES
}
文件位置:linux/mmzone.h
Struct zone{
Spinlock_t lock;
Unsigned long free_pages;
Unsigned long free_min;
Unsigned long free_low;
Unsigned long free_high;
Unsigned long protection[MAX_NR_ZONES];
Spinlock_t lru_lock;
Struct list_head active_list;
Strcut list_head inactive_list;
Unsigned long nr_scan_active;
Unsigned long nr_scan_inactive;
Unsigned long nr_active;
Unsigned long nr_inactive;
Int all_unreclainable;
Unsigned long page_scanned;
Int temp_priority;
Int prev_priority;
Struct free_area free_area[MAX_ORDER];
Wait_queue_head_t *wait_table;
Unsigned long wait_table_size;
Unsigned long wait_table_bit;
Struct per_cpu_pageset pageset[NR_CPUS];
Struct pglist_data *zone_pgdat;
Struct page *zone_mem_map;
Unsigned long zone_start_pfn;
Char *name;
Unsigned long spanned_pages;
Unsigned long present_pages;
}
这个结构很大,但系统只有三个区,因此也只有三个这样的结构。
Lock域是自旋锁,防止该结构被并发访问。这个域只保护结构,不保护驻留在这个区中的所有页。没有锁可以锁住单个页,但部分内核可锁住页中驻留的数据。
Free_pages域是这个区中空闲的页数,内核尽可能保证(通过交换)pages_min个空闲页可用。
Name域是一个以null结束的字符串,表示这个区的名字,内核启动时初始化这个值,代码在mm/page_alloc.c中,三个区的名字:“DMA”“Normal”和HighMem。
2.1 内存分页:
物理内存的最小单位,也叫页帧(page frame)。
页的大小取决于系统中的内存管理单元MMU:
32位:4k/页;
64位:8k/页;
定义:
linux/mm.h:
Struct page {
Page_flags_t flags; 页状态:是否脏,是否被锁定等32种;()
Atomic_t _count; 页引用计数;计数为0,则可被申请;
Atomic_t _mapcount;
Unsigned long private;
Struct address_space *mapping;
Pgoff_t index;
Struct list_head lru;
Void *virtual; 页的虚拟地址。对高端内存,该值为null,需要时动态映射这些页;
};
Page是和物理页对应,它描述物理内存本身,并不描述其中的数据;
系统用一个全局变量struct page *mem_map来存放所有物理页对象指针。
2.2 内存page接口:
申请接口:
Alloc_page(gfp_mask) 只分一个页,返回指向页结构的指针;
Alloc_pages(gfp_mask,order) 只分2 (order次方)页,返回指向第一页结构的指针;
_get_free_page(gfp_mask) 只分一个页,返回指向逻辑地址的指针;
__get_free_pages(gfp_mask,order)只分2 (order次方)页,返回指向第一页逻辑地址的指针;
Get_zeroed_page(gfp_mask) 只分一页,让其内容填0,返回指向其逻辑地址的指针;
因为mem_map中每一个struct page对象和物理页面一一对应,这使得mem_map也分三个区,Linux初始化期间,会将虚拟地址空间的页面直接映射区作线性映射到ZONE_DMA和ZONE_NORMAL,如果页面分配器分配的页落在这两个zone中,那么对应的内核虚拟地址到物理地址的映射的页目录表项已经建立,线性映射就是虚拟地址和物理地址之间只有一个差值(PAGE_OFFSET= 0xc0000000);
如果所分配的页面在ZONE_HIGH中,那么内核这时还没有对该页面的映射,所有页面分配器的调用者(如设备驱动程序)需要在内核虚拟地址空间的动态映射区分配一个虚拟地址,然后映射到该物理页面上,内核提供了接口函数。
2.3 gfp_mask :控制分配行为的掩码,并可以告诉内核在哪个zone中分配物理页面。
<include/linux/gpf.h> 以下定义供内核自己用
#define __GFP_DMA ((__force gfp_t)0x01u)
#define __GFP_HIGHMEM ((__force gfp_t)0x02u)
.......
<include /linux/gfp.h> 以下定义供驱动程序用
#define GFP_ATOMIC (__GFP_HIGH)
#define GFP_KERNEL (__GFP_WAIT|__FPG_IO|__GFP__FS)
GFP_ATOMIC:
内核模块中最常使用的掩码之一,用于原子分配,所以它是不带__GFP_WAIT的。此掩码告诉分配器,在分配页面时候,绝对不能中断当前进程或者把当前进程移出调度器。必要的情况下可以使用仅限紧急情况使用的保留内存页。
在驱动程序中,一般在中断处理例程或非进程上下文的代码中使用GFP_ATOMIC掩码进行内存分配,因为这两种情况下分配必须保证当前进程不能睡眠。
GFP_KERNEL:
内核模块中最常用的掩码之一,可能导致当前进程进入睡眠状态。
GFP_USET:
用于为用户空间分配页,也可能引起进程休眠。
GFP_NOIO
GFP_NOFS
都带有__GFP_WAIT,因此可以被中断,前者分配中禁止io,后者禁止文件系统函数调用;
GFP_HIGHUSET
对GFP_USET的扩展,可以使用非线性映射的高端内存;
GFP_DMA
只能在DMA区域分配空闲的物理页面;
一、 以字节为单位的分配接口:
Linux/slab.h:
Kmalloc()得到以字节为单位的内核内存,物理上是连续的;
Kfree()
Vmalloc() 得到以字节为单位的内核内存,虚拟地址是连续的,物理上不一定是连续的;
Vfree ()
五、 slab层: