一个简单的块设备驱动的实现
这篇文章系列来自原创作者:赵磊,感谢这位作者,链接为:
http://bbs.chinaunix.net/thread-2017377-1-1.html
第一章
这个设备驱动程序实现的是基于内存的块设备驱动程序。本章的目的是用尽可能最简单的方法来写出一个能用的块设备驱动,所谓的能用是指我们可以对这个驱动生成的块设备进行mkfs,mount和读写文件。
这里插一句,我们不打算在这里介绍如何写模块,理由是介绍的文章已经满天飞舞了。
如果你能看得懂、并且成功地编译、运行了这段代码,我们认为你已经达到了本教程的入学资格,
当然,如果你不幸的卡在这段代码中,那么请等到搞定它以后再往下看:
mod.c:
#include <linux/module.h>
static int __init init_base(void)
{
printk("----Hello. World----\n");
return 0;
}
static void __exit exit_base(void)
{
printk("----Bye----\n");
}
module_init(init_base);
module_exit(exit_base);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Zhao Lei");
MODULE_DESCRIPTION("For test");
Makefile:
obj-m := mod.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
rm -rf Module.markers modules.order Module.symvers
好了,这里我们假定你已经搞定上面的最简单的模块了,懂得什么是看模块,以及简单模块的编写、编译、加载和卸载。
还有就是,什么是块设备,什么是块设备驱动,这个也请自行google吧,因为我们已经迫不及待要写完程序下课。
如果你能看得懂、并且成功地编译、运行了这段代码,我们认为你已经达到了本教程的入学资格,
当然,如果你不幸的卡在这段代码中,那么请等到搞定它以后再往下看:
mod.c:
#include <linux/module.h>
static int __init init_base(void)
{
}
static void __exit exit_base(void)
{
}
module_init(init_base);
module_exit(exit_base);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Zhao Lei");
MODULE_DESCRIPTION("For test");
Makefile:
obj-m := mod.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
clean:
好了,这里我们假定你已经搞定上面的最简单的模块了,懂得什么是看模块,以及简单模块的编写、编译、加载和卸载。
还有就是,什么是块设备,什么是块设备驱动,这个也请自行google吧,因为我们已经迫不及待要写完程序下课。
为了建立一个可用的块设备,我们需要做......1件事情:
1:用add_disk()函数向系统中添加这个块设备
添加一个全局的
static struct gendisk *simp_blkdev_disk;
然后申明模块的入口和出口:
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);
然后在入口处添加这个设备、出口处私房这个设备:
static int __init simp_blkdev_init(void)
{
add_disk(simp_blkdev_disk);
return 0;
}
static void __exit simp_blkdev_exit(void)
{
del_gendisk(simp_blkdev_disk);
}
当然,在添加设备之前我们需要申请这个设备的资源,这用到了alloc_disk()函数,因此模块入口函数simp_blkdev_init(void)应该是:
static int __init simp_blkdev_init(void)
{
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
return ret;
}
还有别忘了在卸载模块的代码中也加一个行清理函数:
put_disk(simp_blkdev_disk);
还有就是,设备有关的属性也是需要设置的,因此在alloc_disk()和add_disk()之间我们需要:
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
simp_blkdev_disk->major = ?1;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = ?2;
simp_blkdev_disk->queue = ?3;
set_capacity(simp_blkdev_disk, ?4);
SIMP_BLKDEV_DISKNAME其实是这个块设备的名称,为了绅士一些,我们把它定义成宏了:
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
这里又引出了4个问号。(天哪,是不是有种受骗的感觉,像是陪老婆去做头发)
第1个问号:
每个设备需要对应的主、从驱动号。
我们的设备当然也需要,但很明显我不是脑科医生,因此跟写linux的那帮疯子不熟,得不到预先为我保留的设备号。
还有一种方法是使用动态分配的设备号,但在这一章中我们希望尽可能做得简单,因此也不采用这种方法。
那么我们采用的是:抢别人的设备号。
我们手头没有AK47,因此不敢干的太轰轰烈烈,而偷偷摸摸的事情倒是可以考虑的。
柿子要捡软的捏,而我们试图找出一个不怎么用得上的设备,然后抢他的ID。
打开linux/include/linux/major.h,把所有的设备一个个看下来,我们觉得最胜任被抢设备号的家伙非COMPAQ_SMART2_XXX莫属。
第一因为它不强势,基本不会被用到,因此也不会造成冲突;第二因为它有钱,从COMPAQ_SMART2_MAJOR到COMPAQ_SMART2_MAJOR7有那8个之多的设备号可以被抢,不过瘾的话还有它妹妹:COMPAQ_CISS_MAJOR~COMPAQ_CISS_MAJOR7。
为了让抢劫显得绅士一些,我们在外面又定义一个宏:
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
然后在?1的位置填上SIMP_BLKDEV_DEVICEMAJOR。
第2个问号:
gendisk结构需要设置fops指针,虽然我们用不到,但该设还是要设的。
好吧,就设个空得给它:
在全局部分添加:
struct block_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
};
然后把?2的位置填上&simp_blkdev_fops。
第3个问号:
这个比较麻烦一些。
首先介绍请求队列的概念。对大多数块设备来说,系统会把对块设备的访问需求用bio和bio_vec表示,然后提交给通用块层。
通用块层为了减少块设备在寻道时损失的时间,使用I/O调度器对这些访问需求进行排序,以尽可能提高块设备效率。
关于I/O调度器在本章中不打算进行深入的讲解,但我们必须知道的是:
1:I/O调度器把排序后的访问需求通过request_queue结构传递给块设备驱动程序处理
2:我们的驱动程序需要设置一个request_queue结构
申请request_queue结构的函数是blk_init_queue(),而调用blk_init_queue()函数时需要传入一个函数的地址,这个函数担负着处理对块设备数据的请求。
因此我们需要做的就是:
1:实现一个static void simp_blkdev_do_request(struct request_queue *q)函数。
2:加入一个全局变量,指向块设备需要的请求队列:
static struct request_queue *simp_blkdev_queue;
3:在加载模块时用simp_blkdev_do_request()函数的地址作参数调用blk_init_queue()初始化一个请求队列:
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
if (!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_init_queue;
}
4:卸载模块时把simp_blkdev_queue还回去:
blk_cleanup_queue(simp_blkdev_queue);
5:在?3的位置填上simp_blkdev_queue。
第4个问号:
这个还好,比前面的简单多了,这里需要设置块设备的大小。
块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。
当然,在把字节为单位的大小转换为以扇区为单位时,我们需要除以512,或者右移9位可能更快一些。
同样,我们试图把这一步也做得绅士一些,因此使用宏定义了块设备的大小,目前我们定为16M:
#define SIMP_BLKDEV_BYTES
1:用add_disk()函数向系统中添加这个块设备
当然,在添加设备之前我们需要申请这个设备的资源,这用到了alloc_disk()函数,因此模块入口函数simp_blkdev_init(void)应该是:
还有别忘了在卸载模块的代码中也加一个行清理函数:
还有就是,设备有关的属性也是需要设置的,因此在alloc_disk()和add_disk()之间我们需要:
SIMP_BLKDEV_DISKNAME其实是这个块设备的名称,为了绅士一些,我们把它定义成宏了:
#define SIMP_BLKDEV_DISKNAME
这里又引出了4个问号。(天哪,是不是有种受骗的感觉,像是陪老婆去做头发)
第1个问号:
第2个问号:
第3个问号:
第4个问号: