块设备驱动用到的重要结构体与函数如下红色表示部分。
快设备驱动的模块加载函数中通常需要完成如下操作:
1. 分配、初始化请求队列,绑定请求队列request_queue和请求函数;
2. 分配、初始化gendisk,给gendisk的major,fops,queque等成员赋值,最后添加gendisk;
3. 注册快设备驱动。
本代码源自“LINUX设备驱动开发技术及应用 ”一书,经过少许修改,在2.6.28.12内核编译通过,并运行正常。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/vmalloc.h>
#include <linux/hdreg.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <asm/uaccess.h>
/*设备名称,段大小,设备大小等信息的定义*/
#define VRD_DEV_NAME "vrd"
#define VRD_DEV_MAJOR 220
#define VRD_MAX_DEVICE 2
#define VRD_SECTOR_SIZE 512
#define VRD_SIZE (4*1024*1024)
#define VRD_SECTOR_TOTAL (VRD_SIZE/VRD_SECTOR_SIZE)
/*2.6内核的设备各自管理请求队列的结构体request_queue和gendisk。request()函数处理该结构题的信息和设备驱动程序自身的信息,因此不使用全局变量,而是使用较为有效的结构体。vrd设备驱动程序定义了vrd_devices。这里使用make_request处理方式,因此不设置LOCK,就能定义管理request_queue结构题的queue域,管理gendisk结构体的gd域,以及RAM Disk的内存管理data域*/
typedef struct
{
unsigned char *data;
structrequest_queue *queue;
structgendisk *gd;
}vrd_device;
/*内存disk需要分配内存,分配的内存会放到vdisk上*/
static char *vdisk[VRD_MAX_DEVICE];
/*为了支持将两台设备作为指针组,并分配和管理上衣不得vrd_devices应定义device数组*/
static vrd_device device[VRD_MAX_DEVICE];
/*由make_request函数的vrd_make_reque处理块设备。内核调用该函数,向块设备驱动发出请求时,使用传递的变量处理请求,在这里不实用request_queue结构题的q变量,实际使用的是bio结构*/
static int vrd_make_request(struct request_queue *q, struct bio *bio)
{
vrd_device *pdevice;
char *pVHDDData;
char *pBuffer;
struct bio_vec *bvec;
int i;
/*首先检查请求的扇区大小和位置是否超出设备的范围。内核传递的信息是以请求的起始扇区和字节为单位的容量。请求的起始扇区号传给bio->bi_sector,请求的大小传给bio->bi_size。该值超出容量时,使用bio_io_error(*,*)来处理错误,bio_io_error在2.6内核里的函数原型并不都相同,在有的内核里是两个参数,有的里边是1个参数;bio_endio也是一样,有的是三个参数有的是2个参数*/
if(((bio->bi_sector*VRD_SECTOR_SIZE) + bio-> bi_size) > VRD_SIZE)/*因为RAM Disk没有物理扇区,因此,在vdisk所分配的内存上读取或写入数据。此时的处理位置应该为地址,传送的位置为扇区数,乘以VRD_SECTOR_SIZE大小就可以求出实际地址*/
goto fail;
/*在请求属于正常范围时,vrd就会利用bio->bi_bdev->bd_disk-> private_data获取gendisk的private_data上分配的vrd_device管理结构体的地址。使用该地址所指结构体域中包含的pdevice->data的内存地址,把对应的bio-> bi_sector*VRD_SECTOR_SIZE 的内存地址写入pVHDDData中*/
pdevice = (vrd_device *) bio->bi_bdev->bd_disk-> private_data;
pVHDDData = pdevice->data + (bio-> bi_sector*VRD_SECTOR_SIZE);
/*由于bio变量中含有多个请求处理的bio_vec结构体,为了依次处理各个bio_vec结构体的矢量,使用bio_for_each_segment宏。在宏内部执行for语句时,使用该宏的i变量,因此不能修改*/
bio_for_each_segment(bvec, bio, i)
{
/*请求读/写的扇区信息包含的内核的缓存上,而该缓存以page进行传递。为了获得实际地址,使用kmap函数把bvec->bv_page转换成内存地址,并加上以bvec->bv_offset表示的offset位置,在pBuffer上设置实际内存地址*/
pBuffer = kmap(bvec->bv_page) + bvec-> bv_offset;
switch(bio_data_dir(bio))
{
case READA :
case READ : memcpy(pBuffer, pVHDDData, bvec-> bv_len);
break;
case WRITE : memcpy(pVHDDData, pBuffer, bvec-> bv_len);
break;
default : kunmap(bvec->bv_page);
goto fail;
}
/*每处理一个bvec,把由kmap函数映射的bvec->bv_page,使用kunmap函数取消映射,以bvec->bv_len大小增加pVHDDDData所指的内存位置。*/
kunmap(bvec->bv_page);
pVHDDData += bvec->bv_len;
}
/*结束处理,并终止vrd_make_request函数*/
bio_endio(bio, /*bio->bi_size, */0);
return 0;
fail:
bio_io_error(bio/*, bio->bi_size*/);
return 0;
}
/*安装块设备时,调用open函数,该函数没有特殊的处理内容。对于可清除设备或完整的设备驱动程序可自行处理设备的使用计数*/
int vrd_open(struct inode *inode, struct file *filp)
{
return 0;
}
/*取消安装设备时,调用此函数。该函数没有特殊的处理内容。对于可清除设备或完整的设备驱动程序可自行处理设备的使用计数*/
int vrd_release (struct inode *inode, struct file *filp)
{
return 0;
}
/*vrd是非常简单的RAM disk,对于ioctl命令可全部返回-ENOTTY。2.6内核的ioctl函数就由内核自行处理*/
int vrd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
unsigned long arg)
{
return -ENOTTY;
}
/*vbrd块设备并不是可清除设备,因此,在block_device_operations结构体的域中支持open,release,ioctl相关函数就行*/
static structblock_device_operations vrd_fops =
{
.owner = THIS_MODULE,
.open = vrd_open,
.release = vrd_release,
.ioctl = vrd_ioctl,
};
int vrd_init(void)
{
int lp;
/*内核插入模块后,使用vmalloc函数来分配ram disk块设备的内存。此处没有考虑分配失败的情况*/
vdisk[0] = vmalloc(VRD_SIZE);
vdisk[1] = vmalloc(VRD_SIZE);
/*注册vrd设备驱动程序*/
register_blkdev(VRD_DEV_MAJOR, VRD_DEV_NAME);
/**/
for(lp = 0; lp < VRD_MAX_DEVICE; lp++)
{
device[lp].data = vdisk[lp];
/*分配gendisk结构题,gendisk结构题是注册会设备的信息结构体*/
device[lp].gd =alloc_disk(1);
device[lp].queue =blk_alloc_queue(GFP_KERNEL);
/*注册rd_make_request函数,该函数是内核和会设备驱动程序的实际输出路径。通过blk_alloc_queue函数分配到块设备的请求队列,并代入到vrd_request_queue中*/
blk_queue_make_request(device[lp].queue, &vrd_make_request);
/*设置块设备的想关域gd的major上分配为主设备号,无分区的从设备好表示各台块设备。在first_minor上设置设备的索引值。将会设备驱动程序的operation结构体带入到gd的fops中,并将各台设备的请求队列device[lp].queue在device[lp].gd->queue上进行设置。为了使vrd_make_request函数引用块设备的信息,在gd的private_data上代入device[lp]的地址
另外,为了使proc文件系统和sysfs文件系统表示分区及块设备,在device[lp].gd->disk_name上分别代入块名”vrda“和“vrdb”。最后用set_capacity函数设置各台块设备的总扇区数*/
device[lp].gd->major = VRD_DEV_MAJOR;
device[lp].gd->first_minor = lp;
device[lp].gd->fops = &vrd_fops;
device[lp].gd->queue = device[lp].queue;
device[lp].gd->private_data = &device[lp];
sprintf(device[lp].gd->disk_name, "vrd%c" , 'a'+lp);
set_capacity(device[lp].gd,VRD_SECTOR_TOTAL);
/*为了检索分区并注册块设备,调用add_disk()函数,调用该函数后,内核会在内核内部注册各台设备,需要检索分区时,在gendisk结构题内部设置分区处理变量*/
add_disk(device[lp].gd);
}
return 0;
}
/*清除模块未注销块设备驱动程序时,使用del_gendisk函数根据注册的设备数从内核中清除gendisk结构题,并使用put_disk函数清除gendisk结构题的内存。最后,使用unregister_blkdev函数清除块设备,使用vfree函数清除vrd所分配的内存。*/
void vrd_exit(void)
{
int lp;
for(lp = 0; lp < VRD_MAX_DEVICE; lp++)
{
del_gendisk(device[lp].gd);
put_disk(device[lp].gd);
}
unregister_blkdev(VRD_DEV_MAJOR, VRD_DEV_NAME);
vfree(vdisk[0]);
vfree(vdisk[1]);
}
module_init(vrd_init);
module_exit(vrd_exit);
MODULE_LICENSE("Dual BSD/GPL");
上边源代码文件名称为vrd.c
1.编写Makefile 内容如下:obj-m := vrd.o
2.编译:make -C /usr/src/linux-source2.6.28/ M=$(pwd) modules
编译成功后,会出现vrd.ko模块文件
3.insmod vrd.ko,如果没有错误显示
4.cat /proc/devices 里边的block设备中会出现主设备号为220的vrd设备
5.可以使用mknod /dev/vrd0 b 220 0创建一个设备特殊文件于/dev下边的vrd0文件
6.使用mke2fs /dev/vrd0 将其格式化为ext2文件系统,并确认起是否运行正常,正常显示如下信息
mke2fs 1.41.4 (27-Jan-2009)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
1024 inodes, 4096 blocks
204 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=4194304
1 block group
8192 blocks per group, 8192 fragments per group
1024 inodes per group
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 24 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
7.指定了文件格式之后,就可以挂载文件了,创建任意目录并将上述块设备文件挂载在此目录下
mkdir rmdisk
mount dev/vrd0 rmdisk
cd rmdisk
ls -al
显示内容如下:
drwxr-xr-x 3 root root 1024 2009-06-03 18:18 .
drwxr-xr-x 4 besimple besimple 4096 2009-06-03 11:10 ..
drwx------ 2 root root 12288 2009-06-03 18:18 lost+found
8.还可以编辑文件保存在此目录下边或者复制文件到此目录下边,然后检查起内容,最后确认其是否能够正常卸载
umount rmdisk
看看rmdisk下边的内容,再挂载一次看看里边的内容和上次一样不一样
9.最后确认模块是否能清除
rmmod vrd
如果没有卸载上述设备文件,就清除模块,则会显示"模块正在使用中"