您的位置:首页 > 运维架构 > Linux

【Linux】在内核中申请内存

2011-05-22 16:04 609 查看
<!--
@page { margin: 0.79in }
P { margin-bottom: 0.08in }
TD P { margin-bottom: 0in }
TH P { margin-bottom: 0in }
A:link { so-language: zxx }
-->

Allocating
Memory in the Kernel

 

http://www.linuxjournal.com/article/6930

 

Dec
01, 2003 By Robert
Love in
SysAdmin

 

在内核中申请内存和在用户空间中申请内存不同,有以下因素引起了复杂性,包括:

1,内核的虚拟和物理地址被限制到1GB。

2,内核的内存不能pageable。

3,内核通常需要连续的物理地址。

4,通常内核申请内存是不能睡眠。

5,内核中的错误比其他地方的错误有更多的代价。

 

尽管在内核中访问大量内存不再奢侈,但是一点错误就要耗费很长的时间来解决。

 

A General-Purpose Allocator

 

在内核中申请内存的常用方法是:

 

#include <linux/slab.h>

 

void *kmalloc(size_t size, int flags);

 

kmalloc和用户空间的malloc几乎一样,除了第二个参数flags。size和malloc一样,指定了需要申请的按照字节计算的内存数量。当申请成功后,kmalloc()返回一个指向size大小的内存。返回的内存可以赋予任何类型的对象。和malloc一样,kmalloc也可以失败,如果失败,返回一个NULL。

 

Struct falcon *p;

 

p=kmalloc(sizeof(struct falcon),
GFP_KERNEL);

if(!p)

/*申请失败,错误处理*/

 

Flags

 

flags控制了申请内存的行为。flags分为三类:action
modifiers,zone
modifiers,types。Action
modifiers告诉内如以怎么的行为去申请内存。比如kernel能否睡眠。Zone
modifiers告诉内核从什么地方去申请内存,比如一些请求可能需要申请的内存能够让硬件以DMA的方式方式。type指定了申请的类型。把这写flags可以合成一个变量,传给kmalloc。

 

Table 1是action
modifier。Table 2是zone
modifiers。最常用的是GFP_ATOMIC,
GFP_KERNEL。几乎所有的申请都需要同时指定这两个标记的一个。

 

Table 1. Action
Modifiers


Flag
Description
__GFP_COLD

The kernel should use cache
cold pages.

__GFP_FS

The kernel can start
filesystem I/O.

__GFP_HIGH

The kernel can access
emergency pools.

__GFP_IO

The kernel can start disk
I/O.

__GFP_NOFAIL

The kernel can repeat the
allocation.

__GFP_NORETRY

The kernel does not retry
if the allocation fails.

__GFP_NOWARN

The kernel does not print
failure warnings.

__GFP_REPEAT

The kernel repeats the
allocation if it fails.

__GFP_WAIT

The kernel can sleep.

Table 2. Zone
Modifiers


Flag
Description
__GFP_DMA

Allocate only DMA-capable
memory.

No flag

Allocate from wherever
available.

 

GFP_ATOMIC限制内核申请内存不能被堵塞。在不能睡眠的环境中使用这个标记,必须以原子的方式申请内存,比如中断处理。因为内核不能堵塞并且需要去满足申请的内存大小,因此指定这个标记的成功率会低一些。尽管如此,如果申请内存的环境不能睡眠,这个标记是唯一的选择:

 

struct wolf *p;

 

p=kmalloc(sizeof(struct wolf),
GFP_ATOMIC);

 

if(!p)

/*error*/

 

GFP_KERNEL指定了一个通常的内核申请。GFP_KERNEL指定了申请上下文不能被锁,但是可以睡眠。内核可以在睡眠的时候释放内存,所以指定这个标记,成功的可能性更大些。比如内核可以锁住申请的请求,然后交换一些非活动状态的内存页到硬盘上,压缩内存中的caches等等。

 

有时候,比如在写一个ISA设备驱动,我们需要让内存也可以在DMA的区域上申请。对于ISA设备来说,这部分内存是物理地址的开始的16M(0x
1000000)。为了保证内存申请在这个16M的区域,使用GFP_DMA标记。通常,需要GFP_DMA要和GFP_KERNEL或GFP_ATOMIC一起指定。比如:

 

char *buf;

 

buf=kmalloc(BUF)LEN,
GFP_DMA|GFP_KERNEL);

 

Table 3是type列表。Table
4表示了那些action,
zone的flag标记是一致的。在<linux/gfp.h>中定义了这些标记:

Table 3. Types

Flag
Description
GFP_ATOMIC

The allocation is
high-priority and does not sleep. This is the flag to use in
interrupt handlers, bottom halves and other situations where you
cannot sleep.

GFP_DMA

This is an allocation of
DMA-capable memory. Device drivers that need DMA-capable memory
use this flag.

GFP_KERNEL

This is a normal allocation
and might block. This is the flag to use in process context code
when it is safe to sleep.

GFP_NOFS

This allocation might block
and might initiate disk I/O, but it does not initiate a
filesystem operation. This is the flag to use in filesystem code
when you cannot start another filesystem operation.

GFP_NOIO

This allocation might
block, but it does not initiate block I/O. This is the flag to
use in block layer code when you cannot start
b4ac
more block I/O.

GFP_USER

This is a normal allocation
and might block. This flag is used to allocate memory for
user-space processes.

Table 4.
Composition of the Type Flags


Flag
Value
GFP_ATOMIC

__GFP_HIGH

GFP_NOIO

__GFP_WAIT

GFP_NOFS

(__GFP_WAIT | __GFP_IO)

GFP_KERNEL

(__GFP_WAIT | __GFP_IO |
__GFP_FS)

GFP_USER

(__GFP_WAIT | __GFP_IO |
__GFP_FS)

GFP_DMA

__GFP_DMA

 

Returning Memory

当使用完内存之后,需要kfree()注销内存:

#include <linux/slab.h>

 

void kfree(const void* objp);

 

kfree的使用和free一样。不同的是free()操作在一个NULL或者非法地址上,会造成内存的错误,而kfree是安全的。

 

Allocating from Virtual Memory

kmalloc返回物理的地址,也就是虚拟连续的地址。与用户空间的malloc对比,malloc返回虚拟的地址,但是物理上不一定是连续的。物理上连续的地址有两个好处,一是,需要硬件不能访问虚拟地址,二是物理上连续的地址可以使用一个大的页面来映射,着意味着TLB(translation
lookaside buffer,传输后备区)对于寻址这些内存只需要一个TLB
engry。

申请物理连续地址有一个问题,就是常常比较难找到物理连续的内存,特别是大的内存块。而申请大的连续的虚拟地址则成功可能性更大。如果不一定需要物理连续地址时,可以使用vmalloc():

#include <linux/vmalloc.h>

void * vmalloc(unsigned long size);

void vfree(void * addr);

vmalloc和vfree的使用和用户空间的malloc(),free()一样。vmalloc()一致。

 

尽管kmalloc()的性能更好的,但是如果不是特殊需要,还是推荐使用vmalloc。

 

A Small Fixed-Size Stack

和用户空间的进行不一样的是,在kernel中代码没有大的动态的stack。相反的,在内核的进程有一个小的固定大小的stack。这个大小不同的体系不一样。许多体系总申请两个页给stack,在32位的机器上,一般就是8k。

 

因此申请大的stack上的数据是不鼓励的。

#define BUF_LEN 2048

 

void rabbit_function(void)

{

char buf[BUF_LEN];

/* ... */

}

相比较以上的例子,更倾向于下边的做法:

 

#define BUF_LEN 2048

 

void rabbit_function(void)

{

char *buf;

 

buf = kmalloc(BUF_LEN,
GFP_KERNEL);

if (!buf)

/* error! */

 

/* ... */

}

 

Conclusion

内核中申请内存有一些简单的规则:

1,判断申请内存的时候可否睡眠,也就是调用kmalloc的时候能否被阻塞。如果在一个中断处理,在中断处理的下半部分,或者有一个锁的时候,就不能被阻塞。如果在一个进程上下文,也没有锁,则一般可以睡眠。

2,如果可以睡眠,指定GFP_KERNEL。

3,如果不能睡眠,就指定GFP_ATOMIC。

4,如果需要DMA可以访问的内存,比如ISA或者有些PCI设备,就需要指定GFP_DMA。

5,需要对kmalloc返回的值检查NULL。

6,为了没有内存泄漏,需要用kfree()来释放内存。

Resources

 

For more information, check out these
files in your kernel source tree.

 

include/linux/gfp.h: home of the
allocation flags.

 

include/linux/slab.h: definitions of
kmalloc(), et al.

 

mm/page_alloc.c: page allocation
functions.

 

mm/slab.c: implementation of kmalloc(),
et al.

 

Robert Love (rml@tech9.net) is a kernel
hacker at MontaVista Software and a student at the University of
Florida. He is the author of Linux Kernel Development. Robert enjoys
fine wine and lives in Gainesville, Florida.

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息