Chinaunix首页 | 论坛 | 博客
  • 博客访问: 716901
  • 博文数量: 67
  • 博客积分: 994
  • 博客等级: 准尉
  • 技术积分: 1749
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-03 14:10
文章分类
文章存档

2014年(11)

2013年(14)

2012年(14)

2011年(28)

分类: LINUX

2011-11-07 11:24:44

      我们知道对于物理内存的分配与回收,是先通过内存页面的管理,首先在虚存空间中分配一个虚存区间,然后根据需要为此区间建立起相应的映射并分配对应的物理页面。由于linux操作系统是多任务、多用户的,所以会有很多用户程序的执行与结束,如此频繁的进行内存分配与释放,势必造成许多小块闲散空间的产生,如何能利用起来?

         linux采用了伙伴算法来解决问题。伙伴算法的主要思想是把所有闲散的页面分为10块用链表链接起来,每个链表的块中还有2的幂次方的页面。当我们需要一定数量的页面时,先从对应的链表中查找,如果存在就分配,不存載就继续向高一块查找是否有空闲,有的话分配出来,然后将剩余的空间插入到下面相应的链表。

物理页块的分配是通过函数__get_free_pages实现的。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

{

struct page *page;

VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);

page = alloc_pages(gfp_mask, order);

if (!page)

return 0;

return (unsigned long) page_address(page);

}

其参数gfp_mask:表示所分配内存的特殊要求。常用的标志为GFP_KERNELGFP_ATOMIC,前者表示在分配内存期间可以睡眠,用于进程;后者表示不可以睡眠,用于中断处理程序。 __get_free_pages() 返回值是个32位的地址。从上面函数可以看出来物理页面的分配实际上是通过alloc_pages进行分配完成的。

除了分配内存之外,还要确保剩余内存足以应对紧急情况的处理。

页块分配出去用完后要进行回收,linux使用free_pages进行页块回收。

void free_pages(unsigned long addr, unsigned int order)

{

if (addr != 0) {

VM_BUG_ON(!virt_addr_valid((void *)addr));

__free_pages(virt_to_page((void *)addr), order);

}

}

其中 VM_BUG_ON宏其实就是一个循环操作,宏定义如下:

#define VM_BUG_ON(cond) do { (void)(cond); } while (0)

virt_addr_valid则是对地址进行一系列判断,宏定义如下:

#define virt_addr_valid(kaddr) (((void *)(kaddr) >= (void *)PAGE_OFFSET) && ((void *)(kaddr) < (void*)memory_end))

当然真正其回收作用的就是__ __free_pages函数。

自此我们知道伙伴算法分配内存时,每次至少分配一个页面,当我们需要的内存少于一个页面时,或者更小的数据时,该如何做?Linux引入了slab分配模式。

slab的主要思想是对内核数据进行页面分配时,首先要对数据结构进行初始化,用完之后就要回收。这样不就在重复初始化上花费了很多时间,为了避免这种情况,slab并不会丢弃已分配的对象,而是释放后依然把他们保留在缓冲区中,以便以后请求分配同一对象时,就可以快速获得而免去了初始化。

slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

Slab的构成如下图:

您没有插入代码!

linux把缓冲区分为专用和通用,其中专用缓冲区主要用于频繁使用的数据结构,而通用缓冲区就主要用于开销不大的数据结构了。

SlabAPI主要如下:

专用缓冲区:

缓冲区的创建通过kmem_cache_create()建立,其原型如下:

struct kmem_cache *kmem_cache_create( const char *name, size_t size, size_t offset,

unsigned long c_flags; void (*ctor)(void*, struct kmem_cache *, unsigned long), void (*dtor)(void*, struct kmem_cache *, unsigned long));

其中name为缓冲区的名字,size为对象的大小,offset参数定义了每个对象必需的对齐。 c_flags 参数指定了为缓存启用的选项。其可能取值SLAB_HWCACHE_ALIGN表示与第一个缓冲区中的缓冲行边界对其;SLAB_NO_REAP表示允许系统回收内存;SLAB_CACHE_DMA表示使用的是DMA内存(DMA是直接存储器访问的缩写,他允许不同速度的硬件装置来沟通,而不需要依于 CPU 的大量 中断 负载);最后两个函数分别是构造函数(用于对数据初始化)和析构函数(用于对数据回收处理)。其中数据结构kmem_cache是用来对缓冲区进行管理的。其数据结构如下:

struct kmem_cache {

53 /* 1) per-cpu data, touched during every alloc/free */

54 struct array_cache *array[NR_CPUS];

55 /* 2) Cache tunables. Protected by cache_chain_mutex */

56 unsigned int batchcount;

57 unsigned int limit;

58 unsigned int shared;

59

60 unsigned int buffer_size;

61 u32 reciprocal_buffer_size;

62 /* 3) touched by every alloc & free from the backend */

63

64 unsigned int flags; /* constant flags */

65 unsigned int num; /* # of objs per slab */

...........

}

缓冲区的分配与释放函数分别如下:

kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

kmem_cache_free(struct kmem_cache *cachep, void *objp)

内核函数 kmem_cache_destroy 用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必须为空。

void kmem_cache_destroy( struct kmem_cache *cachep );

通用缓冲区:

通用缓冲区中分配和释放缓冲区的函数为:

void *kmalloc(size_t,int flags );

void kfree(const void *objp);

当然还有其他函数来辅助slab完成任务。kmem_cache_size 函数会返回这个缓存所管理的对象的大小。您也可以通过调用 kmem_cache_name 来检索给定缓存的名称(在创建缓存时定义)。具体函数原型如下:

unsigned int kmem_cache_size( struct kmem_cache *cachep ); const char *kmem_cache_name( struct kmem_cache *cachep );

实现上述函数的内核模块

#include
#include
#include
#include
#include
#include
#include

MODULE_LICENSE("GPL");
static struct kmem_cache *my_cache;
int my_cache_test(void);
static int __init  my_cache_init(void)
{
    printk("I am coming.....\n");
    my_cache=kmem_cache_create("mycache",32,0,SLAB_HWCACHE_ALIGN,NULL);
    if(!my_cache)
    {
        printk("my_cache_init():cannot create !\n");
    }
    my_cache_test();
    return 0;
}
int my_cache_test(void)
{
    void *obj;

//    printk("cache name is %s\n",kmem_cache_name(my_cache));
    printk("cache object size is %d\n",kmem_cache_size(my_cache));

    obj=kmem_cache_alloc(my_cache,GFP_KERNEL);
    if(!obj)
    {
        printk("alloc error\n");    
    }
    else{
        kmem_cache_free(my_cache,obj);
         }
    return 0;
}
void remove_my_cache(void)
{
    if(my_cache)
        kmem_cache_destroy(my_cache);
        printk("destroy success!\n");

    return ;

 }
static void __exit my_cache_exit(void)
{
    printk("leaving....\n");
    kmem_cache_destroy(my_cache);
    remove_my_cache();
    return;
}

MODULE_LICENSE("GPL");
module_init(my_cache_init);
module_exit(my_cache_exit);
阅读(3539) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~