LDD读书笔记第八章-分配内存

kmalloc函数的内幕

kmalloc函数在没有阻塞的情况下运行速度很快。
并且不会对申请到的内存区域清空,所以在使用时必须清空。
函数原型

1
2
#include <linux/slab.h>
void *kmalloc(size_t size, int flags);

第一个参数size 表明要申请的内存区域大小。
flags是分配标志,能以多种方法控制kmalloc的行为。

flags参数

带”__“的标志可与不带的版本一起“或”来使用。

GFP_ATOMIC
用于在中断处理列程或运行在非进程上下文的代码(也就是不能休眠的代码)中使用,不会休眠。
GFP_KERNEL
内核一般的分配内存的方法,可能会休眠
GFP_USER
为用户空间分配内存时使用,可能会休眠
GFP_HIGHUSER
和GFP_USER相似,但是会分配高端内存。
GFP_NOIO
GFP_NOFS
类似于GFP_KERNEL但是有少许限制,GFP_NOFS禁止执行任何文件系统调用,GFP_NOIO禁止任何IO初始化。
__GFP_DMA
请求分配发生在可进行DMA的内存区段中。
__GFP_HIGHMEM
可位于高端内存
__GFP_COLD
会申请“冷”页面。
DMA buffers.
__GFP_NOWARN
避免内核在无法分配时产生警告。
__GFP_HIGH
高优先级申请,可消耗,内核为紧急情况保留的最后一点页面。
__GFP_REPEAT
申请失败时再试一次。
__GFP_NOFAIL
申请失败时始终不返回失败,努力满足分配需求。
__GFP_NORETRY
如果不可获得就立即返回

内存区段

内存的三个区段:可用于DMA的内存,常规内存,高端内存。
通常的内存申请发生在常规内存里
高端内存是为了在32位平台上访问大量的内存而用的一种机制。
需要进行映射,比较难处理
DMA区段为可用于DMA的特别地址范围的内存,外设可以使用这些内存执行DMA。

Direct Memory Access(存储器直接访问)。这是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据,既不通过CPU,也不需要CPU干预。整个数据传输操作在一个称为”DMA控制器”的控制下进行的。CPU除了在数据传输开始和结束时做一点处理外,在传输过程中CPU可以进行其他的工作。这样,在大部分时间里,CPU和输入输出都处于并行操作。因此,使整个计算机系统的效率大大提高。

后备高速缓存

如果需要反复申请相同大小的内存区域。可使用后备高速缓存。
后备高数缓存有时称为“slab分配器”

1
2
3
4
5
6
7
8
9
#include <linux/slab.h>
 
kmem_cache_t *kmem_cache_create(const char *name, size_t size,
		size_t offset,
		unsigned long flags,
		void (*constructor)(void *, kmem_cache_t *,
				unsigned long flags),
		void (*destructor)(void *, kmem_cache_t *,
				unsigned long flags));

大小由size参数指定。name于这个高速缓存相关联,其功能是保管一些信息以便追中问题。
offset是第一个对象在页面中的偏移,用于进行某种对齐。
flags是控制如何完成分配,是个位掩码,可取如下值。
SLAB_NO_REAP
保护高速缓存在系统寻找内存时不会被减少。
SLAB_HWCACHE_ALIGN
所有数据对象跟高速缓存行对其,在SMP中如果经常访问某个数据的话可提高性能,但是空白填充会浪费内存。
SLAB_CACHE_DMA
数据对象都从可用于DMA的内存区段中匹配。
constructor和destructor是可选项。前者用于初始化后者用于清除。如果不指定应写NULL。

建立完成后可申请和释放内存。

1
2
3
4
5
/*申请与释放*/
void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
void kmem_cache_free(kmem_cache_t *cache, const void *obj);
/*释放高速缓存,可能失败,失败时可能造成内存泄漏*/
int kmem_cache_destroy(kmem_cache_t *cache);

内存池

在某些不允许申请内存失败的情况下使用内存池。
内存池是某种形式的后备高速缓存,它试图始终保持空闲的内存,以便在紧急情况下使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mempool_t *mempool_create(int min_nr,
		mempool_alloc_t *alloc_fn,
		mempool_free_t *free_fn,
		void *pool_data);
/*nr是保持的最小数目*/
/*alloc_fn和free_fn分别是负责分配和释放的函数,原型为*/
typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);
typedef void (mempool_free_t)(void *element, void *pool_data);
/*通常让slab分配器帮我们处理这些任务,使用mempool_alloc_slab 和 mempool_free_slab
示列代码为:*/
cache = kmem_cache_create(. . .);
pool = mempool_create(MY_POOL_MINIMUM,
	mempool_alloc_slab, mempool_free_slab,cache);
/*用如下代码申请或释放内存*/
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
/*调整内存池大小*/
int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
/*释放内存池*/
void mempool_destroy(mempool_t *pool);

get_free_page和相关函数

需要大量内存的时候使用页面来分配。
分配函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
get_zeroed_page(unsigned int flags);
/*申请一个页面并清零*/
__get_free_page(unsigned int flags);
/*申请一个页面但不清零*/
__get_free_pages(unsigned int flags, unsigned int order);
/*申请连续的几个页面,不清零*/
/*flag参数和kmalloc一样,order是需要申请的页面的数量以2为底数的对数*/
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
/*第一个是个宏展开成第二个函数*/
 
<h4>alloc_pages接口</h4>
 
 
struct page是内核用来描述单个页面的数据结构。
 
<pre lang="C">/*申请*/
struct page *alloc_pages_node(int nid, unsigned int flags,
	unsigned int order);
struct page *alloc_pages(unsigned int flags, unsigned int order);
struct page *alloc_page(unsigned int flags);
/*释放*/
void __free_page(struct page *page);
void __free_pages(struct page *page, unsigned int order);
void free_hot_page(struct page *page);
void free_cold_page(struct page *page);

vmalloc及其补助函数

vmalloc提供连续的虚拟内存空间,这些内存空间在物理上不是连续的。使用时会通过MMU处理转换成物理内存地址。

相关函数,vmalloc一般使用在软件中需要大量缓冲区的情况。

1
2
3
4
void *vmalloc(unsigned long size);
void vfree(void * addr);
void *ioremap(unsigned long offset, unsigned long size);
void iounmap(void * addr);

kmalloc和get_free_pages返回的地址也是虚拟的。但是地址范围和物理地址是一一对应的。

可能基于某个偏移地址来实现。所以不需要为地址修改也表,但是vmalloc在物理地址中是不连续的。

需要通过页表来访问物理地址,vmalloc可以获得的地址在VMALLOC_START和VMALLOC_END之间,这两个符号在asm/pgtable.h中。

vmalloc的地址只能使用在处理器中,不能使用在如dma的场合。

ioremap用于映射指定的内存区域。

per-CPU变量

当建立一个per-CPU变量时,系统中每个处理器都用该变量的特定副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <linux/percpu.h>
 
/*建立percpu变量*/
DEFINE_PER_CPU(type, name);
void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);
/*获取和更新数据*/
get_cpu_var(sockets_in_use);
put_cpu_var(sockets_in_use);
per_cpu(variable, int cpu_id);
per_cpu_ptr(void *per_cpu_var, int cpu_id);
/*使用动态非配的变量时使用get_cpu和put_cpu来阻止抢占*/
int cpu;
cpu = get_cpu( )
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* work with ptr */
put_cpu( );
/*percpu变量可以用下列宏来导出*/
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
/*访问*/
DECLARE_PER_CPU(type, name);

获取大的缓冲区

大的内存区通常不容易获取到,通常可以使用离散/聚集操作来实现。

在引导时获得专用缓冲区

只有直接链接到内核的代码才能引导时获取缓冲区。

分配和释放使用下列函数

1
2
3
4
5
6
7
#include <linux/bootmen.h>
 
void *alloc_bootmem(unsigned long size);
void *alloc_bootmem_low(unsigned long size);
void *alloc_bootmem_pages(unsigned long size);
void *alloc_bootmem_low_pages(unsigned long size);
void free_bootmem(unsigned long addr, unsigned long size);

分配要么是页(pages版本)要么是页边界对其的空间,除非使用_low后缀的版本否者可能在高端内存。

----------------- 全文结束 -----------------
© 该日志作者:Senghoo, 于2010年07月20日发表于分类"Linux Kernel"下;
© 除非另有注明本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
© 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
© 转载请注明: LDD读书笔记第八章-分配内存 | 转自: Senghoo's Blog
© Tag: ,

你可以继续围观

  • 《深入理解linux内核》边读边说-内存寻址(一)
    内存地址 内存地址有三种类型:逻辑地址,线性地址,物理地址。 逻辑地址:常见程序所表示的地址就是逻辑地址。比如MS-DOS程序设计时使用DS:EAX,CS:IP等地址都是逻辑地址。此类地址把地址分为段偏移的模式来把程序分为若干段。在逻辑地址层面上可以把内存看为若干个段来分开的空间。 线性地址:...
  • linux内核技术手册英文电子版
    关于这本书的详细介绍在 新书入账-LINUX内核技术手册 本书根据CC2.5协议免费发布,看完了感觉不错。 对gentoo等从零开始类发行版安装的时候作为内核帮助手册很不错。 分享下。 官网:http://www.kroah.com/lkn/ 下载地址:linux内核技术手册英文电子版...

发表评论