一、页page
内核把物理页作为内存管理的基本单元。内存管理单元(MMU)通常以页为单位进行处理。从虚拟内存的角度看,页就是最小单位。
32位体系结构支持4KB的页,而64位的体系结构支持8KB的页。
内核用struct page结构来表示系统中的每个物理页。
二、区zone
由于硬件的限制,内核就把所有的页划分成不同的区(zone),Linux主要有四种分区:
- ZONE_DMA–该区包含的页能用来执行DMA操作
- ZONE_DMA32–也可用来执行DMA操作,但是只能被32位设备访问
- ZONE_NARMAL–该区包含的是能正常映射的页
- ZONE_HIGHMEM–高端内存,并不能永久映射到内核地址空间
x86-32上,分区情况:
区 | 描述 | 物理内存 |
---|---|---|
ZONE_DMA | DMA使用的页 | <16M |
ZONE_NORMAL | 正常可寻址的页 | 16~896M |
ZONE_HIGHMEM | 动态映射的页 | 大于896M |
三、获得页
static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
该函数分配2的order次方个连续的物理页,返回指向第一个页的结构体的指针。
void *page_address(const struct page *page)
将指定的页转换成它的逻辑地址。
若无需用到struct page,就可以调用:
extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
//注意,该函数要进行错误检查,分配失败必须进行相应处理
该函数直接返回请求的第一个页的逻辑地址。
如果只需要一页,就可以用一下两个函数:
struct page * alloc_page(gdp_t gfp_mask)
unsigned long __get_free_page(gfp_t gfp_mask);
如果你需要让返回的页的内容全为0,请用下面这个函数:
unsigned long get_zeroed_page(gfp_t gfp_mask);
释放页:
void __free_pages(struct page *page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
void free_page(unsigned long addr);
释放时需要谨慎,仅释放属于你的页,释放错了地址或者用错了order值可能导致系统崩溃。
四、kmalloc
kmalloc()函数与用户空间的malloc函数类似,只不过多了一个flags参数。该函数是申请获得以字节为单位的一块内核内存。分配的内存在物理上是连续的。
void *kmalloc(size_t size, gfp_t flags)
void kfree(const void *ptr)
gfp_mask标志:
这些标志分为三类:行为修饰符、区修饰符、类型标志
行为标志 | 描述 |
---|---|
__GFP_WAIT | 分配器可以睡眠 |
__GFP_HIGH | 分配器可以访问紧急事件缓冲池 |
__GFP_IO | 分配器可以启动磁盘I/O |
__GFP_FS | 分配器可以启动文件系统I/O |
__GFP_COLD | 分配器应该使用高速缓存中快要淘汰出去的页 |
__GFP_NOWARN | 分配器将不打印失败警告 |
__GFP_REPEAT | 分配器在分配失败时重复进行分配,但是这次分配还存在失败的可能 |
__GFP_NOFALL | 分配器将无限的重复进行分配。分配不能失败 |
__GFP_NORETRY | 分配器在分配失败时不会重新分配 |
__GFP_NO_GROW | 由slab层内部使用 |
__GFP_COMP | 添加混合页元数据,在 hugetlb 的代码内部使用 |
区标志 | 描述 |
---|---|
__GFP_DMA | 从 ZONE_DMA 分配 |
__GFP_DMA32 | 只在 ZONE_DMA32 分配 (注1) |
__GFP_HIGHMEM | 从 ZONE_HIGHMEM 或者 ZONE_NORMAL 分配 |
类型标志 | 描述 |
---|---|
GFP_ATOMIC | 这个标志用在中断处理程序,下半部,持有自旋锁以及其他不能睡眠的地方 |
GFP_NOWAIT | 与 GFP_ATOMIC 类似,不同之处在于,调用不会退给紧急内存池。这就增加了内存分配失败的可能性 |
GFP_NOIO | 这种分配可以阻塞,但不会启动磁盘I/O。这个标志在不能引发更多磁盘I/O时能阻塞I/O代码,可能会导致递归 |
GFP_NOFS | 这种分配在必要时可能阻塞,也可能启动磁盘I/O,但不会启动文件系统操作。这个标志在你不能再启动另一个文件系统的操作时,用在文件系统部分的代码中 |
GFP_KERNEL | 这是常规的分配方式,可能会阻塞。这个标志在睡眠安全时用在进程上下文代码中。为了获得调用者所需的内存,内核会尽力而为。这个标志应当为首选标志 |
GFP_USER | 这是常规的分配方式,可能会阻塞。用于为用户空间进程分配内存时 |
GFP_HIGHUSER | 从 ZONE_HIGHMEM 进行分配,可能会阻塞。用于为用户空间进程分配内存 |
GFP_DMA | 从 ZONE_DMA 进行分配。需要获取能供DMA使用的内存的设备驱动程序使用这个标志。通常与以上的某个标志组合在一起使用。 |
五、vmalloc()
vmalloc函数的工作方式与kmalloc类似,只不过vmalloc分配的虚拟地址是连续的,但物理地址无需连续。
通过分配非连续的物理内存块,再修正页表,把内存映射到连续的虚拟地址空间中。这就带来一些消耗,带来性能上的影响,仅仅在不得已时才会使用。
void *vmalloc(unsigned long size)
void vfree(const void *addr)