嵌入式Linux中文站

linux内核中的device mapper详解


   关于device mapper在内核中的架构信息在参考文档1,2中有很好的解释,在这里就不过多介绍,以下将详细的根据device mapper的代码解释device mapper机制。
一、 LVM简介
     LVM2是Linux 下的逻辑卷管理器,它可以对磁盘进行分区等。但是我们这里用LVM主要是利用用户空间的device mapper 库以及它提供的 dmsetup 工具。
LVM的下载地址为:http://git.fedorahosted.org/git/lvm2.git。下载后在tools文件夹下会看到dmsetup.c文件,该文件即为dmsetup工具的源码。LVM2的安装方法为:
    1.   执行脚本 ./configure
    2.   make device-mapper
    3.   make install 
这时ls -l /sbin/dmsetup 发现该命令已经是最近编译的。
 
二、DM支持RAID45
    DM默认支持linear、mirror、striped、snapshot、multipath等 target driver,并不支持raid45。但是在2.6内核的几个特定版本中有提供支持raid45的补丁:http://people.redhat.com/heinzm/sw/dm/dm-raid45/
    举例kernel-2.6.30-rc3来说。下载好内核后,需将内核名字更改为linux-2.6.30-rc3。然后将下载的补丁放在跟刚下载的内核同目录下,执行命令patch -bp0 < patch名
 
二、 关键数据结构
1.  mapped_device  2.  dm_table   3.  dm_target  4.  target_type
这四个数据结构是dm中非常重要的数据结构,关于这四个数据结构参考文档1、2中已经解释很详细。
5. dm_io  6. dm_target_io  *tio  7. clone_info  ci   8.bio *clone 这些结构将在下面介绍
 
三、 创建过程详解:
1.  命令:dmsetup create dm0 dm_table
其中dm_table为:0 4096000 raid45 core 2 8192 nosync  raid4 -1   0  3 -1 /dev/sdb 0 /dev/sdc 0 /dev/sdd 0(具体参见博文:解读device mapper raid45 创建参数)
 
2.  用户空间即dmsetup.c函数的执行过程如下:
     1. cmd = _find_command(argv[0])  返回dispatch table中_create命令
     2. cmd->fn(cmd, argc--, argv++, NULL, multiple_devices)  即执行_create函数
     3. _create->dm_task_create->dm_check_version->_check_version->dm_task_create->dm_task_run->_do_dm_ioctl->ioctl(DM_VERSION)
                    ->dm_task_run->_create_and_load_v4->dm_task_create->dm_task_run->_do_dm_ioctl->ioctl(DM_DEV_CREATE)
                                                                                 ->dm_task_create->dm_task_run->_do_dm_ioctl->ioctl(DM_TABLE_LOAD)
                                                                                 ->dmt->type=DM_DEVICE_RESUME->dm_task_run->_do_dm_ioctl->ioctl(DM_DEV_SUSPEND)->case DM_DEVICE_RESUME->add_dev_node
                    
    由上可知,_create主要执行4个ioctl操作,DM_VERSION、DM_DEV_CREATE、DM_TABLE_LOAD和DM_DEV_SUSPEND(DM_DEVICE_RESUME)。四个ioctl分别对应内核四个命令。在下面会分别介绍这四个命令。dm_task_create根据ioctl要执行的命令创建一个dm_task结构体。dm_task_run根据dmt->type的编号执行ioctl命令,具体编号对应_cmd_data_v4[]中相应的命令。
    总之感觉用户空间的代码风格很乱,dm_task_create可能再次调用到dm_task_create和dm_task_run,dm_task_run中又有可能调用dm_task_create和dm_task_run。而且DM_DEVICE_RESUME这个命令不是通过dm_task_create创建,是直接赋值得到的。层次弄的太乱,一点都不清晰。
 
3.  内核空间执行过程:
    用户空间ioctl后,内核空间对应执行dm-ioctl.c中的dm_ctl_ioctl->ctl_ioctl命令。至于为什么这个ioctl对应的是dm-ioctl.c中的dm_ctl_ioctl而不是dm.c中的dm_blk_ioctl,而且dm_blk_ioctl什么时候被调用的我还不是很清楚。ctl_ioctl执行方法:ctl_ioctl->fn = lookup_ioctl()返回相应函数的函数指针->copy_params()获得函数参数->fn(param, input_param_size)执行相应函数。其中用户空间的命令主要有三个,根据lookup_ioctl函数可知主要对应dev_create,table_load,dev_suspend函数。下面分别解读这三个函数:
  1.  dev_create->dm_create->alloc_dev
                        ->dm_hash_insert
    该过程分为两部分:a、通过函数alloc_dev创建相应的mapped device结构,主要是向内核申请必要的内存资源,包括mapped device和为进行IO操作预申请的内存池,通过内核提供的blk_queue_make_request函数注册该mapped device对应的请求队列dm_request。并将该mapped device作为磁盘块设备注册到内核中。b、调用dm_hash_insert将创建好的mapped device插入到device mapper中的一个全局hash表中,该表中保存了内核中当前创建的所有mapped device
 
   2. table_load->dm_table_create
                       ->populate_table->dm_table_add_target->tgt->type->ctr()
                                                   ->dm_table_complete
     该函数根据用户空间传来的参数构建指定mapped device的映射表和所映射的target device。该函数先构建相应的dm_table、dm_target结构,再调用dm-table.c中的dm_table_add_target函数根据用户传入的参数初始化这些结构,并且根据参数所指定的target类型,调用相应的target类型的构建函数ctr在内存中构建target device对应的结构,然后再根据所建立的dm_target结构更新dm_table中维护的B树。上述过程完毕后,再将建立好的dm_table添加到mapped device的全局hash表对应的hash_cell结构中.
    在设备创建时,DM框架会自动创建对应的struct dm_target结构,并力所能及地初始化了一些成员。现在创建器所要做的就是完成对该结构的初始化。那么DM框架初始化了哪些,ctr初始化又初始化哪些:参考文档2中有详细介绍
 
   3. do_resume->md = hc->md ->dm_swap_table
    建立mapped device和映射表之间的绑定关系,事实上该过程就是通过dm_swap_table函数将当前dm_table结构指针值赋予mapped_device相应的map域中,然后再修改mapped_device表示当前状态的域。
 
四、 DM转发bio过程
内核中读写都是以bio的形式进行转发,对于md设备来说,bio处理所走流程为:
0  dm_request(struct request_queue *q, struct bio *bio)  -->
1  _dm_request(q, bio)   -->
2  __split_and_process_bio(md, bio) --> // md 由 q->queue_data 获得,clone_info为与顶层bio相关的信息包括所写md、md的映射表、需要克隆的bio和dm_io,dm_io始终指向顶层bio,提交返回时负责通知bio完成。__split_and_process_bio填充完以上信息继续下发bio
3  __clone_and_map(&ci) -->  // md、bio等信息记录在 ci 结构体中。首先根据ci->sector找到相应的target,bio的处理分为三种情况:1、该bio可以由该target处理 此时单独克隆一个bio,继续下发 2、该bio长度跨越多个target,但是第一个bi_io_vec长度小于该target 3、正在处理的bi_io_vec(也可能是第一个)大于该target
DM构架及其驱动一般不会是真实设备的驱动,因此只会对bio进行处理之后再转发出去。转发的方法就是修改bio->bi_bdev和bio->bi_sector,单独克隆一个bio的原因是我们不能改动上层的bio。
4  __map_bio(ti, clone, tio) -->  // ti 由 ci->md 查表获得,clone由ci->bio克隆获得。
5  ti->type->map(ti, clone, &tio->info) --> //修改bio->bi_bdev和bio->bi_sector, 任何一个bio(块设备的io请求)都要映射到最终存储它的设备上的相应位置,map函数就是完成这一功能.map_context在许多情况下并没有许多作用。如果map函数将bio赋值后又分发出去,那么就返回DM_MAPIO_SUBMITTED告诉DM不要再处理了;如果map函数修改了bio的内容,希望DM将bio按照新内容再分发,那么就返回DM_MAPIO_REMAPPED即可;如果map函数将bio加入队列中等待后续处理,则返回DM_MAPIO_REQUEUE。DM相应的处理代码可以在dm.c中的__map_bio()函数中找到。对于dm-raid45.c的raid_map函数即返回DM_MAPIO_SUBMITTED。表明DM不需要做任何事情,假设target有该io的行使权。
 
    那么我们看一下raid_map做了什么:
 1.  raid_map --> bio->bi_sector -= ti->begin --> bio_list_add(&rs->io.in, bio)  --> wake_do_raid(rs) 
   该函数首先重映射bio的开始扇区,将该bio加入到rs的bio_list队列中,然后唤醒守护进程对该队列进行处理。守护进程对应do_raid函数,是在raid_ctr函数调用INIT_DELAYED_WORK(&rs->io.dws_do_raid, do_raid)注册的。
 
 
 
参考文档:
1. Linux 内核中的 Device Mapper 机制
http://www.ibm.com/developerworks/cn/linux/l-devmapper/index.html#resources
 
2. Device Mapper 代码分析
http://blog.csdn.net/SonicLing/article/details/5460311#[1]
 
3. LVM学习系列之三
http://www.doc88.com/p-675124528501.html

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/kernel-driver/201710/28-7693.html



分享:

评论