Device Mapper 和 Multiple Devices

阅读更多

 

 

 DM 和 MD 。。。 一个用于逻辑卷 一个用于软RAID 。都是虚拟的。。。

 

 

 开始我也很好奇,如果同时启用2个设备,bio 是如何分发的。 现在有了点眉目。

 

先说一下iscsi 的理解。 简单的看了一下iscsi mod。我的理解就是

 

网络过来的数据包组织成了 struct tio

 

然后经过  block_io.c 的

 

static int
blockio_make_request(struct iet_volume *volume, struct tio *tio, int rw)
 

 

处理生成bio 后 直接 submit_bio 到generic layer。

 

这里其实 iscsi mod 替代了VFS层注册了自己的方法直接去处理用户态数据

<这里可能丢失了page buffer 层,这里按照存储器山的设计是不是不合理 后面再研究>。(当然他也支持通过VFS 接口下去)

 

好了下面就来看看到了 G层 是如何处理的 : 

 

在 sched 的伟大的 task_struct 结构里面有一个这个

 

 

struct task_struct {
//...
  struct bio_list *bio_list;
//...
}
 

 

bio 结构里面有一个

bi_next :用于连接下一个bio ,把他们放到设备 request queue 中

这里把他们用 bio_list管理起来  。 首尾都快速访问。

 

 

在正常的情况下 (实际 设备)bio_alloc 被产生之后 ,就会去通过 

 

generic_make_request 进入 generic block 层 。通过一些检查 ,修改分区偏移 放入队列后 会去通过

request_queue  内的 make_request_fn(q, bio) 调用__make_request 。这个大家都知道,就不那代码解释了

 

 

现在就是  在 Multiple Devices driver 里面我们可以看到: 

 

 

 

static int md_alloc(dev_t dev, char *name)
{
	static DEFINE_MUTEX(disks_mutex);
	mddev_t *mddev = mddev_find(dev);
	struct gendisk *disk;
	int partitioned;
	int shift;
	int unit;
	int error;
//...

	blk_queue_make_request(mddev->queue, md_make_request);/*注册函数*/
//...

}

 

所以 RAID 的bio 请求会到 md_make_request

 

 

而在  Device Mapper driver 里面,我们同样可以在初始化的地方看到

 

 

 

static struct mapped_device *alloc_dev(int minor)
{
	int r;
	struct mapped_device *md = kzalloc(sizeof(*md), GFP_KERNEL);
	void *old_md;
//...

	dm_init_md_queue(md);
//...

}

紧接着:

 

 

static void dm_init_md_queue(struct mapped_device *md)
{
	queue_flag_clear_unlocked(QUEUE_FLAG_STACKABLE, md->queue);

	md->queue->queuedata = md;
	md->queue->backing_dev_info.congested_fn = dm_any_congested;
	md->queue->backing_dev_info.congested_data = md;
	blk_queue_make_request(md->queue, dm_request);
	blk_queue_bounce_limit(md->queue, BLK_BOUNCE_ANY);
//...
}
 

所以LVM 的bio 请求会到 dm_request

 

 

对于一个 bio 普通的内核处理路线 就直接把它放入整合进一个request 然后传给对应设备的request queue,设备在软中断或者调度的时候处理这个队列。

 

但是对于我们上面说的虚拟设备 最好直接通过一个请求调用传递给虚拟设备 这样可以让他们立刻服务。

而让bio 知道自己要被谁服务的方法就是我们上面2个地方都看到的  

 

 blk_queue_make_request(struct request_queue *q, make_request_fn *mfn) 

 

函数。

 

 

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
	/*请求队列的最多安置请求数 (128)*/
	q->nr_requests = BLKDEV_MAX_RQ;
       /*这里就是bio 处理函数啦,generic_make_request调用的*/
	q->make_request_fn = mfn;
	blk_queue_dma_alignment(q, 511);/*和普通的设备一样对于direct的IO 也通过DMA直接处理,这里设置了对齐掩码*/
       /*设置了 请求拥塞开关上下限 113-111*/
	blk_queue_congestion_threshold(q);
        /*队列已满 仍可以作为一次提交的请求数*/
	q->nr_batching = BLK_BATCH_REQ;
       /*都是经典的默认值  利用插拔来提高合并率(我叫他逼尿法)*/
	q->unplug_thresh = 4;		/* hmm */
	q->unplug_delay = msecs_to_jiffies(3);	/* 3 milliseconds */
	if (q->unplug_delay == 0)
		q->unplug_delay = 1;
       /*【kblockd】 线程处理*/
	q->unplug_timer.function = blk_unplug_timeout;
	q->unplug_timer.data = (unsigned long)q;
         /*设置虚拟设备队列的相关限制*/
	blk_set_default_limits(&q->limits);/*请求队列里面能处理的最多量*/
	blk_queue_max_hw_sectors(q, BLK_SAFE_MAX_SECTORS);

	/*
	 * If the caller didn't supply a lock, fall back to our embedded
	 * per-queue locks
	 */
	if (!q->queue_lock)
		q->queue_lock = &q->__queue_lock;

	/*对于处在ZONE_HIGH的内存需要分配的mpool也设置限制 */
	blk_queue_bounce_limit(q, BLK_BOUNCE_HIGH);
}

 

 

