linux磁盘驱动编写,linux中block驱动的编写详解

2. 驱动程序详解

通过编写一个vmem_disk驱动来了解block驱动的结构,vmem_disk是一种模拟磁盘,其数据实际上存储在RAM中。它通过vmalloc()分配出来的内存空间来模拟出一个磁盘,以块设备方式来访问这片内存。现在来看其主要结构。

2.1 block_device_operations

Block_device_operations类似于字符设备驱动中的file_operations结构,它是对块设备各种操作的集合,定义代码如下:

struct block_device_operations {

int (*open) (struct block_device *, fmode_t);

int (*release) (struct gendisk *, fmode_t);

int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

int (*direct_access) (struct block_device *, sector_t,void **, unsigned long *);

int (*media_changed) (struct gendisk *);

int (*revalidate_disk) (struct gendisk *);

int (*getgeo)(struct block_device *, struct hd_geometry *);

struct module *owner;

};

1) 打开和释放

int (*open)(struct inode *inode ,struct file *filp);

int (*release)(struct inode *inode ,struct file *filp);

这个和字符设备驱动类似,当设备被打开和关闭时将调用它们。

2) IO控制

int (*ioctl)(struct inode *inode,struct file *filp uusignwd intcmd,unsigned long arg)

这个和字符设备驱动中的ioctrl类似,也是用于系统调用。块设备包含大量的标准请求,这些标准请求由linux通用块设备层处理,因此大部分ioctrl函数相当短。

3) 介质改变

int (*check_media_change) (kdev_t);

int (*revalidate) (kdev_t);

像磁盘、CD-ROM等块设备是可插拔的,因此需要有个函数来检测设备是否存在。当介质发生改变,使用revalidate_disk来响应,给驱动一个机会进行必要的工作来使介质准备好。

4) 获得驱动信息

int (*getgeo)(struct block_device *,struct hd_geometry *);

该函数根据驱动器的几何信息填充一个hd_geometry结构体,hd_geometry包含磁头、扇区、柱面等信息。

所以我们要填充这个结构体信息,并定义其对应函数。填充如下:

static struct block_device_operations vmem_fops={

.owner=THIS_MODULE,

.getgeo=vmem_getgeo,

.ioctl=vmem_ioctl,

.open=vmem_open,

.release=vmem_release,

};

我们只定义了open、release、ioctrl、getgeo函数。为了简化这个驱动,我们把open、release、ioctrl函数的具体内容也都省略了,只是给出一个定义,没有任何有效内容。但是hd_geometry的信息需要填充,所以getgeo函数定义如下:

static int vmem_getgeo(struct block_device *bdev, struct hd_geometry *geo)

{

geo->cylinders=1;

geo->heads=1;

geo->sectors=BLK_SIZE/SECTOR_SIZE;

return 0;

}

定义了使用的块设备的柱面、磁头和扇区个数。

2.2 gendisk结构体

在linux内核中,用gendisk结构体来表示一个独立的磁盘设备。就像字符设备驱动中使用cdev结构体一样,它也包含主次设备号,需要分配内存,释放结构体和初始化操作。

1) 分配gendisk

分配函数为:

struct gendisk *alloc_disk(int minors);

2) 增加gendisk

这个是用于注册磁盘设备,函数为:

void add_disk(struct gendisk *gd);

3) 释放gendisk

当不再需要使用磁盘时候,需要释放这个结构体,也即释放其分配的内存。

void del_gendisk(struct gendisk *gd);

以上这些函数在快设备初始化和关闭驱动中调用。

2.3 请求处理

每个块设备驱动的核心是它的请求函数,实际的工作,至少如设备的启动,都是在这个函数里完成的。块设备驱动程序的request函数有以下原型:

void request(request_queue_t *queue);

当内核需要驱动程序处理读取、写入以及其它对设备的操作时,就会调用该函数。在其返回前,request函数不必完成所有队列中的请求。事实上,对大多数真实设备而言,它可能没有完成任何请求。

每个设备都有一个请求队列,这是因为对磁盘数据实际传入和传出发生的时间,与内核请求的时间相差很大,因此内核需要有一定灵活性,以安排在适当时刻(比如把影响相邻磁盘扇区的请求分成一组)进行传输。

我们用一个简单的request函数:

static void vmem_request(struct request_queue *q){

struct request *req;

uint64_t pos=0;

ssize_t size=0;

struct bio_vec bvec;

int rv=0;

struct req_iterator iter;

void *kaddr=NULL;

while((req=blk_fetch_request(q)) != NULL){

spin_unlock_irq(q->queue_lock);

pos=blk_rq_pos(req)*SECTOR_SIZE;

size=blk_rq_bytes(req);

if(pos+size>vdev->size){

printk(KERN_WARNING "beyond addr/n");

rv=-EIO;

goto skip;

}

rq_for_each_segment(bvec, req, iter){

kaddr=kmap(bvec.bv_page);

rv=vmem_transfer(vdev, pos, bvec.bv_len, kaddr+bvec.bv_offset, rq_data_dir(req));

if(rv<0)

goto skip;

pos+=bvec.bv_len;

kunmap(bvec.bv_page);

}

skip:

blk_end_request_all(req, rv);

spin_lock_irq(q->queue_lock);

}

}

Blk_fetch_request从请求队列中获取一个请求,当没有请求需要时,返回NULL。然后while中的程序开始处理这个请求。当请求队列创建的时候,request函数绑定了它,并且提供了一个自旋锁。当调用request函数时,该锁由内核控制。因此request函数是一个原子上下文中运行的。因此在获得request时,需要通过spin_unlock_irq函数来解锁。

然后通过blk_rq_pos和blk_rq_bytes来获得请求中的位置和大小。rq_for_each_segment是一个宏定义,其遍历一个请求中的所有bio。这里插入一下对bio的介绍:

从本质上讲,一个request结构是作为一个bio结构的链表实现的。Bio结构是在底层对部分块设备IO请求的描述。Bio结构体定义如下:

与bio对应的数据每次存放的内存不一定是连续的,bio_vec结构体用于描述与这个bio对应的所有内存,它并不总是在一个页面里,因此需要一个向量。IO调度算法将连续的bio合并成一个request,然后可以改善读写磁盘的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值