Linux內核中塊層上的多隊列


如果你想知道SSD為什么使用多隊列,可以看看這篇文章:https://kernel.dk/blk-mq.pdf

1. 多塊層

以下關於多隊列層的總結來自 The Multi-Queue Interface ArticleLinux kernel git 展示了如何轉換為blk-mq。

blk_mq 的API實現了兩級塊層設計,該設計使用兩組獨立的請求隊列。

  1. 軟件暫存隊列,按CPU分配;

  2. 硬件調度隊列,其數量通常與blcok設備支持的實際硬件隊列數量匹配。

如上圖所示,軟件暫存隊列硬件調度隊列之間的映射隊列數不同。

現在,我們考慮兩個隊列放置不同的情況。

假設這里有3種情況:

  • 軟件暫存隊列 > 硬件調度隊列

在這種情況下,兩個或多個軟件暫存隊列被分配到一個硬件上下文中。而在硬件上下文將從所有關聯的軟件隊列中拉入請求的同時,進行一次調度。

  • 軟件暫存隊列 < 硬件調度隊列

在這種情況下,軟件暫存隊列硬件調度隊列之間的映射是有順序的。

  • 軟件暫存隊列 == 硬件調度隊列

在這種情況下,這是最簡單的情況,即執行直接1:1映射。

2. 多隊列塊層中的主要數據結構

2.1 基本結構

2.2 blk_mq_reg(在內核4.5中)

根據 The Multi-Queue Interface Article,blk_mq_reg 結構包含了一個新的塊設備向塊層注冊時需要的所有重要信息。

這個數據結構包括指向 blk_mq_ops 數據結構的指針,用於跟蹤多隊列塊層與設備驅動交互的具體例程。

blk_mq_reg 結構還保存了需要初始化的硬件隊列數量等。

但是,blk_mq_reg 已經不復存在。我們需要通過 blk_mq_ops 來了解塊層和塊設備之間的操作

因此你可以在 kernel 3.15 中找到以下數據結構:

struct blk_mq_reg {
  struct blk_mq_ops       *ops;
  unsigned int            nr_hw_queues;
  unsigned int            queue_depth;
  unsigned int            reserved_tags;
  unsigned int            cmd_size;       /* per-request extra data */
  int                     numa_node;
  unsigned int            timeout;
  unsigned int            flags;          /* BLK_MQ_F_* */
};

但是,在內核4.5中,你已經無法找到。

我認為在內核4.5中,此數據結構已更改為 struct blk_mq_tag_set *set

struct blk_mq_tag_set {
  struct blk_mq_ops       *ops;
  unsigned int            nr_hw_queues;
  unsigned int            queue_depth;    /* max hw supported */
  unsigned int            reserved_tags;
  unsigned int            cmd_size;       /* per-request extra data */
  int                     numa_node;
  unsigned int            timeout;
  unsigned int            flags;          /* BLK_MQ_F_* */
  void                    *driver_data;

  struct blk_mq_tags      **tags;

  struct mutex            tag_list_lock;
  struct list_head        tag_list;
};

因為內核3.15中的函數 function(struct request_queue * blk_mq_init_queue(struct blk_mq_reg * reg,void * driver_data)) ,在內核4.5中已經更改為 function(struct request_queue * blk_mq_init_queue(struct blk_mq_tag_set * set))

2.3 blk_mq_ops 結構(在內核4.5中)

如上文中所述,此數據結構用於多隊列塊層與塊設備層進行通信。

在此數據結構中,執行 blk_mq_hw_ctxblk_mq_ctx 之間上下文映射的函數存儲在 map_queue 字段中。

struct blk_mq_ops {
  /*
   * Queue request
   */
  queue_rq_fn             *queue_rq; // this part

  /*
   * Map to specific hardware queue
   */
  map_queue_fn            *map_queue; // this part

  /*
   * Called on request timeout
   */
  timeout_fn              *timeout;

  /*
   * Called to poll for completion of a specific tag.
   */
  poll_fn                 *poll;

  softirq_done_fn         *complete;

  /*
   * Called when the block layer side of a hardware queue has been
   * set up, allowing the driver to allocate/init matching structures.
   * Ditto for exit/teardown.
   */
  init_hctx_fn            *init_hctx;
  exit_hctx_fn            *exit_hctx;

  /*
   * Called for every command allocated by the block layer to allow
   * the driver to set up driver specific data.
   *
   * Tag greater than or equal to queue_depth is for setting up
   * flush request.
   *
   * Ditto for exit/teardown.
   */
  init_request_fn         *init_request;
  exit_request_fn         *exit_request;
};

2.4 blk_mq_hw_ctx 結構(在內核4.5中)

blk_mq_hw_ctx 結構表示與 request_queue 關聯的硬件上下文。

這個對應的結構是內核4.5中的 blk_mq_ctx 結構。

struct blk_mq_hw_ctx {
        struct {
                spinlock_t              lock;
                struct list_head        dispatch;
        } ____cacheline_aligned_in_smp;

        unsigned long           state;          /* BLK_MQ_S_* flags */
        struct delayed_work     run_work;
        struct delayed_work     delay_work;
        cpumask_var_t           cpumask;
        int                     next_cpu;
        int                     next_cpu_batch;

        unsigned long           flags;          /* BLK_MQ_F_* flags */

        struct request_queue    *queue;
        struct blk_flush_queue  *fq;

        void                    *driver_data;

        struct blk_mq_ctxmap    ctx_map;

        unsigned int            nr_ctx;
        struct blk_mq_ctx       **ctxs;

        atomic_t                wait_index;

        struct blk_mq_tags      *tags;

        unsigned long           queued;
        unsigned long           run;
#define BLK_MQ_MAX_DISPATCH_ORDER       10
        unsigned long           dispatched[BLK_MQ_MAX_DISPATCH_ORDER];

        unsigned int            numa_node;
        unsigned int            queue_num;

        atomic_t                nr_active;

        struct blk_mq_cpu_notifier      cpu_notifier;
        struct kobject          kobj;

        unsigned long           poll_invoked;
        unsigned long           poll_success;
};

2.5 blk_mq_ctx 結構(在內核4.5中)

如上文所述,blk_mq_ctx 作為軟件暫存隊列已分配給每個CPU。

struct blk_mq_ctx {
  struct {
    spinlock_t              lock;
    struct list_head        rq_list;
  }  ____cacheline_aligned_in_smp;

  unsigned int            cpu;
  unsigned int            index_hw;

  unsigned int            last_tag ____cacheline_aligned_in_smp;

  /* incremented at dispatch time */
  unsigned long           rq_dispatched[2];
  unsigned long           rq_merged;

  /* incremented at completion time */
  unsigned long           ____cacheline_aligned_in_smp rq_completed[2];

  struct request_queue    *queue;
  struct kobject          kobj;
} ____cacheline_aligned_in_smp;

2.6 request_queue 結構(在內核4.5中)

blk_mq_hw_ctxblk_mq_ctx 之間的上下文映射是建立在blk_mq_ops 結構的 map_queue 字段上的。在內核4.5中,這個映射仍然是 mq_map 字段,在與塊設備相關的 request_queue 數據結構中。

struct request_queue {
  /*
   * Together with queue_head for cacheline sharing
   */
  struct list_head        queue_head;
  struct request          *last_merge;
  struct elevator_queue   *elevator;
  int                     nr_rqs[2];      /* # allocated [a]sync rqs */
  int                     nr_rqs_elvpriv; /* # allocated rqs w/ elvpriv */

  /*
   * If blkcg is not used, @q->root_rl serves all requests.  If blkcg
   * is used, root blkg allocates from @q->root_rl and all other
   * blkgs from their own blkg->rl.  Which one to use should be
   * determined using bio_request_list().
   */
  struct request_list     root_rl;

  request_fn_proc         *request_fn;
  make_request_fn         *make_request_fn;
  prep_rq_fn              *prep_rq_fn;
  unprep_rq_fn            *unprep_rq_fn;
  softirq_done_fn         *softirq_done_fn;
  rq_timed_out_fn         *rq_timed_out_fn;
  dma_drain_needed_fn     *dma_drain_needed;
  lld_busy_fn             *lld_busy_fn;

  struct blk_mq_ops       *mq_ops;

  unsigned int            *mq_map;

  /* sw queues */
  struct blk_mq_ctx __percpu      *queue_ctx;
  unsigned int            nr_queues;

  /* hw dispatch queues */
  struct blk_mq_hw_ctx    **queue_hw_ctx;
  unsigned int            nr_hw_queues;

  /*
   * Dispatch queue sorting
   */
  sector_t                end_sector;
  struct request          *boundary_rq;

  /*
   * Delayed queue handling
   */
  struct delayed_work     delay_work;

  struct backing_dev_info backing_dev_info;

  /*
   * The queue owner gets to use this for whatever they like.
   * ll_rw_blk doesn't touch it.
   */
  void                    *queuedata;

  /*
   * various queue flags, see QUEUE_* below
   */
  unsigned long           queue_flags;

  /*
   * ida allocated id for this queue.  Used to index queues from
   * ioctx.
   */
  int                     id;

  /*
   * queue needs bounce pages for pages above this limit
   */
  gfp_t                   bounce_gfp;

  /*
   * protects queue structures from reentrancy. ->__queue_lock should
   * _never_ be used directly, it is queue private. always use
   * ->queue_lock.
   */
  spinlock_t              __queue_lock;
  spinlock_t              *queue_lock;

  /*
   * queue kobject
   */
  struct kobject kobj;

  /*
   * mq queue kobject
   */
  struct kobject mq_kobj;

  #ifdef  CONFIG_BLK_DEV_INTEGRITY
  struct blk_integrity integrity;
  #endif  /* CONFIG_BLK_DEV_INTEGRITY */

  #ifdef CONFIG_PM
  struct device           *dev;
  int                     rpm_status;
  unsigned int            nr_pending;
  #endif

  /*
   * queue settings
   */
  unsigned long           nr_requests;    /* Max # of requests */
  unsigned int            nr_congestion_on;
  unsigned int            nr_congestion_off;
  unsigned int            nr_batching;

  unsigned int            dma_drain_size;
  void                    *dma_drain_buffer;
  unsigned int            dma_pad_mask;
  unsigned int            dma_alignment;

  struct blk_queue_tag    *queue_tags;
  struct list_head        tag_busy_list;

  unsigned int            nr_sorted;
  unsigned int            in_flight[2];
  /*
   * Number of active block driver functions for which blk_drain_queue()
   * must wait. Must be incremented around functions that unlock the
   * queue_lock internally, e.g. scsi_request_fn().
   */
  unsigned int            request_fn_active;

  unsigned int            rq_timeout;
  struct timer_list       timeout;
  struct work_struct      timeout_work;
  struct list_head        timeout_list;

  struct list_head        icq_list;
  #ifdef CONFIG_BLK_CGROUP
  DECLARE_BITMAP          (blkcg_pols, BLKCG_MAX_POLS);
  struct blkcg_gq         *root_blkg;
  struct list_head        blkg_list;
  #endif

  struct queue_limits     limits;

  /*
   * sg stuff
   */
  unsigned int            sg_timeout;
  unsigned int            sg_reserved_size;
  int                     node;
  #ifdef CONFIG_BLK_DEV_IO_TRACE
  struct blk_trace        *blk_trace;
  #endif
  /*
   * for flush operations
   */
  unsigned int            flush_flags;
  unsigned int            flush_not_queueable:1;
  struct blk_flush_queue  *fq;

  struct list_head        requeue_list;
  spinlock_t              requeue_lock;
  struct work_struct      requeue_work;

  struct mutex            sysfs_lock;

  int                     bypass_depth;
  atomic_t                mq_freeze_depth;

  #if defined(CONFIG_BLK_DEV_BSG)
  bsg_job_fn              *bsg_job_fn;
  int                     bsg_job_size;
  struct bsg_class_device bsg_dev;
  #endif

  #ifdef CONFIG_BLK_DEV_THROTTLING
  /* Throttle data */
  struct throtl_data *td;
  #endif
  struct rcu_head         rcu_head;
  wait_queue_head_t       mq_freeze_wq;
  struct percpu_ref       q_usage_counter;
  struct list_head        all_q_node;

  struct blk_mq_tag_set   *tag_set;
  struct list_head        tag_set_list;
  struct bio_set          *bio_split;

  bool                    mq_sysfs_init_done;
};

3. 隊列初始化

當一個新的使用多隊列 API 的設備驅動被加載時,它會創建並初始化一個新的 blk_mq_ops 結構,並將一個新的 blk_mq_reg 的相關指針設置為它的地址。

更詳細的說,除了下面的結構,其他的操作現在都是嚴格要求的。

但是,為了在上下文分配或 I/O 請求完成時執行特定的操作,可以指定其他操作。

作為必要的數據,驅動程序必須初始化它所支持的提交隊列的數量,以及它們的大小。

其他數據也是必須的,以確定驅動所支持的命令大小,以及必須暴露給塊層的特定標志。

但是,在內核4.5版本中,struct blk_mq_tag_set 很重要,上面的工作就是在這個 struct blk_mq_tag_set 中實現的。

3.1 queue_fn

必須將其設置為負責處理命令的功能,例如,通過將命令傳遞給低級驅動程序。

3.2 map_queue

執行硬件和軟件上下文之間的映射。

3.3 blk_mq_init_queue 函數(在內核4.5中)

在為設備相關的 gendisk 和 request_queue 做好准備后,驅動調用 blk_mq_init_queue 函數。(在內核4.5中)

這個函數初始化硬件和軟件上下文,並執行它們之間的映射。

這個初始化例程還設置了一個備用的 make_request 函數,代替了傳統的請求提交路徑,其中包括函數 blk_make_request() (在內核4.5中)的多隊列提交路徑(其中包括 blk_mq_make_request() 函數)。

換句話說,備用的 make_request 函數是用 blk_queue_make_reqeust() 設置的。

4. 提交請求

設備初始化用 blk_mq_make_request() 代替了傳統的塊 I/O 提交函數(在內核4.5中),讓多隊列結構從上層的角度來使用。

多隊列塊層使用的 make_request (在內核4.5中不存在) 函數包含了從進程阻塞中獲益的可能性,但只適用於支持單個硬件隊列或異步請求的驅動。

如果請求是同步的,並且驅動主動使用多隊列接口,則不會阻塞。

如果允許阻塞,make_request 函數也會執行請求合並,先在任務的阻塞列表里面搜索一個候選者。

最后在映射到當前 CPU 的軟件隊列中,提交路徑不涉及任何 I/O 調度相關的回調。

最后,make_request 會立即將任何同步請求發送到相關硬件隊列中,而在 async 或 flush 請求的情況下,它會延遲這個過渡,以便后續的合並和更高效的調度。

5. 請求調度

如果一個 I/O 請求是同步的(因此不允許在多隊列塊層中阻塞),它對設備驅動程序的調度是在同一請求的上下文中進行的。

如果請求是 async 或 flush,則存在任務阻塞。調度的順序如下:

  1. 在提交另一個I/O請求給與同一硬件隊列相關聯的軟件隊列時。
  2. 在reqeust提交過程中安排的延遲工作被執行時。

多隊列塊層主要的 排隊等候 函數是 blk_mq_run_hw_queue() (在內核4.5代碼中),它基本依賴於另一個由其 blk_mq_ops 結構的 queue_rq (在內核4.5中)字段指向的驅動專用例程。

重要:我們必須檢查 blk_mq_run_hw_queuerequest_qu 之間的關系!

重要:這個函數可以延遲隊列的任何運行,同時它可以立即向驅動發送一個同步請求。

內部函數 __blk_mq_run_hw_queue() (在內核4.5中),在 reqeust 是同步的情況下被 blk_mq_run_hw_queue() (在內核4.5代碼中)調用,首先加入與當前服務的硬件隊列相關聯的任何軟件隊列,然后它將結果列表與已經在調度列表中的任何條目加入。

在收集了所有待服務的條目之后,函數 __blk_mq_run_hw_queue() (在內核4.5中)處理它們(這些條目),啟動每個 reqeust,並通過它的 queue_rq 函數將其傳遞給驅動。

該函數最后通過重排或刪除相關請求處理可能出現的錯誤。

原文:https://hyunyoung2.github.io/2016/09/14/Multi_Queue/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM