관리 대기열 및 Blk-mq 초기화

이전 글에서는 nvme_configure_admin_queue() 함수의 내용을 분석해 보았고, 이번에는 nvme_reset_work() 함수의 다른 기능들을 소개하도록 하겠습니다.

nvme_reset_work() 함수의 주요 작업은 다음과 같이 요약할 수 있습니다.

ⓐ nvme_reset_work() 함수를 입력한 후, NVME_CTRL_RESETTING 플래그를 먼저 확인하여 nvme_reset_work() 함수가 반복적으로 호출되지 않도록 합니다.

ⓑ nvme_pci_enable() 함수 호출

ⓒ nvme_configure_admin_queue() 함수 호출

ⓓ nvme_init_queue() 함수 호출

ⓔ nvme_alloc_admin_tags() 함수 호출

ⓕ nvme_init_identify() 함수 호출

ⓖ nvme_setup_io_queues() 함수 호출

ⓗ nvme_start_queues() / nvme_dev_add() 함수 호출 후 nvme_queue_scan() 함수 호출

ⓓ nvme_init_queue() 함수

static void nvme_init_queue(struct nvme_queue *nvmeq, u16 qid)
{
	struct nvme_dev *dev = nvmeq->dev;
	spin_lock_irq(&nvmeq->q_lock);
	nvmeq->sq_tail = 0;
	nvmeq->cq_head = 0;
	nvmeq->cq_phase = 1;
	nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride];
	memset((void *)nvmeq->cqes, 0, CQ_SIZE(nvmeq->q_depth));
	dev->online_queues++;
	spin_unlock_irq(&nvmeq->q_lock);
}

nvme_init_queue가 하는 일은 상대적으로 간단합니다. 이전 nvme_configure_admin_queue 함수에서 적용한 대기열을 초기화하는 것입니다. 이 과정에서 초기화하여 SQ Tail, CQ Head, CQ 위상 변수에 값을 할당한 후 q_db를 통해 Doorbell 레지스터를 가리킨다.

SQ, CQ, Phase, Doorbell에 대한 자세한 설명은 다음을 참조하십시오.

NVMe 시리즈 주제 2: 대기열(Queue) 관리

ⓔ nvme_alloc_admin_tags() 함수

static int nvme_alloc_admin_tags(struct nvme_dev *dev)
{
	if (!dev->ctrl.admin_q) {
		dev->admin_tagset.ops = &nvme_mq_admin_ops;
		dev->admin_tagset.nr_hw_queues = 1;
		dev->admin_tagset.queue_depth = NVME_AQ_BLKMQ_DEPTH - 1;
		dev->admin_tagset.timeout = ADMIN_TIMEOUT;
		dev->admin_tagset.numa_node = dev_to_node(dev->dev);
		dev->admin_tagset.cmd_size = nvme_cmd_size(dev);
		dev->admin_tagset.driver_data = dev;

		if (blk_mq_alloc_tag_set(&dev->admin_tagset))
			return -ENOMEM;
		dev->ctrl.admin_q = blk_mq_init_queue(&dev->admin_tagset);
		if (IS_ERR(dev->ctrl.admin_q)) {
			blk_mq_free_tag_set(&dev->admin_tagset);
			return -ENOMEM;
		}
		if (!blk_get_queue(dev->ctrl.admin_q)) {
			nvme_dev_remove_admin(dev);
			dev->ctrl.admin_q = NULL;
			return -ENODEV;
		}
	} else
		blk_mq_start_stopped_hw_queues(dev->ctrl.admin_q, true);

    return 0;
}

이 기능은 MQ(Multi-Queue)를 사용하는 NVMe 장치의 핵심 기능이므로 이 기능을 분석하기 전에 Linux Multi-Queue Block Layer에 대해 알아보겠습니다.

이전 NVMe 기사(NVMe 시리즈 주제 2: 대기열 관리)에서 소개한 것처럼 다중 대기열, 기본 비동기 및 잠금 해제는 NVMe의 가장 큰 기능입니다. 단일 큐 블록 레이어가 멀티 큐 블록 레이어로 변경됨 멀티 큐 블록 레이어의 아키텍처는 다음 그림과 같이 NVMe의 멀티 큐 설계와 직접적으로 일치합니다.

img

이른바 Multi-Queue 메커니즘은 멀티 코어 CPU의 경우 서로 다른 블록 레이어 제출 대기열을 서로 다른 CPU 코어에 할당하여 IO 워크로드의 균형을 더 잘 맞추고 SSD와 같은 저장 장치의 IO 효율성을 크게 향상시키는 것입니다. Multi-Queue Block Layer는 어떻게 생겼습니까? 그림을 그리고 보세요:

img

  • 다중 대기열 블록 계층은 소프트웨어 대기열과 하드웨어 디스패치 대기열의 두 계층으로 나뉩니다.
  • 소프트웨어 큐는 코어당이며 큐 수는 프로토콜과 관련이 있습니다 예를 들어 NVMe 프로토콜은 최대 64K 쌍의 IO SQ/CQ를 가질 수 있습니다. Software Queues 계층이 하는 일은 위 그림에서 식별 부분입니다.
  • 하드웨어 대기열의 수는 기본 장치 드라이버에 의해 결정되며 하나 이상이 될 수 있습니다. 지원되는 최대 수는 일반적으로 최대 MSI-X 인터럽트 수와 동일하며 2K를 지원합니다. 장치 드라이버는 map_queue를 통해 소프트웨어 대기열과 하드웨어 대기열 간의 도킹 관계를 유지합니다.
  • Hardware Queue와 Software Queue의 수가 반드시 같지는 않다는 점을 강조할 필요가 있는데, 위 그림의 1:1 Mapping 상황이 가장 이상적인 상황이다.

이상으로 Multi-Queue Block Layer의 기본 이론에 대한 복습은 마쳤 으니 nvme_alloc_admin_tags 함수에 대해 다시 살펴보도록 하겠습니다 .

위의 코드에서 크게 세 단계로 나뉩니다.

  • admin_tagset 구조를 초기화하고 특히 이 프로세스에서 ops 할당(나중에 사용됨)을 언급합니다.

    static struct blk_mq_ops nvme_mq_admin_ops = {

    .queue_rq = nvme_queue_rq,

    .complete = nvme_complete_rq,

    .init_hctx = nvme_admin_init_hctx,

    .exit_hctx = nvme_admin_exit_hctx,

    .init_request = nvme_admin_init_request,

    .timeout = nvme_timeout,

    *};**

  • 그런 다음 blk_mq_alloc_tag_set을 호출하여 태그 세트를 할당하고 요청 큐와 연결합니다.

  • 그런 다음 blk_mq_init_allocated_queue를 호출하여 하드웨어 대기열과 소프트웨어 대기열을 초기화하고 둘 사이의 매핑 관계를 구성한 다음 마지막으로 반환 값을 dev->ctrl.admin_q에 전달합니다.

blk_mq_init_allocated_queue는 blk_mq_realloc_hw_ctxs를 호출한 다음 blk_mq_init_hctx를 호출하고 마지막으로 nvme_admin_init_hctx인 set->ops->init_hctx를 호출합니다.

즉, blk_mq_init_allocated_queue 초기화는 최종적으로 nvme_admin_init_hctx 를 호출합니다 .

static int nvme_admin_init_hctx(struct blk_mq_hw_ctx *hctx, void *data,
				unsigned int hctx_idx)
{
	struct nvme_dev *dev = data;
	struct nvme_queue *nvmeq = dev->queues[0];

	WARN_ON(hctx_idx != 0);
	WARN_ON(dev->admin_tagset.tags[0] != hctx->tags);
	WARN_ON(nvmeq->tags);

	hctx->driver_data = nvmeq;
	nvmeq->tags = &dev->admin_tagset.tags[0];
	return 0;
}

위의 코드에서 Hardware Queue가 초기화되면 nvme_configure_admin_queue 함수에서 적용된 NVMe Queue(nvmeq)가 Hardware Queue의 driver_data에 할당됨을 알 수 있습니다 . -NVMe Queue와 Hardware Queue 간의 하나의 대응 , 이는 NVMe와 Linux Multi-Queue Block Layer 간의 암묵적 협력의 핵심이기도 합니다.