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 接口 这里看到如果你想修改一些限制,主要就是
都知道 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, };
然后就会回到 上面的递归流程。