mfkp3 发表于 2014-11-27 15:53

make_request_fn 中能否调用wait_for_completion等bio完成.

本帖最后由 mfkp3 于 2014-11-28 11:02 编辑

本人内核刚入门,尝试写个简单块设备模块例子。遇到一些问题,诚向大家请教:

例子中虚拟一个块设备minidev, 底层的实际裸设备是/dev/sda. 实现make_request函数,在make_request函数中封装下发的bio, 并等待该bio完成,以实现同步写。但是发现系统并没有触发我的end_io, 导致写的进程卡死。
代码片段如下:
--------------------我贴上全部代码,麻烦大家帮忙分析下。------------------
-----------------miniblk.h----------------
#ifndef __MINIBLK__
#define __MINIBLK__

#include <linux/blkdev.h>

#define MINIDEV_NAME            "minibd"
/* 修改成相应的磁盘名,危险操作 */
#define MINIDEV_BDEV            "/dev/sda"

struct minidev {
      struct task_struct *task;
      struct gendisk *disk;
      struct request_queue *rq;
      dev_t major;
      struct block_device *bdev;
};

#endif // __MINIBD_H__

-----------------miniblk.c----------------
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/buffer_head.h>
#include <linux/kthread.h>
#include <linux/bio.h>
#include "miniblk.h"

struct minidev *minidev;
static DECLARE_COMPLETION(bio_finish);

static struct block_device_operations minidev_fops = {
      .owner = THIS_MODULE,
};

struct mybio {
      int err;
      struct bio *bio;
      void *private;
      bio_end_io_t *end_io;
};

struct mybio *init_mybio(struct bio *bio)
{
      struct mybio *mybio = kzalloc(sizeof(struct mybio), GFP_KERNEL);
      if (!mybio)
                return NULL;
      mybio->bio = bio;
      mybio->private = bio->bi_private;
      mybio->end_io = bio->bi_end_io;

      return mybio;
}

void free_mybio(struct mybio *mybio)
{
      kfree(mybio);
}

void mybio_endio(struct bio *bio, int err)
{
      struct mybio *mybio = bio->bi_private;
      //complete(&bio_finish);

      bio->bi_private = mybio->private;
      bio->bi_end_io = mybio->end_io;
      if (bio->bi_end_io)
                bio->bi_end_io(bio, err);
      free_mybio(mybio);
}

static void minidev_make_request(struct request_queue *q, struct bio *bio)
{
      static uint64_t count = 0;
      struct mybio *mybio;

      mybio = init_mybio(bio);
      if (mybio == NULL) {
                bio_endio(bio, -EIO);
                pr_info("wrapper init failed.\n");
      }

      bio->bi_private = mybio;
      bio->bi_end_io = mybio_endio;
      bio->bi_bdev = minidev->bdev;
      submit_bio(bio->bi_rw & WRITE, bio);

      pr_info("submit bio %p.\n", bio);
      //wait_for_completion(&bio_finish);
      pr_info("make request handler count: %llu.bio:%p\n", ++count, bio);
      return;
}

static int init_minidev(struct minidev *dev)
{
      int ret = 0;
      struct request_queue *q;
      struct gendisk *d;
      struct block_device *bdev;
      dev_t major;

      major = register_blkdev(0, MINIDEV_NAME);
      if(major < 0) {
                return major;
      }
      dev->major = major;

      q = blk_alloc_queue(GFP_KERNEL);
      if(!q) {
                ret = -ENOMEM;
                goto err_queue;
      }
      blk_queue_make_request(q, minidev_make_request);
      dev->rq = q;

      d = alloc_disk(1);
      if(!d) {
                ret = -ENOMEM;
                goto err_disk;
      }
      strncpy(d->disk_name, MINIDEV_NAME, strlen(MINIDEV_NAME));
      d->major = major;
      d->first_minor = 0;
      d->fops = &minidev_fops;
      d->queue = q;
      dev->disk = d;

      bdev = blkdev_get_by_path(MINIDEV_BDEV, FMODE_READ | FMODE_WRITE, dev);
      if(IS_ERR(bdev)) {
                printk(KERN_INFO "open bdev fail!");
                ret = -EBUSY;
                goto err_bdev;
      }
      set_capacity(dev->disk, i_size_read(bdev->bd_inode) >> 9);
      dev->bdev = bdev;
      add_disk(minidev->disk);

      return 0;

err_bdev:
      put_disk(minidev->disk);

err_disk:
      blk_cleanup_queue(q);

err_queue:
      unregister_blkdev(major, MINIDEV_NAME);
      return ret;
}

static int __init minidev_init(void)
{
      int ret = 0;
      minidev = kzalloc(sizeof(struct minidev), GFP_KERNEL);
      if(!minidev) {
                ret = -ENOMEM;
                goto fail;
      }

      pr_info("ready init minidev.\n");
      ret = init_minidev(minidev);
      if(ret) {
                goto fail;
      }

      printk(KERN_INFO "miniblk init!\n");
      return 0;

fail:
      kfree(minidev);
      return ret;
}

static void __exit minidev_exit(void)
{
      del_gendisk(minidev->disk);
      put_disk(minidev->disk);
      blkdev_put(minidev->bdev, FMODE_READ | FMODE_WRITE);
      blk_cleanup_queue(minidev->rq);
      unregister_blkdev(minidev->major, MINIDEV_NAME);
      kfree(minidev);

      printk(KERN_INFO "miniblk exit!\n");
}

module_init(minidev_init);
module_exit(minidev_exit);
MODULE_LICENSE("GPL");

Tinnal 发表于 2014-11-27 20:42

回复 1# mfkp3

为什么虚拟块设备会绕过来去访问真实的/dev/sda的呢。建议初学的话以《Linux设备驱动》的例子来学习。
同时,非常推荐你看一下 飞翔的鸵鸟的经典之作 《写一个块设备驱动》
http://bbs.chinaunix.net/thread-2017377-1-1.html
   

镇水铁牛 发表于 2014-11-27 20:43

有没有完整的代码?建议在struct mybio {
      struct bio *bio;
      void *private;
      bio_end_io_t *end_io;
      int err;
};增加struct completion event;成员,不要用static DECLARE_COMPLETION(bio_finish);这种的。
你可以参考内核的实现如下:static void bi_complete(struct bio *bio, int error) {
         complete((struct completion*)bio->bi_private);
}
int sync_page_io(struct block_device *bdev, sector_t sector, int size, struct page *page, int rw) {
         struct bio *bio = bio_alloc(GFP_NOIO, 1);
         struct completion event;int ret;
         rw |= (1 << BIO_RW_SYNC);
         bio->bi_bdev = bdev;
         bio->bi_sector = sector;
         bio_add_page(bio, page, size, 0);
         init_completion(&event);
         bio->bi_private = &event;
         bio->bi_end_io = bi_complete;
         submit_bio(rw, bio);
         wait_for_completion(&event);
         ret = test_bit(BIO_UPTODATE, &bio->bi_flags);
         bio_put(bio);
         return ret;
}EXPORT_SYMBOL_GPL(sync_page_io);

镇水铁牛 发表于 2014-11-27 20:50

如果想实现把一个物理scsi块设备虚拟成一个 virtual target,可以看看md的实现。
你的代码不全,无法确定minidev_make_request函数是否被调用,它的注册方式等?

humjb_1983 发表于 2014-11-28 08:59

感觉好像逻辑有问题?make_request-->submit_bio-->make_request-->submit_bio-->make_request......

mfkp3 发表于 2014-11-28 09:25

回复 2# Tinnal

谢谢你的建议,我只是不明白为什么这里会出现问题,探索本身也是一种学习~


   

mfkp3 发表于 2014-11-28 09:33

回复 4# 镇水铁牛


   谢谢你的回复,请教下为什么放在struct里比定义静态的要好呢? 在这里我只是为了处理方便。
   另外,我有在minidev_make_request中打印log, 我能清楚的看到log打出来了, 并且内核发出警告也指明进程在wait_for_completion中阻塞时间过长,所以才奇怪明明下发下去了,应该是加入到实际bdev的request_queue,   为什么没有endio调用,
   
   我再仔细整理下我的代码,看是否哪个地方有疏漏。

mfkp3 发表于 2014-11-28 09:37

回复 5# humjb_1983

我的minidev_make_request中改变了bio的bdev, 因此下发下去后调用generic_make_request时, request_queue变了, make_request_fn也变了,应该不会循环吧。
   

镇水铁牛 发表于 2014-11-28 20:22

看了你的代码,应该没啥大问题,修改下这里试试看:
d->first_minor = 0;
d->minors = 16;
但是这个gendisk的minors域你要设置下,不设置应该是初始值0了,设置范围是1~16

mfkp3 发表于 2014-11-29 11:11

回复 9# 镇水铁牛

代码如果注释掉那两行completion,就一切正常,进程也不会卡死,可是这是为什么呢?
   
页: [1] 2
查看完整版本: make_request_fn 中能否调用wait_for_completion等bio完成.