<!-- @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. |
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 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.