linux块设备优缺点,linux块设备分析与使用

linux块设备分析与使用

creator

sz111@126.com

本篇文章力求简单明了的解释Linux的块设备驱动,让大家对它少些畏惧,快速的开发一个块设备。分析完这篇之后,下一步就是分析MMC卡的驱动,争取分析之后可以达到优化读卡速度的目的。Linux的块设备看似比较复杂,其实梳理一下并不难,有如下两点:

1.对请求的响应。request。(如果使用请求队列)

2.制造请求。make_request.(不使用请求队列)

request是采用一定的算法组合了请求以提高性能,这个时候算法组合就是系统默认的make_request函数,函数名为__mak_request,而如果不采用请求组合的时候,就可以自定义make_request函数。因为内核中这个函数是个函数指针,可以改变的。有些时候不需要组合的方式,如SD卡和RAMDISK。

以上两个方式都不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作,它才会调用这个函数。

基本上块设备就是对以上两种方式选择一个,然后对其进行处理。所有的块设备驱动都是围绕这个部分展开。期间有很多数据结构需要我们特别注意。

我们先使用请求队列对请求进行响应,等于是采用默认的make_request函数,采用Linux默认的队列优先级算法。对这个方法进行分析展开。

原型为void request(request_queue_t *queue)

主要依靠这个函数对请求进行响应,并且所有的请求最终都被驱动处理。这个函数是被内核来调用。每个设备都有一个请求队列。Request函数会在设备的请求队列生成的时候和队列绑定在一起。通过函数

request_queue_t

*blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

生成一个请求队列。其中rfn函数就是我们用户自己的request函数。生成的这个队列会放到gendisk结构里面,gendisk是来表示一个独立的磁盘设备或分区。下面我们对request函数实例进行分析。

static void

sbull_request(request_queue_t *q)

{

struct request *req;

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

sbull_transfer(dev,req->sector,req->current_nr_sectors,

req->buffer,rq_data_dir(req);

end_request(req,1);

}

}

这里面先从队列取得第一个未完成的请求(request结构),取得req是采用elv_next_request函数,为何采用这个函数呢?上面我们讲了,如果我们没有设定make_request函数的时候,就采用默认的make_request函数,elv_next_request是一个函数指针。目前Linux采用的是电梯调度程序程序,所以如果取得下一个未完成的请求就要采用电梯调度程序中的函数。当然,如果更换了调度程序,就不能采用这个函数了。

end_request(req,1)表示对req的响应成功。

void

end_request(struct request *req, int uptodate)

{

if (!end_that_request_first(req,

uptodate, req->hard_cur_sectors)) {

add_disk_randomness(req->rq_disk);

blkdev_dequeue_request(req);//从队列里面删除请求。

end_that_request_last(req);

}

}

end_that_request_first:驱动程序从前一次结束的地方开始,完成了规定数目的扇区的传输。如果成功,返回0.表示该请求执行完毕。这时候必须用blkdev_dequeue_request(req)从队列里面删除请求,并把其传输给end_that_request_last(req);通知任何等待已经晚上请求的对象,并重复利用该request结构。

我们对request结构主要关心3个成员变量。

sector_t

sector;

开始扇区索引号。特别注意的是,指的是512字节的扇区,如果硬件上2048的话,把其放入请求中的时候,要将它除以4

unsigned long

nr_sectors

需要传输的扇区(512字节)数。

unsigned int

current_nr_sectors

当前需要传输的扇区(512字节)数

另外一个重要的就是struct bio *bio;

bio是给这个请求的bio结构的链表.你不应当直接存取这个成员;使用rq_for_each_bio(后面描述)代替.

具体的关于IO操作的东西都在bio里面。

刚才我分析的是采用请求队列的方式,现在我们分析不使用请求队列的方式。为使用这个模式,你的驱动必须提供一个"制作请求"函数,而不是一个请求函数. make_request函数有这个原型:

typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);

注意一个请求队列仍然存在,即便它从不会真正有任何请求.

make_request函数用一个bio结构作为它的主要参数,这个bio结构表示一个或多个要传送的缓冲. make_request函数做2个事情之一:它可或者直接进行传输,或者重定向这个请求到另一个设备.

直接进行传送只是使用我们前面描述的存取者方法来完成这个bio.因为没有使用请求结构,但是,你的函数应当通知这个bio结构的创建者直接指出完成,使用对bio_endio的调用:

void bio_endio(struct bio *bio, unsigned int bytes, int error);

这里, bytes是你至今已经传送的字节数.它可小于由这个bio整体所代表的字节数;在这个方式中,你可指示部分完成,并且更新在bio中的内部的"当前缓冲"指针.你应当再次调用bio_endio在你的设备进行进一步处理时,或者当你不能完成这个请求指出一个错误.错误是通过提供一个非零值给error参数来指示的;这个值通常是一个错误码,例如-EIO. make_request应当返回0,不管这个I/O是否成功.

