1 整体介绍
之前在LDD3学习1里面就提过scull的变种,LDD学习1--启程-CSDN博客,大概的变种有这些:
名称 | 全名 | 说明 | 对应章节 |
scull | Simple Character Utility for Loading Localities | 基础版本 | 3 |
scullc | Scull with Slab cache | 使用基于slab高速缓存 | 8.2.1 |
scullp | Scull with whole page | 使用整页的缓存 | 8.3.1 |
scullv | Scull with Virtual Buffers | 使用虚拟地址空间的连续区域 | 8.3.4 |
sculld | Scull with ... | 会注册设备 | |
scull-shared | Scull with ... | 没有具体内容 | |
它们主要的区别是在内存的分配上,内容基本上都在第八章。
2 细节
主要的流程这里就不写了,可以参考scull那篇LDD3学习5--Scull代码流程-CSDN博客,这些只列一些重点吧。
2.1 scullc(Scull with Slab cache)
作用:实现内存缓冲区的字符设备。
在scullc_write中:
/* Allocate a quantum using the memory cache */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmem_cache_alloc(scullc_cache, GFP_KERNEL);
if (!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos], 0, scullc_quantum);
}
里面的行,也就是quantum,使用kmem_cache_alloc来获得。kmem_cache_alloc主要用于从特定的高速缓存(cache)中分配内存对象。这些高速缓存是通过kmem_cache_create函数预先创建的。
scullc_read和基本版几乎一致。
在scullc_init的时候,创建了cache。
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scullc_devices = kmalloc(scullc_devs*sizeof (struct scullc_dev), GFP_KERNEL);
if (!scullc_devices) {
result = -ENOMEM;
goto fail_malloc;
}
memset(scullc_devices, 0, scullc_devs*sizeof (struct scullc_dev));
for (i = 0; i < scullc_devs; i++) {
scullc_devices[i].quantum = scullc_quantum;
scullc_devices[i].qset = scullc_qset;
mutex_init (&scullc_devices[i].lock);
scullc_setup_cdev(scullc_devices + i, i);
}
scullc_cache = kmem_cache_create("scullc", scullc_quantum,
0, SLAB_HWCACHE_ALIGN, NULL); /* no ctor/dtor */
if (!scullc_cache) {
scullc_cleanup();
return -ENOMEM;
}
在follow的时候,流程和之前倒是差不多。最后一个区别就是在scullc_trim清理的时候,用的kmem_cache_free。
for (dptr = dev; dptr; dptr = next) { /* all the list items */
if (dptr->data) {
for (i = 0; i < qset; i++)
if (dptr->data[i])
kmem_cache_free(scullc_cache, dptr->data[i]);
kfree(dptr->data);
dptr->data=NULL;
}
next=dptr->next;
if (dptr != dev) kfree(dptr); /* all of them but the first */
}
这种分配方式的解答如下:
别是对于频繁分配和释放相同类型内存块(如 scullc 设备所需的内存块)的情况。通过kmem_cache_alloc和kmem_cache_free函数,scullc 能够更加高效地利用内存,并且减少内存碎片的产生。对内存的使用效率和数据的一致性有较高要求时,例如,在开发一个网络设备驱动或者一个共享的存储设备驱动时,scullc 的这些特性可以更好地满足需求。
评估:
2.2 scullp(Scull with whole page)
作用:使用整页的内存。
在scullp_init的时候,和基本版没有区别。
在write的时候:
int quantum = PAGE_SIZE << dev->order;
...
/* Here's the allocation of a single quantum */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] =
(void *)__get_free_pages(GFP_KERNEL, dptr->order);
if (!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
}
分配默认4000byte的空间的时候,使用__get_free_pages来获取。
read的时候倒是差不多。
在scullp_trim的时候,使用:
/* This code frees a whole quantum-set */
for (i = 0; i < qset; i++)
if (dptr->data[i])
free_pages((unsigned long)(dptr->data[i]),
dptr->order);
说明如下:
scullp 由于是页面对齐的内存分配,数据存储相对更加规整。在进行读写操作时,能够更好地利用现代处理器的缓存机制。因为数据在内存中的分布更加符合页的边界,处理器在缓存数据时可以更高效地将整页数据加载到缓存中。
例如,在读取数据时,如果要读取的数据位于同一页或者相邻的页内,scullp 可以更方便地利用内存的局部性原理,减少内存访问的次数,从而提高数据读取的效率。在写入数据时,也更容易保证数据的连续性和完整性,因为它是以页为单位进行操作的。
2.3 scullv(Scull with Virtual Buffers)
作用:实现虚拟缓冲区管理。
大体和基本版还是一致的,区别还是在内存分配。
/* follow the list up to the right position */
dptr = scullv_follow(dev, item);
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(void *), GFP_KERNEL);
if (!dptr->data)
goto nomem;
memset(dptr->data, 0, qset * sizeof(char *));
}
/* Allocate a quantum using virtual addresses */
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE << dptr->order);
if (!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
}
可以看到,块还是用的kmalloc,但是每一个缓冲用的vmalloc。
最后,在释放的时候,用的vfree。
for (dptr = dev; dptr; dptr = next) { /* all the list items */
if (dptr->data) {
/* Release the quantum-set */
for (i = 0; i < qset; i++)
if (dptr->data[i])
vfree(dptr->data[i]);
kfree(dptr->data);
dptr->data=NULL;
}
next=dptr->next;
if (dptr != dev) kfree(dptr); /* all of them but the first */
}
2.4 sculld(Scull with ...)
作用:基于设备缓冲区的扩展版本。
在init的时候,多了一个注册函数:
/*
* Register with the driver core.
*/
register_ldd_driver(&sculld_driver);
cleanup的时候,要取消注册。
unregister_ldd_driver(&sculld_driver);
init时候每个设备也多了一个注册的函数:
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
snprintf(dev->devname, sizeof(dev->devname), "sculld%d", index);
dev->ldev.name = dev->devname;
dev->ldev.driver = &sculld_driver;
dev_set_drvdata(&dev->ldev.dev, dev);
register_ldd_device(&dev->ldev);
device_create_file(&dev->ldev.dev, &dev_attr_dev);
}
仅此而已
2.5 scull-shared(Scull with Shared Buffers)
作用:模拟多个进程共享同一个缓冲区。
本来以为是使用了共享内存,结果里面只有几个函数。
3 小结
接口 | 用途 | 推荐场景 |
kmalloc | 小块物理连续内存 | DMA 或少量内存分配 |
kmem_cache_alloc | 固定大小对象内存分配,频繁分配/释放 | 结构体对象的内存管理 |
vmalloc | 大块虚拟连续但物理不连续内存 | 大块非 DMA 数据存储(如大型数组、表) |
__get_free_pages | 中等大小的物理连续内存,灵活 | 需要手动管理中等大小内存块 |