ramdisk驱动程序分析-2.6内核--块设备驱动框架(1)

来源:互联网 发布:淘宝ipad5.0.1版本 编辑:程序博客网 时间:2024/05/10 11:12

块设备驱动用到的重要结构体与函数如下红色表示部分。

快设备驱动的模块加载函数中通常需要完成如下操作:

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_queuegendiskrequest()函数处理该结构题的信息和设备驱动程序自身的信息,因此不使用全局变量,而是使用较为有效的结构体。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_error2.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获取gendiskprivate_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命令可全部返回-ENOTTY2.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);

/*设置块设备的想关域gdmajor上分配为主设备号,无分区的从设备好表示各台块设备。在first_minor上设置设备的索引值。将会设备驱动程序的operation结构体带入到gdfops中,并将各台设备的请求队列device[lp].queuedevice[lp].gd->queue上进行设置。为了使vrd_make_request函数引用块设备的信息,在gdprivate_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
如果没有卸载上述设备文件,就清除模块,则会显示"模块正在使用中"