《Linux内核设计与实现》读书笔记(十二)

内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决)。

所有内核的内存管理必须要简洁而且高效。

主要内容:

1. 内存的管理单元

内存最基本的管理单元是页,同时按照内存地址的大小,大致分为3个区。

1.1 页

页的大小与体系结构有关,在 x86 结构中一般是 4KB或者8KB。

可以通过 getconf 命令来查看系统的page的大小:

[wangyubin@localhost ]$ getconf -a | PAGESIZE4096PAGE_SIZE4096_AVPHYS_PAGES637406_PHYS_PAGES2012863

以上的 PAGESIZE 就是当前机器页大小,即 4KB

页的结构体头文件是: <linux/mm_types.h> 位置:include/linux/mm_types.h

/* * 页中包含的成员非常多,还包含了一些联合体 * 其中有些字段我暂时还不清楚含义,以后再补上。。。 */struct page {unsigned atomic_t _count;union {atomic_t _mapcount; {u16 inuse;u16 objects;};};union {struct {unsigned address_space *mapping; };#if USE_SPLIT_PTLOCKSspinlock_t ptl;page *first_page; };union {pgoff_t index;*freelist;};defined(WANT_PAGE_VIRTUAL)/* WANT_PAGE_VIRTUAL */#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGSunsigned #ifdef CONFIG_KMEMCHECK/** kmemcheck wants to track the status of each byte in a page; this* is a pointer to such a status block. NULL if not tracked.*/void *shadow;#endif};

物理内存的每个页都有一个对应的 page 结构,看似会在管理上浪费很多内存,其实细细算来并没有多少。

比如上面的page结构体,每个字段都算4个字节的话,总共40多个字节。(union结构只算一个字段)

那么对于一个页大小 4KB 的 4G内存来说,一个有 4*1024*1024 / 4 = 1048576 个page,

一个page 算40个字节,在管理内存上共消耗内存 40MB左右。

如果页的大小是 8KB 的话,消耗的内存只有 20MB 左右。相对于 4GB 来说并不算很多。

1.2 区

页是内存管理的最小单元,但是并不是所有的页对于内核都一样。

内核将内存按地址的顺序分成了不同的区,有的硬件只能访问有专门的区。

内核中分的区定义在头文件 <linux/mmzone.h> 位置:include/linux/mmzone.h

内存区的种类参见 enum zone_type 中的定义。

内存区的结构体定义也在 <linux/mmzone.h> 中。

具体参考其中 struct zone 的定义。

其实一般主要关注的区只有3个:

描述

物理内存

ZONE_DMADMA使用的页<16MB

ZONE_NORMAL正常可寻址的页16~896MB

ZONE_HIGHMEM动态映射的页>896MB

某些硬件只能直接访问内存地址,不支持内存映射,对于这些硬件内核会分配 ZONE_DMA 区的内存。

某些硬件的内存寻址范围很广,比虚拟寻址范围还要大的多,那么就会用到 ZONE_HIGHMEM 区的内存,

对于 ZONE_HIGHMEM 区的内存,后面还会讨论。

对于大部分的内存申请,只要用 ZONE_NORMAL 区的内存即可。

2. 获取内存的方法

内核中提供了多种获取内存的方法,了解各种方法的特点,可以恰当的将其用于合适的场景。

2.1 按页获取 – 最原始的方法,用于底层获取内存的方式

以下分配内存的方法参见:<linux/gfp.h>

方法

描述

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,返回指向其逻辑地址的指针

alloc** 方法和 get** 方法的区别在于,一个返回的是内存的物理地址,一个返回内存物理地址映射后的逻辑地址。

如果无须直接操作物理页结构体的话,一般使用 get** 方法。

相应的释放内存的函数如下:也是在 <linux/gfp.h> 中定义的

extern void __free_pages(struct page *page, unsigned int order);extern void free_pages(unsigned long addr, unsigned int order);extern void free_hot_page(struct page *page);

在请求内存时,参数中有个 gfp_mask 标志,这个标志是控制分配内存时必须遵守的一些规则。

gfp_mask 标志有3类:(所有的 GFP 标志都在 <linux/gfp.h> 中定义)

行为标志主要有以下几种:

行为标志

描述

__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 的代码内部使用

区标志主要以下3种:

区标志

描述

__GFP_DMA从 ZONE_DMA 分配

__GFP_DMA32只在 ZONE_DMA32 分配 (注1)

__GFP_HIGHMEM从 ZONE_HIGHMEM 或者 ZONE_NORMAL 分配 (注2)

不要害怕错过什么,因为在路上你就已经收获了自由自在的好心情。

《Linux内核设计与实现》读书笔记(十二)

相关文章:

你感兴趣的文章:

标签云: