關于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
?
評論
查看更多