前面在Linux块设备驱动程序的引入一节,通过例子讲述了块设备驱动程序引入的原因,并从文件系统和设备层之间入口函数ll_rw_block开始分析,层层递进,理清了I/O调度层到块设备驱动之间的调用关系,最后根据这些关系,参考内核driverslockacsi.c设备驱动,总结出来注册块设备驱动的步骤。这一节将沿着上一节总结的编写设备驱动程序的思路,并参考内核中自带的块设备驱动程序,编写使用内存来模拟磁盘的块设备驱动程序,加深我们对块设备驱动程序的理解。
块设备驱动程序编写可大概分为以下几个步骤:
- 创建一个块设备
- 分配一个request_queue队列
- 分配一个gendisk结构体
- 设置gendisk结构体
- 设置主设备号、次设备号、设备名称、fops
- 设置gendisk中请求队列
- 设置设备容量大小
- 注册gendisk结构体
- 完成request_queue队列request_fn_proc函数的
下面我们按照上述步骤,开始编写用内存来模拟磁盘的块设备驱动程序
驱动的入口函数
int ramblock_init(void) { major = register_blkdev(0, "ramblock");------------------------------------>① /* 分配并初始化request_queue队列 */ ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);------>② /* 分配gendisk结构体 */ ramblock_disk = alloc_disk(16); //次设备号:分区个数----------------------->③ /* 初始化gendisk结构体 */ ramblock_disk->major = major;---------------------------------------------->④ ramblock_disk->first_minor = 0; sprintf(ramblock_disk->disk_name, "ramblock_disk"); ramblock_disk->fops = &ramblock_fops; ramblock_disk->queue = ramblock_queue; set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); /* 分配块设备读写操作的内存 */ ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);------------------------->⑤ /* 注册gendisk */ add_disk(ramblock_disk);--------------------------------------------------->⑥ return 0; }
入口函数ramblock_init中分配、设置、并注册了块设备驱动所需要的资源
① 注册一个块设备,register_blkdev函数传入的是要注册的主设备号和设备名称。相对于字符设备的注册函数,它的功能相对减少了很多,仅在/proc/devices目录下创建一个设备目录。当传入主设备号为0时,会自动分配主设备号
② 分配并初始化一个request_queue请求队列,设置请求队列的处理函数,并设置请求队列使用的自旋锁资源
③ 分配一个gendisk结构体,并设置gendisk可支持的最大分区个数
④ 初始化gendisk结构体,设置主设备号,设备节点名称,请求队列,设备容量等
⑤ 分配ram资源,用于模拟块设备读写的区域
⑥ 注册gendisk结构体
请求处理函数do_ramblock_request
应用程序对文件的读写请求通过文件系统和通用块层转换为一个或多个bio结构,再经过I/O调度层优化合并后,最终调用到gendisk中request_queue队列中的request_fn_proc函数,实现将对扇区的读写请求的内容写入到相关的扇区中。
do_ramblock_request函数实现如下:
static void do_ramblock_request(struct request_queue * q) { int res = 0; struct request *req; req = blk_fetch_request(q); ----------------------------------->① while (req) { /* 源/目的 */ unsigned long offset = blk_rq_pos(req) * 512;-------------->② /* 目的/源 */ //request->buffer /* 长度 */ unsigned long length = blk_rq_cur_sectors(req) * 512;------>③ if (req->cmd_type != REQ_TYPE_FS)-------------------------->④ goto done; if(rq_data_dir(req) == READ)------------------------------->⑤ { memcpy(req->buffer, ramblock_buf+offset, length); } else { memcpy(ramblock_buf+offset, req->buffer, length); } done: /* wrap up, 0 = success, -errno = fail */ if (!__blk_end_request_cur(req, res)) req = blk_fetch_request(q); } }
do_ramblock_request函数从请求队列里依次取出所有请求,获取请求中要读写数据的源地址、目的地址和传输数据长度(注意此处的地址是以扇区为单位),根据数据的传输方向,完成数据的传输
① 取出请求队列中的一个请求
② 获取当前请求的扇区位置,如果是读,此处是源地址,如果是写,此处是目的地址
③ 获取当前请求要传输的数据传输大小
④ 判断当前请求是否是文件系统读写请求,如果不是文件系统请求就结束当前请求,跳转到done位置执行
⑤ 判断请求中数据的传输方向,如果是读数据,就从请求中当前扇区的位置读数指定大小的数据传送到请求的buffer地址中,如果是写数据,就将请求中buffer地址的数据传输到当前扇区中
由于是使用ram模拟磁盘的读写,ram区域数据的传输直接使用memcpy函数完成
设置ram模拟的磁盘几何特征
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 总容量 = 盘面数 * 柱面数 * 扇区数 * 512 */ geo->heads = 4; geo->cylinders = 32; geo->sectors = RAMBLOCK_SIZE/geo->heads/geo->cylinders/512; return 0; } static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo, };
和字符设备一样,如果使用/dev接口访问块设备,最终就会回调这个操作方法集的注册函数。block_device_operations结构体的getgeo函数用于获得驱动信息,在初始化gendisk结构体时,被赋值给fops成员
该函数根据驱动器的几何信息填充一个hd_geometry结构体,hd_geometry结构体包含磁头,扇区,柱面等信息。由于驱动程序是使用内存模拟磁盘,所有此处可以随意设置,但要保证:总容量 = 盘面数 * 柱面数 * 扇区数 * 512
驱动程序的测试
1)加载驱动程序:insmod ramblock_drv.ko
2)格式化文件系统:mkdosfs /dev/rmablock_disk
3)挂载文件系统到/tmp目录:mount /dev/ramblock_disk /tmp
4)切换到/tmp目录:cd /tmp
5)创建文本并写入hello world字符串:echo "hello world" > hello.txt
6)切换到根目录:cd /
6)取消/tmp目录文件系统挂载:umount /tmp
7)是否/tmp目录中有文件:ls - l
8)重新挂载ramblock设备:mount /dev/ramblock_disk /tmp
9)切换到/tmp目录
10)看看5)创建的文本文件是否存在:cat hello.txt
完整驱动程序
#include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/mutex.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/gfp.h> #include <asm/uaccess.h> #include <asm/dma.h> #define RAMBLOCK_SIZE (1024*1024) static int major = 0; static char *ramblock_buf; static DEFINE_SPINLOCK(ramblock_lock); static struct gendisk *ramblock_disk; static struct request_queue *ramblock_queue; static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 总容量 = 盘面数 * 柱面数 * 扇区数 * 512 */ geo->heads = 4; geo->cylinders = 32; geo->sectors = RAMBLOCK_SIZE/geo->heads/geo->cylinders/512; return 0; } static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo, }; static void do_ramblock_request(struct request_queue * q) { int res = 0; struct request *req; req = blk_fetch_request(q); while (req) { /* 源/目的 */ unsigned long offset = blk_rq_pos(req) * 512; /* 目的/源 */ //request->buffer /* 长度 */ unsigned long length = blk_rq_cur_sectors(req) * 512; if (req->cmd_type != REQ_TYPE_FS) goto done; if(rq_data_dir(req) == READ) { memcpy(req->buffer, ramblock_buf+offset, length); } else { memcpy(ramblock_buf+offset, req->buffer, length); } done: /* wrap up, 0 = success, -errno = fail */ if (!__blk_end_request_cur(req, res)) req = blk_fetch_request(q); } } int ramblock_init(void) { major = register_blkdev(0, "ramblock"); /* 分配并初始化request_queue队列 */ ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); /* 分配gendisk结构体 */ ramblock_disk = alloc_disk(16); //次设备号:分区个数 /* 初始化gendisk结构体 */ ramblock_disk->major = major; ramblock_disk->first_minor = 0; sprintf(ramblock_disk->disk_name, "ramblock_disk"); ramblock_disk->fops = &ramblock_fops; ramblock_disk->queue = ramblock_queue; set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); /* 分配块设备读写操作的内存 */ ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /* 注册gendisk */ add_disk(ramblock_disk); return 0; } void ramblock_exit(void) { unregister_blkdev(major, "ramblock"); del_gendisk(ramblock_disk); put_disk(ramblock_disk); blk_cleanup_queue(ramblock_queue); kfree(ramblock_buf); } module_init(ramblock_init); module_exit(ramblock_exit); MODULE_LICENSE("GPL");