如果sbull用request_mode=2加载,它操作一个make_request函数.因为sbull已经有一个函数看传送单个bio,这个make_request函数简单:

static int sbull_make_request(request_queue_t *q, struct bio *bio){struct sbull_dev *dev = q->queuedata;int status;status = sbull_xfer_bio(dev, bio);bio_endio(bio, bio->bi_size, status);return 0;}

请注意你应当从不调用bio_endio从一个通常的请求函数;那个工作由end_that_request_first代替来处理.

一些块驱动,例如那些实现卷管理者和软件RAID阵列的,真正需要重定向请求到另一个设备来处理真正的I/O.编写这样的一个驱动超出了本书的范围.我们,但是,注意如果

make_request函数返回一个非零值, bio被再次提交.一个"堆叠"驱动,可,因此,修改bi_bdev成员来指向一个不同的设备,改变起始扇区值,接着返回;块系统接着传递bio到新设备.还有一个bio_split调用来划分一个bio到多个块以提交给多个设备.尽管如果队列参数被之前设置,划分一个bio几乎从不需要.

任何一个方式,你都必须告知块子系统,你的驱动在使用一个自定义的make_request函数.为此,你必须分配一个请求队列,使用:

request_queue_t *blk_alloc_queue(int flags);

这个函数不同于blk_init_queue,它不真正建立队列来持有请求. flags参数是一组分配标志被用来为队列分配内存;常常地正确值是GFP_KERNEL.一旦你有一个队列,传递它和你的make_request函数到blk_queue_make_request:

void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);

sbull代码来设置make_request函数,象:

dev->queue = blk_alloc_queue(GFP_KERNEL);if (dev->queue == NULL)goto out_vfree;blk_queue_make_request(dev->queue, sbull_make_request);

对于好奇的人,花些时间深入drivers/block/ll_rw_block.c会发现,所有的队列都有一个make_request函数.缺省的版本, generic_make_request,处理bio和一个请求结构的结合.通过提供一个它自己的make_request函数,一个驱动真正只覆盖一个特定的请求队列方法,并且排序大部分工作.

下面我们就对LDD3上面的块设备例子进行分析。

/*

* Sample disk driver, from the beginning.

*/

#include

#include

#include

#include

#include

#include

/* printk() */

#include

/* kmalloc()

*/

#include

/*

everything... */

#include

/* error codes

*/

#include

#include

/* size_t */

#include

/* O_ACCMODE */

#include

/* HDIO_GETGEO

*/

#include

#include

#include

#include

#include

/*

invalidate_bdev */

#include

MODULE_LICENSE("Dual

BSD/GPL");

static int

sbull_major = 0;

module_param(sbull_major,

int, 0);

static int

hardsect_size = 512;

module_param(hardsect_size,

int, 0);

static int

nsectors = 1024;/* How big the drive

is */

module_param(nsectors,

int, 0);

static int

ndevices = 4;

module_param(ndevices,

int, 0);

/*

* The different "request modes" we

can use.

*/

enum {

RM_SIMPLE= 0,/* The extra-simple

request function采用简单的方式*/

RM_FULL= 1,/* The full-blown

version采用比较全的队列传输方式。*/

RM_NOQUEUE = 2,/* Use make_request不采用队列请求,采用制造请求的方式*/

};

static int

request_mode = RM_SIMPLE;//默认采用简单方式

module_param(request_mode,

int, 0);

/*

* Minor number and partition management.

*/

#define

SBULL_MINORS16

#define

MINOR_SHIFT4

#define

DEVNUM(kdevnum)(MINOR(kdev_t_to_nr(kdevnum))

>> MINOR_SHIFT

/*

* We can tweak our hardware sector size, but

the kernel talks to us

* in terms of small sectors, always.

*/

#define

KERNEL_SECTOR_SIZE512

/*

* After this much idle time, the driver will

simulate a media change.

*/

#define

INVALIDATE_DELAY30*HZ

/*

* The internal representation of our device.

*/

struct

sbull_dev {

int size;/* Device size in

sectors */

u8 *data;/* The data array */

short users;/* How many users */

short media_change;/* Flag a media change? */

spinlock_t lock;/* For mutual exclusion */

struct request_queue *queue;/* The device request queue */

struct gendisk *gd;/* The gendisk structure */

struct timer_list timer;/* For simulated media changes */

};

static struct

sbull_dev *Devices = NULL;

/*

* Handle an I/O request.

*/

static void

sbull_transfer(struct sbull_dev *dev, unsigned long sector,

unsigned long nsect, char *buffer,

int write)

{

unsigned long offset =

sector*KERNEL_SECTOR_SIZE;

unsigned long nbytes =

nsect*KERNEL_SECTOR_SIZE;

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);

}

/*

* The simple form of the request function.

*/

static void

sbull_request(request_queue_t *q)

{

struct request *req;

while ((req = elv_next_request(q)) !=

NULL) {

struct sbull_dev *dev =

req->rq_disk->private_data;

if (! blk_fs_request(req)) {

printk (KERN_NOTICE

"Skip non-fs request\n");

end_request(req, 0);

continue;

}

//printk (KERN_NOTICE "Req

dev %d dir %ld sec %ld, nr %d f %lx\n",

//dev - Devices,

rq_data_dir(req),

//req->sector,

req->current_nr_sectors,

//req->flags);

sbull_transfer(dev,

req->sector, req->current_nr_sectors,

req->buffer,

rq_data_dir(req));

end_request(req, 1);

}

}

/*

* Transfer a single BIO.

*/

static int

sbull_xfer_bio(struct sbull_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) {

char *buffer =

__bio_kmap_atomic(bio, i, KM_USER0);

sbull_transfer(dev, sector,

bio_cur_sectors(bio),

buffer,

bio_data_dir(bio) == WRITE);

sector += bio_cur_sectors(bio);

__bio_kunmap_atomic(bio,

KM_USER0);

}

return 0; /* Always "succeed"

*/

}

/*

* Transfer a full request.

*/

static int

sbull_xfer_request(struct sbull_dev *dev, struct request *req)

{

struct bio *bio;

int nsect = 0;

rq_for_each_bio(bio, req) {

sbull_xfer_bio(dev, bio);

nsect +=

bio->bi_size/KERNEL_SECTOR_SIZE;

}

return nsect;

}

/*

* Smarter request function that "handles

clustering".

*/

static void

sbull_full_request(request_queue_t *q)

{

struct request *req;

int sectors_xferred;

struct sbull_dev *dev = q->queuedata;

while ((req = elv_next_request(q)) !=

NULL) {

if (! blk_fs_request(req)) {

printk (KERN_NOTICE

"Skip non-fs request\n");

end_request(req, 0);

continue;

}

sectors_xferred =

sbull_xfer_request(dev, req);

if (! end_that_request_first(req,

1, sectors_xferred)) {

blkdev_dequeue_request(req);

end_that_request_last(req);

}

}

}

/*

* The direct make request version.

*/

static int

sbull_make_request(request_queue_t *q, struct bio *bio)

{

struct sbull_dev *dev = q->queuedata;

int status;

status = sbull_xfer_bio(dev, bio);//制造请求就是直接对bio进行处理,然后通过bio_endio来通知上层处理情况。

bio_endio(bio, bio->bi_size, status);

return 0;

}

/*

* Open and close.

*/

static int

sbull_open(struct inode *inode, struct file *filp)

{

struct sbull_dev *dev =

inode->i_bdev->bd_disk->private_data;

del_timer_sync(&dev->timer);

filp->private_data = dev;

spin_lock(&dev->lock);

if (! dev->users)

check_disk_change(inode->i_bdev);

dev->users++;

spin_unlock(&dev->lock);

return 0;

}

static int

sbull_release(struct inode *inode, struct file *filp)

{

struct sbull_dev *dev =

inode->i_bdev->bd_disk->private_data;

spin_lock(&dev->lock);

dev->users--;

if (!dev->users) {

dev->timer.expires = jiffies +

INVALIDATE_DELAY;

add_timer(&dev->timer);

}

spin_unlock(&dev->lock);

return 0;

}

/*

* Look for a (simulated) media change.

*/

int

sbull_media_changed(struct gendisk *gd)

{

struct sbull_dev *dev =

gd->private_data;

return dev->media_change;

}

/*

* Revalidate.WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking

* with open.That needs to be reevaluated.

*/

int

sbull_revalidate(struct gendisk *gd)

{

struct sbull_dev *dev =

gd->private_data;

if (dev->media_change) {

dev->media_change = 0;

memset (dev->data, 0,

dev->size);

}

return 0;

}

/*

* The "invalidate" function runs out

of the device timer; it sets

* a flag to simulate the removal of the media.

*/

void

sbull_invalidate(unsigned long ldev)

