LDD3源码分析之slab高速缓存

转载自http://blog.csdn.net/liuhaoyutz/article/details/7415466

作者:刘昊昱 

博客:http://blog.csdn.net/liuhaoyutz

编译环境:Ubuntu 10.10

内核版本:2.6.32-38-generic-pae

LDD3源码路径:examples/scullc

 

本文分析LDD3第8章中关于使用slab高速缓存的代码,对应的源码在scullc目录下。另外,在较新的内核下编译scullc时会遇到一些错误,本文最后给出了解决这些错误的方法。
 
一、scullc源码分析
首先介绍一下slab相关的概念和函数。我们编写程序时,如果经常为某种数据结构进行分配和释放内存空间的操作,常常会用到空闲链表,其中包含多个已经分配好的可供使用的数据结构内存块,当需要使用数据结构时,直接去链表中去取,然后把数据放进去;使用完后,再把数据结构放回空闲链表,以供以后使用。
Linux内核中也有类似的需求,但是空闲链表的问题是不能全局控制,当内存紧缺时,内核无法通知每个空闲链表,让其释放出一些内存空间来。为此,Linux内核提供了slab分配器。简单理解,slab就是内核维护的一组大小不同的空闲链表,Linux把每个空闲链表称为后备高速缓存(lookaside cache),当需要使用时,可以从中取出需要的内存块,不用时,再把内存块归还给slab。
slab维护的后备高速缓存是kmem_cache_t类型,可以由kmem_cache_create函数创建:

  
  
  
  
[cpp] view plain copy
  1. kmem_cache_t *kmem_cache_create(const char *name, size_t size,  
  2.                                 size_t offset,   
  3.                                 unsigned long flags,  
  4.                                 void (*constructor)(void *, kmem_cache_t *,  
  5.                                                     unsigned long flags),  
  6.                                 void (*destructor)(void *, kmem_cache_t *,  
  7.                                                    unsigned long flags));  
kmem_cache_create函数创建一个新的后备高速缓存,其中可以容纳任意数目的内存块,这些内存块的大小都相同,由size参数指定。
使用kmem_cache_create创建了某个对象的后备高速缓存后,就可以调用kmem_cache_alloc从中分配内存对象:

  
  
  
  
[cpp] view plain copy
  1. void *kmem_cache_alloc(kmem_cache_t *cache, int flags);  
释放一个内存对象使用kmem_cache_free函数:

  
  
  
  
[cpp] view plain copy
  1. void kmem_cache_free(kmem_cache_t *cache, const void *obj);  
如果驱动程序不会再使用后备高速缓存了,例如模块被卸载时,应该调用kmem_cache_destroy函数释放后备高速缓存:

  
  
  
  
[cpp] view plain copy
  1. int kmem_cache_destroy(kmem_cache_t *cache);  
我们知道了slab的相关概念和操作函数,下面可以看scullc的代码了。
scullc和前面分析过的scull绝大部分代码都是相同的,他们的区别在内存分配上,scullc使用slab分配内存,而scull使用kmalloc。下面我们只分析scullc与scull不同的代码,其他代码如果有问题,大家可以参考前面分析scull的相关文章。
首先看scullc的模块初始化函数scullc_init,其中需要分析的是如下语句:

  
  
  
  
[cpp] view plain copy
  1. 560    scullc_cache = kmem_cache_create("scullc", scullc_quantum,  
  2. 561            0, SLAB_HWCACHE_ALIGN, NULL, NULL); /* no ctor/dtor */  
  3. 562    if (!scullc_cache) {  
  4. 563        scullc_cleanup();  
  5. 564        return -ENOMEM;  
  6. 565    }  
scullc_cache定义在52行:

  
  
  
  
[cpp] view plain copy
  1. 51/* declare one cache pointer: use it for all devices */  
  2. 52kmem_cache_t *scullc_cache;  
可以看出,在scullc的模块初始化函数中,创建了一个slab后备高速缓存,其关联的名称叫scullc,其中包含的内存块的大小是scullc_quantum(默认值4000),每个内存块即是驱动程序中的一个量子。
现在有了后备高速缓存,下面scullc可以从中为量子分配内存块了,相关代码在scullc_write函数中,其中有必要分析的是如下语句:

  
  
  
  
[cpp] view plain copy
  1. 245    /* Allocate a quantum using the memory cache */  
  2. 246    if (!dptr->data[s_pos]) {  
  3. 247        dptr->data[s_pos] = kmem_cache_alloc(scullc_cache, GFP_KERNEL);  
  4. 248        if (!dptr->data[s_pos])  
  5. 249            goto nomem;  
  6. 250        memset(dptr->data[s_pos], 0, scullc_quantum);  
  7. 251    }  
247行,从scullc_cache指向的后备高速缓存中分配了一个量子内存块。
scullc不再使用量子内存块时,应该返回给后备高速缓存,完成这项工作的函数是scullc_trim,其中关键的代码如下:
[cpp] view plain copy
  1. 488            for (i = 0; i < qset; i++)  
  2. 489                if (dptr->data[i])  
  3. 490                    kmem_cache_free(scullc_cache, dptr->data[i]);  
最后,scullc模块卸载时,必须把后备高速缓存返回给系统,在scullc_cleanup函数中,有如下语句:

   
   
   
   
[cpp] view plain copy
  1. 593    if (scullc_cache)  
  2. 594        kmem_cache_destroy(scullc_cache);  
与scull相比,scullc的最大区别是运行速度略有提高,对内存利用率更高。由于后备调整缓存中的内存块都是同样的大小,所以其在内存中的排列位置达到了最大密集程度,相反,scull的数据对象则会引起不可预测的内存碎片。
在我的机器上,测试scullc模块过程如下图所示:
LDD3源码分析之slab高速缓存_第1张图片
 
二、编译scullc时遇到的问题
2.6.32-38-generic-pae上编译LDD3提供的scullc模块时,会出现如下错误:
LDD3源码分析之slab高速缓存_第2张图片
 
 
 
 
 
 
 
 
 
 
Makefile中第12行和第42行的CFLAGS替换为EXTRA_CFLAGS即可解决上面的错误。再次make,会出现如下错误:
LDD3源码分析之slab高速缓存_第3张图片
因为linux/config.h现在已经不存在了,所以直接把main.c的第18行去掉,即可解决这个错误,再次编译,出现如下错误信息:
LDD3源码分析之slab高速缓存_第4张图片
这是因为新内核中不再使用kmem_cache_t为个类型定义,而是使用struct kmem_cache结构体代表一个后备高速缓存。所以把main.c的第51行改为

     
     
     
     
[cpp] view plain copy
  1. 51struct kmem_cache *scullc_cache;  
即可解决这个问题,再次make,又出现如下错误:
LDD3源码分析之slab高速缓存_第5张图片
这是因为在新内核中INIT_WORK宏发生了变化,现在INIT_WORK只能接受两个参数,所以将439行改为:

      
      
      
      
[cpp] view plain copy
  1. 439    INIT_WORK(&stuff->work, scullc_do_deferred_op);  
注意,新的INIT_WORK的第二个参数scullc_do_deferred_op函数以INIT_WORK的第一个参数做为参数。
连锁反应,还需要把scullc_do_deferred_op函数的实现修改如下:

      
      
      
      
[cpp] view plain copy
  1. 409static void scullc_do_deferred_op(struct work_struct *p)  
  2. 410{  
  3. 411    struct async_work *stuff = container_of(p, struct async_work, work);  
  4. 412    aio_complete(stuff->iocb, stuff->result, 0);  
  5. 413    kfree(stuff);  
  6. 414}  
409行和411行发生了变化。
再次编译,出现如下错误:
LDD3源码分析之slab高速缓存_第6张图片
这是因为,在新内核中,kmem_cache_create函数发生了变化,最后一个参数destructor被删除掉了。所以,把560行最后一个参数NULL删除即可解决这个问题。再次编译,编译通过,但是还有几个警告信息,如下图所示:
LDD3源码分析之slab高速缓存_第7张图片

这是因为,在2.6.32-38-generic-pae内核中,schedule_delayed_work函数的定义发生了变化,现在其函数原型是

[cpp] view plain copy
  1. int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)  

而在LDD3使用的2.6.10版本的内核中,其函数原型是:

[cpp] view plain copy
  1. int fastcall schedule_delayed_work(struct work_struct *work, unsigned long delay)  

所以要在新内核上执行schedule_delayed_work,原来的work_struct必须改为delayed_work

delayed_work结构体定义如下:

[cpp] view plain copy
  1. struct delayed_work {  
  2.     struct work_struct work;  
  3.     struct timer_list timer;  
  4. };  

为了修正这个警告,需要修改如下三个地方:

403行改为:   struct delayed_work work;
411行改为:   struct async_work *stuff = container_of(p, struct async_work, work.work);
439行改为:   INIT_DELAYED_WORK(&stuff->work, scullc_do_deferred_op);
再次编译,还有如下警告信息:
LDD3源码分析之slab高速缓存_第8张图片
这是因为在新内核中,file_operations结构体的aio_read和aio_write成员函数原型发生了变化。所以做如下修改:

         
         
         
         
[cpp] view plain copy
  1. 445static ssize_t scullc_aio_read(struct kiocb *iocb, const struct iovec *buf, unsigned long count,  
  2. 446        loff_t pos)  
  3. 447{  
  4. 448    return scullc_defer_op(0, iocb, (char __user *) buf, count, pos);  
  5. 449}  
  6. 450  
  7. 451static ssize_t scullc_aio_write(struct kiocb *iocb, const struct iovec *buf,  
  8. 452        unsigned long count, loff_t pos)  
  9. 453{  
  10. 454    return scullc_defer_op(1, iocb, (char __user *) buf, count, pos);  
  11. 455}  
改动的地方是445行和451行两个函数的函数原型,使参数类型符合新的定义。
另外还修改了448行,把第三个参数强制转换为char __user*类型。
修改后,编译成功,如下图所示:
LDD3源码分析之slab高速缓存_第9张图片

你可能感兴趣的:(LDD3源码分析之slab高速缓存)