天天看點

塊裝置核心參數max_segments和max_sectors_kb解析

linux塊裝置在處理io時會受到一些參數(裝置的queue limits參數,以下簡稱limits參數)的影響,比如一個請求中允許的最大扇區數,最大segment數等。這些參數可以在/sys/block//queue/下檢視,塊裝置在初始化時會設定預設值。這裡主要分析max_segments和max_sectors_kb。

1. 基本概念

1.1 段的概念

首先就需要了解一下什麼是段(segment)。

一個段就是一個記憶體頁或者記憶體頁的一部分,它們包含磁盤上實體相鄰的資料塊。

磁盤的每個io操作就是磁盤與一些RAM單元之間互相傳送一些相鄰扇區的内容。大多數情況下,磁盤控制器采用DMA方式傳送資料,塊裝置驅動程式隻要向磁盤控制器發送适當的指令就可以觸發一次資料傳送,一旦完成資料傳送,磁盤控制器就會發出一個中斷通知塊裝置驅動程式。

DMA方式傳送的是磁盤上相鄰的扇區資料,雖然也允許傳送不相鄰的扇區資料,但是這個比較低效,因為磁頭移動慢。

老的磁盤控制器僅僅支援“簡單”的DMA方式:磁盤必須與RAM中連續的記憶體單元傳送資料。但是新的磁盤控制器支援分散聚集(scatter-gather)DMA方式,磁盤可以與一些非連續的記憶體區域互相傳送資料。

為了支援分散聚集DMA方式,塊裝置驅動程式必須能夠處理稱為段的資料存儲單元,一次分散聚集DMA可以傳送幾個段。

如果不同的段在RAM中相應的頁框正好是連續的并且在磁盤上相應的資料也是相鄰的,那麼通用塊層就可以進行合并,這種合并方式産生的更大的記憶體區域就稱為實體段。

1.2 各參數含義

max_segments表示裝置能夠允許的最大段的數目。

max_sectors_kb表示裝置允許的最大請求大小。

max_hw_sectors_kb表示單個請求所能處理的最大KB(硬限制) 

以一塊sata磁盤為例:

cat /sys/block/sda/queue/max_sectors_kb

512

一個bio中bio_vec 最大為128個,每個ve_len為4096。128 * 4KB = 512KB,與max_sectors_kb吻合。

2. limits參數的設定及影響

2.1 limits預設值的設定

一般的塊裝置在初始化時會調用blk_queue_make_request設定make_request回調函數,與此同時,還會調用blk_set_default_limits設定裝置queue的限制的預設值,預設的值如下。

enum blk_default_limits {
 BLK_MAX_SEGMENTS	= 128,
 BLK_SAFE_MAX_SECTORS	= 255,
 BLK_DEF_MAX_SECTORS	= 1024,
 BLK_MAX_SEGMENT_SIZE	= 65536,
 BLK_SEG_BOUNDARY_MASK	= 0xFFFFFFFFUL,
};
           

2.2 DIRECT IO中max_segments和max_sectors_kb

使用direct io時,跳過檔案系統的cache,io直接下發到塊裝置。以write為例,io path如下:

sys_write() –> vfs_write() –> do_sync_write() –> blkdev_aio_write() –> __generic_file_aio_write() –> generic_file_direct_write() –> blkdev_direct_IO() –> __blockdev_direct_IO() –> submit_page_section() –> dio_send_cur_page() –> dio_bio_submit() –> submit_bio() –> generic_make_request()

在dio_send_cur_page 中,會嘗試将連續的請求合并成一個bio,然後傳回,如果合并失敗或者目前請求與已有的bio不連續,就直接dio_bio_submit送出請求。

其中dio_bio_add_page() –> bio_add_page() –> __bio_add_page() 是将目前請求合并到bio中。

1)max_segments

在 __bio_add_page有如下一段代碼,會判斷bio中的實體段數目是否超過裝置的max_segments,如果超過就傳回0表示add page失敗,進而會調用dio_bio_submit 送出請求。

while (bio->bi_phys_segments >= queue_max_segments(q)) {
  if (retried_segments)
   return 0;
  retried_segments = 1;
  blk_recount_segments(q, bio);
 }
      

2)max_sectors_kb

同樣是在__bio_add_page中,

if (((bio->bi_size + len) >> 9) > max_sectors)
  return 0;
      

其中max_sectors就是裝置的max_sectors_kb*2,即是當該bio的大小超過max_sectors_kb時,就傳回0表示add page失敗,這樣就會調用dio_bio_submit送出請求。

2.3 Device Mapper裝置的limits參數

device mapper裝置(簡稱dm裝置)在剛開始建立初始化時會先設定成預設值,然後再會根據其底層的裝置和預設值,兩者取最小值來設定dm裝置的限制值。其流程如下:

dm_create() –> alloc_dev() –> dm_init_md_queue() –> blk_queue_make_request()設定預設值

然後在dm_calculate_queue_limits中先設定成預設,然後會最終調用blk_stack_limits,取底層裝置和dm裝置的這些參數的最小值來設定dm的參數。

do_resume() –> dm_swap_table() –> dm_calculate_queue_limits() –> ti->type->iterate_devices() –>dm_set_device_limits() –> bdev_stack_limits() –> blk_stack_limits()

不過在不同的核心中還有些不同,上面描述的是3.2.54核心的流程,在新核心(3.10.11)中,設定成預設的流程也一樣,不過在dm_calculate_queue_limits中調用了blk_set_stacking_limits(),這個函數會把limits參數設定成極大值,這樣在後續調用blk_stack_limits的時候,也是取兩者最小值,不過這個時候因為dm的limits參數是極大值,是以會使用底層裝置的limits參數的值來設定dm的limits參數。

這樣做的好處在于可以繼承底層裝置的限制,底層裝置都能處理這些limits參數,在它們之上的dm裝置同樣也可以。

這裡附上blk_set_stacking_limits的代碼

void blk_set_stacking_limits(struct queue_limits *lim)
{
 blk_set_default_limits(lim);
 /* Inherit limits from component devices */
 lim->discard_zeroes_data = 1;
 lim->max_segments = USHRT_MAX;
 lim->max_hw_sectors = UINT_MAX;
 lim->max_sectors = UINT_MAX;
 lim->max_write_same_sectors = UINT_MAX;
}
      

2.4 軟raid裝置中的limits參數

軟raid的建立過程參考http://blog.csdn.net/liumangxiong/article/details/12405231

簡單來說,就是配置設定裝置,初始化資料結構,注冊請求處理函數,根據不同的raid級别再進行一些必要的初始化。

調用關系如下:

md_init() –> md_probe() –> md_alloc() –> blk_queue_make_request()

之前已經說到在blk_queue_make_request中會注冊make_request_fn,并且會設定limits參數的預設值。

do_md_run –> md_run() –> mddev->pers->run(mddev) 對應raid1中的run() –> 會周遊raid中的裝置,分别調用disk_stack_limits() –> bdev_stack_limits() –> blk_stack_limits()

這裡也是會根據md裝置和其底層的成員裝置的queue->limits參數,兩者取最小值。

與device mapper裝置類似,在不同核心下limits參數設定還有些不同。

上面描述的是3.2.54核心下的流程。

在3.10.11核心下,md_alloc中在調用blk_queue_make_request之後還會調用blk_set_stacking_limits将md裝置的limits參數設定為極大值。

這樣就能在blk_stack_limits中取最小值時繼承底層裝置的limits參數。

在使用fio在這兩個核心下對軟raid1測試的時候,就會發現差別,當fio使用大于127KB的塊大小時,在3.2.54核心使用iostat就會看到實際落到md裝置的io大小小于fio下發的大小,在3.10.11就沒有這個問題。

另外,需要提到的是在3.10.11核心中也沒有《systemtap分析軟raid io拆分問題》中max_segments設定為1的問題,因為新核心的raid1中的run()函數中已經取消掉這段代碼

if (rdev->bdev->bd_disk->queue->merge_bvec_fn) {
   blk_queue_max_segments(mddev->queue, 1);
   blk_queue_segment_boundary(mddev->queue,
         PAGE_CACHE_SIZE - 1);
  }
      

這樣就不會将max_segments設定成1了。

3. 參考資料

1)《深入了解linux核心》

2)《linux裝置驅動程式》

3)軟raid代碼分析http://blog.csdn.net/liumangxiong/article/details/12405231

4)https://www.kernel.org/doc/Documentation/block/queue-sysfs.txt

5)3.2.54及3.10.11核心源代碼

轉自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22954220&id=4813537