Chinaunix首页 | 论坛 | 博客
  • 博客访问: 919564
  • 博文数量: 96
  • 博客积分: 10071
  • 博客等级: 上将
  • 技术积分: 1118
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-20 17:54
文章分类

全部博文(96)

文章存档

2011年(3)

2010年(3)

2009年(29)

2008年(54)

2007年(7)

分类: LINUX

2008-05-22 23:02:35

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_MINORS     16  

#define MINOR_SHIFT 4

#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_SIZE 512

 

/*

 * After this much idle time, the driver will simulate a media change.

 */

#define INVALIDATE_DELAY      30*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);

阅读(10270) | 评论(0) | 转发(4) |
给主人留下些什么吧!~~