【原创】《Linux设备驱动程序》学习之循序渐进 --- 分配内存


【原创】《Linux设备驱动程序》学习之循序渐进 --- 分配内存


第八章 --- 分配内存

kmalloc 函数的内幕

记住 kmalloc 原型是:  
#include <linux/slab.h>  
void *kmalloc(size_t size, int flags); 

最常用的标志, GFP_KERNEL, 意思是这个分配((内部最终通过调用__get_free_pages 来进行, 它是 GFP_ 前缀的来源) 代表运行在内核空间的进程而进行的. 

当使用 GFP_ATOMIC 时, kmalloc 甚至能够使用最后一个空闲页. 如果这最后一个空闲页不存在, 则返回分配失败. 

所有的标志定义在 <linux/gfp.h>, 并且每个标志用一个双下划线做前缀, 例如 __GFP_DMA.

Linux 内核把内存分为3个区段: 可用于DMA的内存,  常规内存和高端内存.

驱动开发者应当记住的一件事情是, 内核只能分配某些预定义的, 固定大小的字节数组. 如果你请求一个任意数量内存, 你可能得到稍微多于你请求的, 至多是 2 倍数量. 同样, 程序员应当记住 kmalloc 能够处理的最小分配是 32 或者 64 字节, 依赖系统的体系所使用的页大小.

后备高速缓存

Linux 内核的缓存管理者有时称为" slab 分配器". 因此, 它的功能和类型在 <linux/slab.h> 中声明. slab 分配器实现有一个 kmem_cache_t 类型的缓存; 使用一个对 kmem_cache_create 的调用来创建它们:  
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)); 

一旦一个对象的缓存被创建, 你可以通过调用 kmem_cache_alloc 从它分配对象.  
void *kmem_cache_alloc(kmem_cache_t *cache, int flags); 
这里, cache 参数是你之前已经创建的缓存; flags 是你会传递给 kmalloc 的相同, 并且被参考如果 kmem_cache_alloc 需要出去并分配更多内存. 

为释放一个对象, 使用 kmem_cache_free:  
 void kmem_cache_free(kmem_cache_t *cache, const void *obj);  
当驱动代码用完这个缓存, 典型地当模块被卸载, 它应当如下释放它的缓存:  
 int kmem_cache_destroy(kmem_cache_t *cache);  
这个销毁操作只在从这个缓存中分配的所有的对象都已返回给它时才成功. 因此, 一个模块应当检查从 kmem_cache_destroy 的返回值; 一个失败指示某类在模块中的内存泄漏(因为某些对象已被丢失.) 

内存池 

如果你考虑在你的驱动中使用一个 mempool, 请记住一件事: mempools 分配一块内存在一个链表中, 对任何真实的使用是空闲和无用的. 容易使用 mempools 消耗大量的内存. 在几乎每个情况下, 首选的可选项是不使用 mempool 并且代替以简单处理分配失败的可能性. 如果你的驱动有任何方法以不危害到系统完整性的方式来响应一个分配失败, 就这样做. 驱动代码中的 mempools 的使用应当少.

vmalloc及其辅助函数

我们展示给你的下一个内存分配函数是 vmlloc, 它在虚拟内存空间分配一块连续的内存区.

我们这里描述 vmalloc 因为它是一个基本的 Linux 内存分配机制. 我们应当注意, 但是, vmalloc 的使用在大部分情况下不鼓励.

调用 vmalloc 的正确时机是当你在为一个大的只存在于软件中的顺序缓冲分配内存时. 重要的是注意 vamlloc 比 __get_free_pages 有更多开销, 因为它必须获取内存并且建立页表. 因此, 调用 vmalloc 来分配仅仅一页是无意义的.

vmalloc 的一个小的缺点在于它无法在原子上下文中使用, 因为, 内部地, 它使用 kmalloc(GFP_KERNEL) 来获取页表的存储, 并且因此可能睡眠.

per-CPU 的变量 

对于一个处理器, 在修改一个per-CPU变量的临界区中不应当被抢占. 并且如果你的进程在对一个per-CPU变量存取时将, 要被移动到另一个处理器上, 也不好. 因为这个原因, 你必须显式使用 get_cpu_var 宏来存取当前处理器的给定变量拷贝, 并且当你完成时调用 put_cpu_var. 

如果你想使用每-CPU变量来创建一个简单的整数计数器, 看一下在 <linux/percpu_counter.h> 中的现成的实现.

在引导时获得专用缓冲区

在启动时分配是获得连续内存页而避开 __get_free_pages 施加的对缓冲大小限制的唯一方法, 不但最大允许大小还有限制的大小选择.

快速参考 

#include <linux/slab.h> 
void *kmalloc(size_t size, int flags); 
void kfree(void *obj); 
内存分配的最常用接口. 
#include <linux/mm.h> 
GFP_USER 
GFP_KERNEL 
GFP_NOFS 
GFP_NOIO 
GFP_ATOMIC  
控制内存分配如何进行的标志, 从最少限制的到最多的. GFP_USER 和 GFP_KERNEL 优先级允许当前进程被置为睡眠来满足请求. GFP_NOFS 和 GFP_NOIO 禁止文件系统操作和所有的 I/O 操作, 分别地, 而 GFP_ATOMIC 分配根本不能睡眠. 
__GFP_DMA 
__GFP_HIGHMEM 
__GFP_COLD 
__GFP_NOWARN 
__GFP_HIGH 
__GFP_REPEAT 
__GFP_NOFAIL 
__GFP_NORETRY  
这些标志修改内核的行为, 当分配内存时. 
#include <linux/malloc.h> 
kmem_cache_t  *kmem_cache_create(char  *name,  size_t  size,  size_t  offset,  unsigned  long  flags, 
constructor(), destructor( )); 

int kmem_cache_destroy(kmem_cache_t *cache); 
创建和销毁一个 slab 缓存. 这个缓存可被用来分配几个相同大小的对象. 
SLAB_NO_REAP 
SLAB_HWCACHE_ALIGN 
SLAB_CACHE_DMA  
在创建一个缓存时可指定的标志. 
SLAB_CTOR_ATOMIC 
SLAB_CTOR_CONSTRUCTOR  
分配器可用传递给构造函数和析构函数的标志. 
void *kmem_cache_alloc(kmem_cache_t *cache, int flags); 
void kmem_cache_free(kmem_cache_t *cache, const void *obj); 
从缓存中分配和释放一个单个对象. /proc/slabinfo 一个包含对 slab 缓存使用情况统计的虚拟文件. 
#include <linux/mempool.h> 
mempool_t  *mempool_create(int  min_nr,  mempool_alloc_t  *alloc_fn,  mempool_free_t  *free_fn, void *data); 
void mempool_destroy(mempool_t *pool); 
创建内存池的函数, 它试图避免内存分配设备, 通过保持一个已分配项的"紧急列表". 
void *mempool_alloc(mempool_t *pool, int gfp_mask); 
void mempool_free(void *element, mempool_t *pool); 
从(并且返回它们给)内存池分配项的函数. 
unsigned long get_zeroed_page(int flags); 
unsigned long __get_free_page(int flags); 
unsigned long __get_free_pages(int flags, unsigned long order); 
面向页的分配函数. get_zeroed_page 返回一个单个的, 零填充的页. 这个调用的所有的其他版本不初始化返回页的内容. 
int get_order(unsigned long size); 

返回关联在当前平台的大小的分配级别, 根据 PAGE_SIZE. 这个参数必须是 2 的幂, 并且返回值至少是 0. 
void free_page(unsigned long addr); 
void free_pages(unsigned long addr, unsigned long order); 
释放面向页分配的函数. 
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); 
Linux 内核中最底层页分配器的所有变体. 
void __free_page(struct page *page); 
void __free_pages(struct page *page, unsigned int order); 
void free_hot_page(struct page *page); 
使用一个 alloc_page 形式分配的页的各种释放方法. 
#include <linux/vmalloc.h> 
void * vmalloc(unsigned long size); 
void vfree(void * addr); 
#include <asm/io.h> 
void * ioremap(unsigned long offset, unsigned long size); 
void iounmap(void *addr); 
分配或释放一个连续虚拟地址空间的函数. iormap 存取物理内存通过虚拟地址, 而 vmalloc 分配空闲页. 使用 ioreamp 映射的区是 iounmap 释放, 而从 vmalloc 获得的页使用 vfree 来释放. 
#include <linux/percpu.h> 
DEFINE_PER_CPU(type, name); 
DECLARE_PER_CPU(type, name); 
定义和声明每-CPU变量的宏. 
per_cpu(variable, int cpu_id) 
get_cpu_var(variable) 
put_cpu_var(variable) 
提供对静态声明的每-CPU变量存取的宏. 
void *alloc_percpu(type); 
void *__alloc_percpu(size_t size, size_t align); 
void free_percpu(void *variable); 

进行运行时分配和释放每-CPU变量的函数. 
int get_cpu( ); 
void put_cpu( ); 
per_cpu_ptr(void *variable, int cpu_id) 
get_cpu 获得对当前处理器的引用(因此, 阻止抢占和移动到另一个处理器)并且返回处理器的ID; put_cpu 返回这个引用. 为存取一个动态分配的每-CPU变量, 用应当被存取版本所在的 CPU 的 ID 来使用 per_cpu_ptr. 对一个动态的每-CPU 变量当前 CPU 版本的操作, 应当用对 get_cpu 和 put_cpu 的调用来包围.  
#include <linux/bootmem.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); 
在系统启动时进行分配和释放内存的函数(只能被直接连接到内核中去的驱动使用)

原文链接:

http://blog.csdn.net/geng823/article/details/38082501

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值