리눅스 커널 (42) 썸네일형 리스트형 NVMe device driver code tuning & comments - 4 디바이스 드라이버 초기화에 이어서, 이번에는 IO를 각 SQ에 어떻게 큐잉하는지에 대한 내용이다. 파일 시스템부터 디바이스 드라이버 계층 까지의 코드 흐름은 일단 생략하고, nvme_queue_rq() 함수에 대해 소개하겠다. 이 함수에 대해 간단히 설명하면 블록 멀티 큐에서 nvme device driver 로 넘어갈 때 호출되는 함수이다. 그래서 IO type에 따라 알맞는 SQ로 큐잉하기 위해 다음과 같이 함수 일부를 수정하였다. static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { ... op = rq_data_dir(req); if (nvmeq->qid == 0) __nv.. NVMe device driver code tuning & comments - 3 이번에는 nvme_queue 구조체 안에 여러개의 SQ를 만들 차례이다. nvme_reset_work() -> nvme_setup_io_queues() -> nvme_create_io_queues() 먼저 모든 nvme_queue는 원래대로 SQ를 각각 하나 씩 생성하게 한다. 그리고 이것이 성공하면 다음과 같이 모든 코어에 추가로 SQ를 생성하게 한다. static int nvme_create_io_queues(struct nvme_dev *dev) { ... if (!ret) //all nvme queue was successfully created { int result = 0, extra_sq_cnt = 0, qid = 1; while (!(result = nvme_create_extra_queu.. NVMe device driver code tuning & comments - 2 이번에는 nvme device driver 가 초기화 될 때 커널코드를 수정한 것을 그 흐름에 따라 정리해보겠다. 먼저 nvme_probe()가 디바이스 드라이버 초기화를 위해 제일 먼저 호출 된다. static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int node, result = -ENOMEM; struct nvme_dev *dev; unsigned long quirks = id->driver_data; node = dev_to_node(&pdev->dev); if (node == NUMA_NO_NODE) set_dev_node(&pdev->dev, first_memory_node); dev = kzalloc_n.. NVMe device driver code tuning & comments - 1 구현한 NVMe device driver 구조. nvme device driver 관련된 자료구조중 nvme_queue 구조체가 3개의 SQ를 갖게한다. 3개의 SQ는 아래와 같은데, 1. Read Only SQ 2. Write Only SQ 3. Urgent SQ NVMe SSD가 WRR을 지원하면, 생성할 SQ의 Weight를 설정하여 IO의 차별적인 서비스 제공이 가능해진다. 여기서는 좀 극단적으로 Write에는 2, Read에는 8이라는 weight를 주어 응답성과 관련된 read연산의 성능을 향상 시키는 것을 목표로 하였고, 더 나아가 Urgent SQ를 두어 우선순위가 높은 프로세스의 IO는 이보다 더 빨리 처리되도록 구현하였다. 이는 아래와 같은 스케줄링을 NVMe SSD가 하기 때문이다. .. urgent queue 를 위한 tag 예약 1. request_queue 구조체 안에 int adm; 추가 ->adm = 0; //일반 IO 큐로 다룬다. ->adm = 1; //관리자 큐로 다룬다. 2. request 구조체 안에 int urg; 추가 3. blk_mq_tags 구조체 안에 int nr_allocated; 추가 ->read/write를 위해 할당한 태그 개수를 저장한다. 4. __blk_mq_get_tag() 수정 static int __blk_mq_get_tag(struct blk_mq_alloc_data *data, struct sbitmap_queue *bt) { if (!(data->flags & BLK_MQ_REQ_INTERNAL) && !hctx_may_queue(data->hctx, bt)) return -1; if.. blk_mq_init_queue 블록 레이어에서 사용할 request_queue 를 할당하기 위해 호출되는 함수이다. 관리자 큐를 위해 nvme_alloc_admin_tagset() 에서 호출되는 것과 io 큐를 위해 nvme_alloc_ns() 에서 호출된다. 아래 흐름은 io 큐를 위해 호출된 상황이므로 set 은 dev.tagset 이다. 관리자 용 tagset이 아닌 것이다. blk_mq_init_queue(struct blk_mq_tag_set * set) from nvme_alloc_ns // ->uninit_q = blk_alloc_queue_node(GFP_KERNEL, set->numa_node); ->blk_mq_init_allocated_queue(set, uninit_q); // ->q->mq_ops = set->.. blk_mq_alloc_tag_set 흐름 이 함수는 nvme_reset_work() 에서 nvme_alloc_admin_tags() 에서도 호출된다. 그러나 이 때에는 관리자 큐를 위한 tagset 및 tags를 할당하기 위한 것이나 그 흐름은 거의 유사하다. nvme_dev_add(struct nvme_dev * dev) from nvme_reset_work ->dev->tagset.ops = &nvme_mq_ops; ->dev->tagset.nr_hw_queues = dev->online_queues - 1; ->dev->tagset.queue_depth = min_t(int, dev->q_depth, BLK_MQ_MAX_DEPTH) - 1; ->dev->tagset.driver_data = dev; ->blk_mq_alloc_tag_set.. nvme command 관련 추가 내용 struct nvme_dev { u32 __iomem *dbs; struct dma_pool *prp_page_pool; struct dma_pool *prp_small_pool; u32 db_stride; void __iomem *bar; void __iomem *cmb; pci_bus_addr_t cmb_bus_addr; /* shadow doorbell buffer support: */ u32 *dbbuf_dbs; dma_addr_t dbbuf_dbs_dma_addr; u32 *dbbuf_eis; dma_addr_t dbbuf_eis_dma_addr; /* host memory buffer support: */ u64 host_mem_size; u32 nr_host_mem_descs; dma_add.. nvme command i/o completion queue 만들기 Queue size : CQ내에 entry 개수. 2Bytes Queue Identifier : CQ의 id. 2Bytes Interrupt Vector : MSI-X 또는 MSI vector 번호. 2Bytes IEN : Interrupt Enable, 1 == enable. 1bit PC : Physically Contiguous, 1 == SQ는 물리적으로 호스트 메모리에서 연속적이다. 1bit PRP Entry 1 : 물리적으로 연속적이지 않을 때, 호스트 페이지를 포함하는 PRP list에 포인터이다. 8Bytes enum nvme_admin_opcode { nvme_admin_delete_sq= 0x00, nvme_admin_create_sq= 0x.. nvme_setup_io_queues 흐름 nvme_setup_io_queues(struct nvme_dev * dev) from nvme_reset_work ->struct nvme_queue * adminq = dev->queues[0]; ->struct pci_dev * pdev = to_pci_dev(dev->dev); ->nr_io_queues = num_present_cpus(); ->result = nvme_set_queue_count(&dev->ctrl, &nr_io_queues) ->pci_free_irq(pdev, 0, adminq); ->pci_free_irq_vectors(pdev); ->pci_disable_msix(dev); ->pci_disable_msi(dev); ->dev->max_qid = nr_io_queues;.. 이전 1 2 3 4 5 다음