作者:刘昊昱
博客: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函数创建:
kmem_cache_create函数创建一个新的后备高速缓存,其中可以容纳任意数目的内存块,这些内存块的大小都相同,由size参数指定。
- 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_create创建了某个对象的后备高速缓存后,就可以调用kmem_cache_alloc从中分配内存对象:
释放一个内存对象使用kmem_cache_free函数:
- void *kmem_cache_alloc(kmem_cache_t *cache, int flags);
如果驱动程序不会再使用后备高速缓存了,例如模块被卸载时,应该调用kmem_cache_destroy函数释放后备高速缓存:
- void kmem_cache_free(kmem_cache_t *cache, const void *obj);
我们知道了slab的相关概念和操作函数,下面可以看scullc的代码了。
- int kmem_cache_destroy(kmem_cache_t *cache);
scullc和前面分析过的scull绝大部分代码都是相同的,他们的区别在内存分配上,scullc使用slab分配内存,而scull使用kmalloc。下面我们只分析scullc与scull不同的代码,其他代码如果有问题,大家可以参考前面分析scull的相关文章。
首先看scullc的模块初始化函数scullc_init,其中需要分析的是如下语句:
scullc_cache定义在52行:
- 560 scullc_cache = kmem_cache_create("scullc", scullc_quantum,
- 561 0, SLAB_HWCACHE_ALIGN, NULL, NULL); /* no ctor/dtor */
- 562 if (!scullc_cache) {
- 563 scullc_cleanup();
- 564 return -ENOMEM;
- 565 }
可以看出,在scullc的模块初始化函数中,创建了一个slab后备高速缓存,其关联的名称叫scullc,其中包含的内存块的大小是scullc_quantum(默认值4000),每个内存块即是驱动程序中的一个量子。
- 51/* declare one cache pointer: use it for all devices */
- 52kmem_cache_t *scullc_cache;
现在有了后备高速缓存,下面scullc可以从中为量子分配内存块了,相关代码在scullc_write函数中,其中有必要分析的是如下语句:
247行,从scullc_cache指向的后备高速缓存中分配了一个量子内存块。
- 245 /* Allocate a quantum using the memory cache */
- 246 if (!dptr->data[s_pos]) {
- 247 dptr->data[s_pos] = kmem_cache_alloc(scullc_cache, GFP_KERNEL);
- 248 if (!dptr->data[s_pos])
- 249 goto nomem;
- 250 memset(dptr->data[s_pos], 0, scullc_quantum);
- 251 }
scullc不再使用量子内存块时,应该返回给后备高速缓存,完成这项工作的函数是scullc_trim,其中关键的代码如下:
- 488 for (i = 0; i < qset; i++)
- 489 if (dptr->data[i])
- 490 kmem_cache_free(scullc_cache, dptr->data[i]);
最后,scullc模块卸载时,必须把后备高速缓存返回给系统,在scullc_cleanup函数中,有如下语句:
与scull相比,scullc的最大区别是运行速度略有提高,对内存利用率更高。由于后备调整缓存中的内存块都是同样的大小,所以其在内存中的排列位置达到了最大密集程度,相反,scull的数据对象则会引起不可预测的内存碎片。
- 593 if (scullc_cache)
- 594 kmem_cache_destroy(scullc_cache);
在我的机器上,测试scullc模块过程如下图所示:
二、编译scullc时遇到的问题
在2.6.32-38-generic-pae上编译LDD3提供的scullc模块时,会出现如下错误:
把Makefile中第12行和第42行的CFLAGS替换为EXTRA_CFLAGS即可解决上面的错误。再次make,会出现如下错误:
因为linux/config.h现在已经不存在了,所以直接把main.c的第18行去掉,即可解决这个错误,再次编译,出现如下错误信息:
这是因为新内核中不再使用kmem_cache_t为个类型定义,而是使用struct kmem_cache结构体代表一个后备高速缓存。所以把main.c的第51行改为
即可解决这个问题,再次make,又出现如下错误:
- 51struct kmem_cache *scullc_cache;
这是因为在新内核中INIT_WORK宏发生了变化,现在INIT_WORK只能接受两个参数,所以将439行改为:
注意,新的INIT_WORK的第二个参数scullc_do_deferred_op函数以INIT_WORK的第一个参数做为参数。
- 439 INIT_WORK(&stuff->work, scullc_do_deferred_op);
连锁反应,还需要把scullc_do_deferred_op函数的实现修改如下:
409行和411行发生了变化。
- 409static void scullc_do_deferred_op(struct work_struct *p)
- 410{
- 411 struct async_work *stuff = container_of(p, struct async_work, work);
- 412 aio_complete(stuff->iocb, stuff->result, 0);
- 413 kfree(stuff);
- 414}
再次编译,出现如下错误:
这是因为,在新内核中,kmem_cache_create函数发生了变化,最后一个参数destructor被删除掉了。所以,把560行最后一个参数NULL删除即可解决这个问题。再次编译,编译通过,但是还有几个警告信息,如下图所示:
这是因为,在2.6.32-38-generic-pae内核中,schedule_delayed_work函数的定义发生了变化,现在其函数原型是
- int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
而在LDD3使用的2.6.10版本的内核中,其函数原型是:
- int fastcall schedule_delayed_work(struct work_struct *work, unsigned long delay)
所以要在新内核上执行schedule_delayed_work,原来的work_struct必须改为delayed_work。
delayed_work结构体定义如下:
- struct delayed_work {
- struct work_struct work;
- struct timer_list timer;
- };
为了修正这个警告,需要修改如下三个地方:
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);
再次编译,还有如下警告信息:
这是因为在新内核中,file_operations结构体的aio_read和aio_write成员函数原型发生了变化。所以做如下修改:
改动的地方是445行和451行两个函数的函数原型,使参数类型符合新的定义。
- 445static ssize_t scullc_aio_read(struct kiocb *iocb, const struct iovec *buf, unsigned long count,
- 446 loff_t pos)
- 447{
- 448 return scullc_defer_op(0, iocb, (char __user *) buf, count, pos);
- 449}
- 450
- 451static ssize_t scullc_aio_write(struct kiocb *iocb, const struct iovec *buf,
- 452 unsigned long count, loff_t pos)
- 453{
- 454 return scullc_defer_op(1, iocb, (char __user *) buf, count, pos);
- 455}
另外还修改了448行,把第三个参数强制转换为char __user*类型。
修改后,编译成功,如下图所示: