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");
回复 1# mfkp3
为什么虚拟块设备会绕过来去访问真实的/dev/sda的呢。建议初学的话以《Linux设备驱动》的例子来学习。
同时,非常推荐你看一下 飞翔的鸵鸟的经典之作 《写一个块设备驱动》
http://bbs.chinaunix.net/thread-2017377-1-1.html
有没有完整的代码?建议在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); 如果想实现把一个物理scsi块设备虚拟成一个 virtual target,可以看看md的实现。
你的代码不全,无法确定minidev_make_request函数是否被调用,它的注册方式等? 感觉好像逻辑有问题?make_request-->submit_bio-->make_request-->submit_bio-->make_request...... 回复 2# Tinnal
谢谢你的建议,我只是不明白为什么这里会出现问题,探索本身也是一种学习~
回复 4# 镇水铁牛
谢谢你的回复,请教下为什么放在struct里比定义静态的要好呢? 在这里我只是为了处理方便。
另外,我有在minidev_make_request中打印log, 我能清楚的看到log打出来了, 并且内核发出警告也指明进程在wait_for_completion中阻塞时间过长,所以才奇怪明明下发下去了,应该是加入到实际bdev的request_queue, 为什么没有endio调用,
我再仔细整理下我的代码,看是否哪个地方有疏漏。 回复 5# humjb_1983
我的minidev_make_request中改变了bio的bdev, 因此下发下去后调用generic_make_request时, request_queue变了, make_request_fn也变了,应该不会循环吧。
看了你的代码,应该没啥大问题,修改下这里试试看:
d->first_minor = 0;
d->minors = 16;
但是这个gendisk的minors域你要设置下,不设置应该是初始值0了,设置范围是1~16 回复 9# 镇水铁牛
代码如果注释掉那两行completion,就一切正常,进程也不会卡死,可是这是为什么呢?
页:
[1]
2