因为没有提供proc 接口 这里看到如果你想修改一些限制,主要就是

 

 写道
enum blk_default_limits{}

blk_set_default_limits()
 

 

 

都知道 dm 有对应的设备树 ,层层转发

 

 

我们知道

 

 

dm_request(struct request_queue *q, struct bio *bio) -> _split_and_process_bio(md, bio)-> __clone_and_map(&ci); 

->__map_bio(ti, clone, tio);  

 

 

交给对于策略的 dm_type 处理map(ti, clone, &tio->info);

 

后就会根据 DM_MAPIO_REMAPPED 标志 ,继续 generic_make_request(clone);

 

代码如下: 

 

 

 

static void __map_bio(struct dm_target *ti, struct bio *clone,
		      struct dm_target_io *tio)
{
	int r;
	sector_t sector;
	struct mapped_device *md;

	clone->bi_end_io = clone_endio;
	clone->bi_private = tio;


	atomic_inc(&tio->io->io_count);
	sector = clone->bi_sector;
       /*如果是liner策略就是:linear_map*/
	r = ti->type->map(ti, clone, &tio->info);
     
	if (r == DM_MAPIO_REMAPPED) {
	 /* the bio has been remapped so dispatch it 这里又去调用
         * */
       
		generic_make_request(clone);
	} else if (r < 0 || r == DM_MAPIO_REQUEUE) {
		/* error the io and bail out, or requeue it if needed */
		md = tio->io->md;
		dec_pending(tio->io, r);
		clone->bi_private = md->bs;
		bio_put(clone);
		free_tio(md, tio);
	} else if (r) {
		//...
	}
}
 

 

 

 

回来看一下 

 

我们希望一次执行只调用 一个 q->make_request_fn  ,

但是对于基于栈的设备 就用 current->bio_list来维护 反复make_request_fn 提交的请求

 

同样 current->bio_list 也作为一个标志来表明是否 generic_make_request  当前激活。

 

如果 bio_list == null 说明没有激活 generic_make_request , 所以新的请求需要加入到bio_list队尾

 

 

下面的函数是一个明显的递归 ,一起来看看

 

 

void generic_make_request(struct bio *bio)/*szx:__make_request*/
{
	struct bio_list bio_list_on_stack;
       /*第一次先不会进入:如果进入了这个时候gen_m_r 已经激活了, 
         *不断的把bio_list 里面放入要处理的bio 直到不需要remap 这次递归结束不会进gen_m_r了 */
	if (current->bio_list) {
		/* make_request is active */
		bio_list_add(current->bio_list, bio);
		return;
	}
        /*调用者要保证 bio->bi_next 为空*/
	BUG_ON(bio->bi_next);
	bio_list_init(&bio_list_on_stack);
	current->bio_list = &bio_list_on_stack;static int raid_map(struct dm_target *ti, struct bio *bio, union map_info *map_context)
{
	struct raid_set *rs = ti->private;
	mddev_t *mddev = &rs->md;

	mddev->pers->make_request(mddev, bio);

	return DM_MAPIO_SUBMITTED;
}
  do { /*第一次会从这里进入开始dm_requst这样的fn*/ __generic_make_request(bio); /*如果是从这个调用返回了,说明current->bio_list 里面有料了, *就开始用真正需要的下层block device 处理*/ bio = bio_list_pop(current->bio_list); } while (bio); current->bio_list = NULL; /* deactivate 去激活gn_m_r*/ }

  我不知道到这里 你明白没有。。

 

 

 

对于raid 策略的 target_device 。dm_request --->....->

 

 到了dm_raid.c对应的 target_driver 之后就会调用事先注册的_map_io 

 

 

 

 

 

 

static int raid_map(struct dm_target *ti, struct bio *bio, union map_info *map_context)
{
	struct raid_set *rs = ti->private;
	mddev_t *mddev = &rs->md;
/*这里就会去调用对应的  raid 级别(策略)*/
	mddev->pers->make_request(mddev, bio);

	return DM_MAPIO_SUBMITTED;
}
 

 

然后就会调用自己管理的所有 raid_type 的make_request 方法。

 

static struct mdk_personality raid1_personality =
{
	.name		= "raid1", 
	.level		= 1,
	.owner		= THIS_MODULE,
	.make_request	= make_request,
	.run		= run,
	.stop		= stop,
	.status		= status,
	.error_handler	= error,
	.hot_add_disk	= raid1_add_disk,
	.hot_remove_disk= raid1_remove_disk,
	.spare_active	= raid1_spare_active,
	.sync_request	= sync_request,
	.resize		= raid1_resize,
	.size		= raid1_size,
	.check_reshape	= raid1_reshape,
	.quiesce	= raid1_quiesce,
	.takeover	= raid1_takeover,
};

 

 

 

然后就会回到 上面的递归流程。

 

你可能感兴趣的:(Linux)