Linux驱动----块驱动开发实例
详细原理不介绍, 参照《Linux 设备驱动 Edition 3》中第16章块设备驱动. 本文把Linux设备驱动示例代码sbull移植到2.6.32版本,对应于发行版redhat6,centos6。由于对sbull比较复杂,便于理解,进行相应裁剪,去掉对功能没有影响代码,具体原因测试过程,可以不修改(你觉得无关紧要或者无法确定代码),看看会出现什么问题,这样会理解更深。
注意:
1.本人内核版本:
# uname -r
2.6.32-71.el6
2.发行版:
# cat /etc/redhat-release
CentOS Linux release 6.0 (Final)
一.测试:
这个块驱动程序,以内存代替硬盘,也就是我们常说的ramdisk,既然这样,就可以实现文件系统任何操作。例如:磁盘分区, 文件系统建立, 文件系统挂载/卸载,文件操作等
1.编译,运行,加载内核模块,查看设备文件是否创建
# insmod bdevice.ko
# ls /dev/vbda
/dev/vbda
2.磁盘分区
#fdisk /dev/vbda
Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-1, default 1): w
Using default value 1
Command (m for help): w
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
# ls /dev/vbda1
3.文件系统建立
# mkfs.ext3 /dev/vbda1
mke2fs 1.41.12 (17-May-2010)
文件系统标签=
操作系统:Linux
块大小=1024 (log=0)
分块大小=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
2000 inodes, 8000 blocks
400 blocks (5.00%) reserved for the super user
第一个数据块=1
Maximum filesystem blocks=8388608
1 block group
8192 blocks per group, 8192 fragments per group
2000 inodes per group
正在写入inode表: 完成
Creating journal (1024 blocks): 完成
Writing superblocks and filesystem accounting information: 完成
This filesystem will be automatically checked every 20 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
4文件系统挂载
# mount /dev/vbda1 /opt/
# cd /opt/
# ls
#dmesg
查看扇区数
write:offset:%ld,nbytes:%ld\n
#free
查看buffer的应用情况
buffers
Mem: xxxx
5.文件读写操作
# date >xxx
# cat xxx
2013年 08月 30日 星期五 09:58:29 CS
# rm xxx -f
6.dd 操作测试
#free
#dmesg -c
#dd if=/dev/zero of=aaa
#dmesg
查看扇区数
write:offset:%ld,nbytes:%ld\n
#free
查看cached的应用情况
cached
Mem: xxxx
#dmesg -c
#dd if=/dev/zero of=/dev/vbda
#dmesg
查看扇区数
write:offset:%ld,nbytes:%ld\n
#free
查看cached的应用情况
cached
Mem: xxxxx
7.文件系统卸载
# umount /opt/
8.内核模块卸载
# rmmod bdevice.ko
思考:
什么情况下,消耗cached?
什么情况下,消耗buffers?
#echo 3 >/proc/sys/vm/drop_caches
二.代码
==================================
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/spinlock.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h> /* invalidate_bdev */
#include <linux/bio.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/blkdev.h>
#define KERNEL_SECTOR_SIZE 512
static int major, index=0;
static int hardsect_size = 512;
static int nsectors = 1024*2*10; /* How big the drive is */
#define DONE_OK 0;
struct virt_dev {
spinlock_t lock;
int size; /* Device size in sectors */
u8 *data; /* The data array */
//unsigned long ioaddr;
//unsigned int irq;
struct gendisk *disk;
struct request_queue *queue;
};
static struct virt_dev *vbd;
static const struct block_device_operations vbd_fops = {
.owner = THIS_MODULE,
};
/*
* * Handle an I/O request.
* */
static void sbull_transfer(struct virt_dev *dev, unsigned long sector,
unsigned long nsect, char *buffer, int write)
{
if(write)
printk("write:offset:%ld,nbytes:%ld\n", sector, nsect);
else
printk("read:offset:%ld,nbytes:%ld\n", sector, nsect);
unsigned long offset = sector*512;
unsigned long nbytes = nsect*512;
if ((offset + nbytes) > dev->size) {
printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if (write)
memcpy(dev->data + offset, buffer, nbytes);
else
memcpy(buffer, dev->data + offset, nbytes);
}
/*
* * Transfer a single BIO.
* */
static int sbull_xfer_bio(struct virt_dev *dev, struct bio *bio)
{
int i;
struct bio_vec *bvec;
sector_t sector = bio->bi_sector;
/* Do each segment independently. */
bio_for_each_segment(bvec, bio, i) {
printk("sbull_xfer_bio\n");
char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
sbull_transfer(dev, sector, bio_cur_bytes(bio)>>9,
buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_bytes(bio)>>9;
__bio_kunmap_atomic(bio, KM_USER0);
}
return 0; /* Always "succeed" */
}
/*
* * Transfer a full request.
* */
static int sbull_xfer_request(struct virt_dev *dev, struct request *req)
{
struct bio *bio;
int nsect = 0;
__rq_for_each_bio(bio, req) {
printk("sbull_xfer_request\n");
sbull_xfer_bio(dev, bio);
nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
}
return nsect;
}
static void do_vbd_request(struct request_queue *q)
{
struct request *req;
int sectors_xferred;
while ((req = blk_peek_request(q)) != NULL) {
printk("do_vbd_request begin\n");
blk_start_request(req);
if (blk_fs_request(req)) {
struct virt_dev *dev = req->rq_disk->private_data;
/*
do{
printk("sectors:%d\n",blk_rq_cur_sectors(req));
sbull_transfer(dev, blk_rq_pos(req), blk_rq_cur_sectors(req),
req->buffer, rq_data_dir(req));
} while(__blk_end_request_cur(req, 0));
*/
sectors_xferred = sbull_xfer_request(dev, req);
}
__blk_end_request_all(req, 0);
}
}
static int setup_vblk(void)
{
int err;
vbd = kmalloc(sizeof(struct virt_dev), GFP_KERNEL);
if(!vbd) {
err= -ENOMEM;
return err;
}
spin_lock_init(&vbd->lock);
vbd->size = nsectors*hardsect_size;
vbd->data = vmalloc(vbd->size);
memset (vbd->data, 0, vbd->size);
vbd->disk = alloc_disk(1 << 4);
if (!vbd->disk) {
err = -ENOMEM;
goto out_free;
}
vbd->disk->queue = blk_init_queue(do_vbd_request, &vbd->lock);
if (!vbd->disk->queue) {
err = -ENOMEM;
goto out_put_disk;
}
sprintf(vbd->disk->disk_name, "vbd%c", 'a'+ index % 26);
printk("vbd->disk->disk_name=%s\n" ,vbd->disk->disk_name);
vbd->disk->major = major;
vbd->disk->first_minor = 0;
vbd->disk->private_data = vbd;
vbd->disk->fops = &vbd_fops;
set_capacity(vbd->disk, nsectors);
add_disk(vbd->disk);
return 0;
out_put_disk:
put_disk(vbd->disk);
out_free:
kfree(vbd);
return err;
}
static int __init init(void)
{
int err;
major = register_blkdev(0, "vblk");
if(major < 0)
return major;
printk("major=%d\n" , major);
err = setup_vblk();
if (err) {
printk("setup_vblk err\n");
goto out_err;
}
return 0;
out_err:
unregister_blkdev(major, "vblk");
return err;
}
static void __exit fini(void)
{
printk("%s\n", __func__);
del_gendisk(vbd->disk);
blk_cleanup_queue(vbd->disk->queue);
put_disk(vbd->disk);
kfree(vbd);
unregister_blkdev(major,"vblk");
}
MODULE_LICENSE("GPL");
module_init(init);
module_exit(fini);