{

struct sbull_dev *dev = (struct sbull_dev

*) ldev;

spin_lock(&dev->lock);

if (dev->users || !dev->data)

printk (KERN_WARNING "sbull:

timer sanity check failed\n");

else

dev->media_change = 1;

spin_unlock(&dev->lock);

}

/*

* The ioctl() implementation

*/

int sbull_ioctl

(struct inode *inode, struct file *filp,

unsigned int cmd, unsigned

long arg)

{

long size;

struct hd_geometry geo;

struct sbull_dev *dev =

filp->private_data;

switch(cmd) {

case HDIO_GETGEO:

/*

* Get geometry: since we are a virtual device,

we have to make

* up something plausible.So we claim 16 sectors, four heads,

* and calculate the corresponding number of

cylinders.We set the

* start of data at sector four.

*/

size

= dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);

geo.cylinders = (size & ~0x3f)

>> 6;

geo.heads = 4;

geo.sectors = 16;

geo.start = 4;

if (copy_to_user((void __user *)

arg, &geo, sizeof(geo)))

return -EFAULT;

return 0;

}

return -ENOTTY; /* unknown command */

}

/*

* The device operations structure.

*/

static struct

block_device_operations sbull_ops = {

.owner= THIS_MODULE,

.open = sbull_open,

.release = sbull_release,

.media_changed= sbull_media_changed,

.revalidate_disk = sbull_revalidate,

.ioctl= sbull_ioctl

};

/*

* Set up our internal device.

*/

static void

setup_device(struct sbull_dev *dev, int which)

{

/*

*

Get some memory.

*/

memset (dev, 0, sizeof (struct

sbull_dev));

dev->size = nsectors*hardsect_size;

dev->data = vmalloc(dev->size);

if (dev->data == NULL) {

printk (KERN_NOTICE "vmalloc

failure.\n");

return;

}

spin_lock_init(&dev->lock);

/*

*

The timer which "invalidates" the device.

*/

init_timer(&dev->timer);

dev->timer.data = (unsigned long) dev;

dev->timer.function =

sbull_invalidate;

/*

*

The I/O queue, depending on whether we are using our own

*

make_request function or not.

*/

switch (request_mode) {

case RM_NOQUEUE://采用制造请求的方式,需要采用blk_alloc_queue分配队列,同时采用blk_queue_make_request设定制造请求函数。

dev->queue =

blk_alloc_queue(GFP_KERNEL);

if (dev->queue == NULL)

goto out_vfree;

blk_queue_make_request(dev->queue,

sbull_make_request);

break;

case RM_FULL://简单和复杂的对请求处理的函数,都是要采用blk_init_queue来初始化队列

dev->queue =

blk_init_queue(sbull_full_request, &dev->lock);

if (dev->queue == NULL)

goto out_vfree;

break;

default:

printk(KERN_NOTICE "Bad

request mode %d, using simple\n", request_mode);

/*

fall into.. */

case RM_SIMPLE://简单和复杂的对请求处理的函数,都是要采用blk_init_queue来初始化队列

dev->queue =

blk_init_queue(sbull_request, &dev->lock);

if (dev->queue == NULL)

goto out_vfree;

break;

}

blk_queue_hardsect_size(dev->queue,

hardsect_size);

dev->queue->queuedata = dev;

/*

*

And the gendisk structure.

*/

dev->gd = alloc_disk(SBULL_MINORS);

if (! dev->gd) {

printk (KERN_NOTICE

"alloc_disk failure\n");

goto out_vfree;

}

dev->gd->major = sbull_major;

dev->gd->first_minor =

which*SBULL_MINORS;

dev->gd->fops = &sbull_ops;

dev->gd->queue = dev->queue;

dev->gd->private_data = dev;

snprintf (dev->gd->disk_name, 32,

"sbull%c", which + 'a');

set_capacity(dev->gd,

nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));

add_disk(dev->gd);

return;

out_vfree:

if (dev->data)

vfree(dev->data);

}

static int

__init sbull_init(void)

{

int i;

/*

*

Get registered.

*/

sbull_major =

register_blkdev(sbull_major, "sbull");//注册一个块设备

if (sbull_major <= 0) {

printk(KERN_WARNING "sbull:

unable to get major number\n");

return -EBUSY;

}

/*

*

Allocate the device array, and initialize each one.

*/

Devices = kmalloc(ndevices*sizeof (struct

sbull_dev), GFP_KERNEL);

if (Devices == NULL)

goto out_unregister;

for (i = 0; i < ndevices; i++)

setup_device(Devices + i, i);

return 0;

out_unregister:

unregister_blkdev(sbull_major,

"sbd");

return -ENOMEM;

}

static void

sbull_exit(void)

{

int i;

for (i = 0; i < ndevices; i++) {

struct sbull_dev *dev = Devices +

i;

del_timer_sync(&dev->timer);

if (dev->gd) {

del_gendisk(dev->gd);

put_disk(dev->gd);

}

if (dev->queue) {

if (request_mode ==

RM_NOQUEUE)

blk_put_queue(dev->queue);

else

blk_cleanup_queue(dev->queue);

}

if (dev->data)

vfree(dev->data);

}

unregister_blkdev(sbull_major,

"sbull");

kfree(Devices);

}

module_init(sbull_init);

module_exit(sbull_exit);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值