Linux kernel DM map_info接口改为per-bio data

最近在将一个之前版本的 Linux DM(device mapper)驱动移植到新的 3.18 版本 Linux kernel 上出现编译报错,提示 DM target_type 中的 dm_map_fn 等成员函数指针类型不正确。

看看老版本上 dm_map_fn 函数指针的声明:

typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio, union map_info *map_context);

这个是新的 3.18 版本 Linux 上的形式:

typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio);

其它几个报错的函数指针也比较类似,看起来明显就是新 kernel 中去掉了老的 map_info 类型参数。

有些类型 device mapper target(例如:multipath、snapshot 等驱动)需要在 dm_map_fn 处理函数中分配与 bio 相关的辅助数据并在 dm_endio_fn 处理函数中使用,老版本 Linux 中的标准处理流程就是在 dm_map_fn 中按需分配数据,并赋值给 map_info 类型的 map_context 的 ptr 指针,在 dm_endio_fn 中则需要判断 map_context 的 ptr 指针是否非空,并做对应的使用和释放操作。

先上个老版本 kernel 里的代码,以 dm-mirror 驱动为例;

static int mirror_map(struct dm_target *ti, struct bio *bio, union map_info *map_context)
{
	struct mirror_set *ms = (struct mirror_set *) ti->private;
	struct dm_raid1_read_record *read_record = NULL;

	...
	read_record = mempool_alloc(ms->read_record_pool, GFP_NOIO);
	if (likely(read_record)) {
		dm_bio_record(&read_record->details, bio);
		map_context->ptr = read_record;
		read_record->m = m;
	}
	...
}

static int mirror_end_io(struct dm_target *ti, struct bio *bio, int error, union map_info *map_context)
{
	struct mirror_set *ms = (struct mirror_set *) ti->private;
	struct dm_raid1_read_record *read_record = map_context->ptr;

	...
	if (read_record) {
		mempool_free(read_record, ms->read_record_pool);
		map_context->ptr = NULL;
	}
	...
}

新 kernel 里则去掉了在 target 的处理函数里自己分配并保存自定义数据的方式,由于 kernel 中各种不同的子系统都可以可以请求 bio 子系统创建一个包含额外空余空间的 bio slab cache(per-bio data)并放在 bio 之前,这个空余空间就可以被其它驱动由于各种用途。

DM 中则使用 per-bio data 这一特性存放 target 驱动相关和 device mapper 相关的数据。DM target 驱动可以通过在 dm_ctr_fn 构造处理函数里设置 dm_target 结构体的自定义数据大小(per-bio data size)的形式直接将数据保存到 bio 请求中,如果大小为 0 表示不需要额外的数据,如果大于 0 则 DM 会自动给每个 bio 请求加上指定大小的自定义数据,这样 target 驱动也就不需要额外的分配释放操作了。

相对应的 dm_target 的 dm_map_fn 和 dm_endio_fn 处理函数中直接使用 dm_per_bio_data 函数就可以从 bio 里取出自定义数据,不需要自行管理数据也非常方便。

这样就可以来看看新版本 kernel 里的 dm-mirror 代码:

static int mirror_map(struct dm_target *ti, struct bio *bio)
{
	struct mirror_set *ms = ti->private;
	struct dm_raid1_bio_record *bio_record =
		dm_per_bio_data(bio, sizeof(struct dm_raid1_bio_record));

	bio_record->details.bi_bdev = NULL;
	...
	dm_bio_record(&bio_record->details, bio);
	bio_record->m = m;
	...
}

static int mirror_end_io(struct dm_target *ti, struct bio *bio, int error)
{
	struct mirror_set *ms = (struct mirror_set *) ti->private;
	struct dm_raid1_bio_record *bio_record =
		dm_per_bio_data(bio, sizeof(struct dm_raid1_bio_record));

	...
	bio_record->details.bi_bdev = NULL;
	...
}

static int mirror_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
	...
	ti->per_bio_data_size = sizeof(struct dm_raid1_bio_record);
	...
}

实际使用中还是要注意 per_bio_data_size 最好不要太大。将 device mapper 驱动按 per-bio data 的形式进行修改,就可完成新 kernel 上的移植。





*