<<Linux内核设计与实现>>读书笔记(十二)-内存管理
来源:互联网 发布:mac u盘 没有退出选项 编辑:程序博客网 时间:2024/06/15 21:38
内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决).
所有内核的内容管理必须简洁而且高效.
主要内容:
- 内存的管理单元
- 获取内存的方法
- 获取高端内存
- 内核内存的分配方式
- 总结
1.内存的管理单元
内存最基本的管理单元是页,同时按照内存地址的大小,大致分为3个区.
1.1 页
页的大小与体系结构有关,在 x86 结构中一般是 4KB 或者 8KB.
可以通过getconf命令来查看系统的page的大小.
[wangyubin@localhost ]$ getconf -a | grep -i 'page'PAGESIZE 4096PAGE_SIZE 4096_AVPHYS_PAGES 637406_PHYS_PAGES 2012863
以上的PAGESIZE就是当前机器页的大小,即4KB.
页的结构体头文件是:linux/mm_types.h 位置:include/linux/mm_types.h
/* * 页中包含的成员非常多,还包含了一些联合体 * 其中有些字段我暂时还不清楚含义,以后再补上。。。 */struct page { unsigned long flags; /* 存放页的状态,各种状态参见<linux/page-flags.h> */ atomic_t _count; /* 页的引用计数 */ union { atomic_t _mapcount; /* 已经映射到mms的pte的个数 */ struct { /* 用于slab层 */ u16 inuse; u16 objects; }; }; union { struct { unsigned long private; /* 此page作为私有数据时,指向私有数据 */ struct address_space *mapping; /* 此page作为页缓存时,指向关联的address_space */ };#if USE_SPLIT_PTLOCKS spinlock_t ptl;#endif struct kmem_cache *slab; /* 指向slab层 */ struct page *first_page; /* 尾部复合页中的第一个页 */ }; union { pgoff_t index; /* Our offset within mapping. */ void *freelist; /* SLUB: freelist req. slab lock */ }; struct list_head lru; /* 将页关联起来的链表项 */#if defined(WANT_PAGE_VIRTUAL) void *virtual; /* 页的虚拟地址 */#endif /* WANT_PAGE_VIRTUAL */#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS unsigned long debug_flags; /* Use atomic bitops on this */#endif#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 的 4GB内存来说,一共有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_DMA区的内存.
某些硬件的内存寻址范围很广,比虚拟寻址范围还要大得多,那么就会用到ZONE_HIGHMEM区的内存.
对于ZONE_HIGHMEM区的内存,后面还会讨论.
对于大部分的内存申请,只要ZONE_NORMAL区的内存即可.
2.获取内存的方法
内核中提供了多种获取内存的方法,了解各种方法的特点,可以恰当地将其用于合适的场景.
2.1 按页获取-最原始的方法,用于底层获取内存的方式
以下分配内存的方法参见:linux/gfp.h
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中定义)
- 行为标志:控制分配内存时,分配器的一些行为
- 区标志:控制内存分配在哪个区(ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM之类)
- 类型标志:由上面2种标志组合而成的一些常用的场景
行为标志主要有以下几种:
区标志主要有以下3种:
注1:ZONE_DMA32 和 ZONE_DMA 类似,该区包含的页也可以进行DMA操作.
唯一不同的地方在于,ZONE_DMA32 区的页只能被32位设备访问.
注2:优先从 ZONE_HIGHMEM 分配,如果 ZONE_HIGHMEM 没有多于的页则从 ZONE_NORMAL 分配.
类型标志是编程中最常用的,在使用标志时,应首先看看类型标志中是否有合适的,如果没有,再去自己组合 行为标志和区标志.
这就增加了内存分配失败的可能性 GFP_NOIO __GFP_WAIT 这种分配可以阻塞,但不会启动磁盘IO.
这个标志在不能引发更多磁盘IO时能阻塞IO代码,可能会导致递归 GFP_NOFS (__GFP_WAIT|__GFP_IO) 这种分配在必要时可能阻塞,也可能启动磁盘IO,但不会启动文件系统操作.
这个标志在你不能再启动另一个文件系统的操作时,用在文件系统部分的代码中 GFP_KERNEL (__GFP_WAIT|__GFP_IO|GFP_FS) 这是常规的分配方式,可能会阻塞.这个标志在睡眠安全时用在进程上下文代码中.
为了获得调用者所需的内存,内核会尽力而为.这个标志应当为首选标志 GFP_USER (__GFP_WAIT|__GFP_IO|__GFP_FS) 这是常规的分配方式,可能会阻塞.用于为用户空间进程分配内存时 GFP_HIGHUSER ((__GFP_WAIT|__GFP_IO|__GFP_GS)|GFP_HIGHMEM) 从ZONE_HIGHMEM进行分配,可能会阻塞.用于为用户空间进程分配内存 GFP_DMA __GFP_DMA 从ZONE_DMA进行分配.需要获取能供DMA使用的内存的设备驱动程序使用这个标志.通常与以上的某个标志组合在一起使用
以上各种类型标志的使用场景总结:
2.2 按字节获取-用的最多的获取方法
这种内存
分配方法是平时使用比较多的,主要有2种分配方法:kmalloc()和vmalloc()
kmalloc的定义在 linux/slab_def.h中
/** * @size - 申请分配的字节数 * @flags - 上面讨论的各种 gfp_mask */static __always_inline void *kmalloc(size_t size, gfp_t flags)#+end_srcvmalloc的定义在 mm/vmalloc.c 中#+begin_src C/** * @size - 申请分配的字节数 */void *vmalloc(unsigned long size)
kmalloc 和 vmalloc 区别在于:
- kmalloc 分配的内存物理地址是连续的,虚拟地址也是连续的
- vmalloc 分配的内存物理地址是不连续的,虚拟地址是连续的
因此在使用中,用得比较多的还是kmalloc,因为kmalloc 的性能较好.
因为在kmalloc的物理地址和虚拟地址之间的映射比较简单,只需要将物理地址的第一页和虚拟地址的第一页关联起来即可.
而vmalloc由于物理地址是不连续的,所以要将物理地址的每一页都和虚拟地址关联起来才行.
kmalloc 和 vmalloc 所对应的释放内存的方法分别为:
void kfree(const void *)void vfree(const void *)
2.3 slab层获取-效率最高的获取方法
频繁的分配/释放内存必然导致系统性能的下降,所以有必要为频繁分配/释放的对象类型建立缓存.
而且,如果能为每个处理器建立专门的高速缓冲,还可以避免SMP锁带来的性能损耗.
2.3.1 slab层实现原理
linux中的高速缓冲是用所谓slab层来实现的,slab层即内核中管理高速缓存的机制.
整个slab层的实现原理如下:
- 可以在内存中建立各种对象的高速缓存(比如进程描述相关的结构 task_struct 的高速缓冲)
- 除了针对特定对象的高速缓冲以外,也有通用对象的高速缓存
- 每个高速缓存中包含多个slab,slab用于管理缓存的对象
- slab中包含多个缓存的对象,物理页上由一页或多个连续的页组成
高速缓冲->slab->缓存对象之间的关系如下图:
2.2.3 slab层的应用
slab结构体的定义参见:mm/slab.c
struct slab { struct list_head list; /* 存放缓存对象,这个链表有 满,部分满,空 3种状态 */ unsigned long colouroff; /* slab 着色的偏移量 */ void *s_mem; /* 在 slab 中的第一个对象 */ unsigned int inuse; /* slab 中已分配的对象数 */ kmem_bufctl_t free; /* 第一个空闲对象(如果有的话) */ unsigned short nodeid; /* 应该是在 NUMA 环境下使用 */
slab层的应用主要有四个方法:
- 高速缓存的创建
- 从高速缓存中分配对象
- 从高速缓缓存中释放对象
- 高速缓存的销毁
/** * 创建高速缓存 * 参见文件: mm/slab.c * 这个函数的注释很详细,这里就不多说了。 */struct kmem_cache *kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))/** * 从高速缓存中分配对象也很简单 * 函数参见文件:mm/slab.c * @cachep - 指向高速缓存指针 * @flags - 之前讨论的 gfp_mask 标志,只有在高速缓存中所有slab都没有空闲对象时, * 需要申请新的空间时,这个标志才会起作用。 * * 分配成功时,返回指向对象的指针 */void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)/** * 向高速缓存释放对象 * @cachep - 指向高速缓存指针 * @objp - 要释放的对象的指针 */void kmem_cache_free(struct kmem_cache *cachep, void *objp)/** * 销毁高速缓存 * @cachep - 指向高速缓存指针 */void kmem_cache_destroy(struct kmem_cache *cachep)
我做了创建高速缓存额例子,来尝试使用上面的几个函数.
测试代码如下:(其中用到的 kn_common.h 和 kn_common.c参见之前的博客《Linux内核设计与实现》读书笔记(六)- 内核数据结构).
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- 《Linux内核设计与实现》读书笔记(十二)- 内存管理
- <<Linux内核设计与实现>>读书笔记(十二)-内存管理
- Linux内核设计与实现读书笔记十二——内存管理(内附思维导图)
- linux内核设计与实现读书笔记——内存管理
- Linux内核设计与实现(十二)——内存管理
- 《Linux内核设计与实现》读书笔记(一)--进程管理
- 《Linux内核设计与实现》- 内存管理
- linux内核设计与实现--内存管理
- linux内核设计与实现(第十二章)----内存管理
- Linux内核设计与实现读书笔记(2)-进程管理
- 进程管理——linux内核设计与实现读书笔记
- 《Linux内核设计与实现》读书笔记之进程管理
- cachecloud部署详细过程
- Android-进程与线程
- sklearn中的交叉验证(Cross-Validation)
- CacheCloud管理平台
- Codeforces Round #413 B. T-shirt buying
- <<Linux内核设计与实现>>读书笔记(十二)-内存管理
- web.js.创建对象
- Spring的两种事务定义方式
- 数据持久化--userDefaults
- mysql中varchar(50)最多能存多少个汉字
- opencv与tesseract
- spark-streaming kafka api(KafkaUtils.createDirectStream)使用
- javascript简单计算器
- centos7安装mysql