본문 바로가기

리눅스 커널/블록 멀티 큐

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)
                __nvme_submit_cmd(nvmeq, &cmnd);
        else if (current->prio < 120)
                __nvme_submit_cmd_extra(nvmeq, &cmnd);
        else if (op == READ)
                __nvme_submit_cmd_extra(nvmeq, &cmnd);
        else
                __nvme_submit_cmd(nvmeq, &cmnd);
        //printk("log, core: %d, write: %d, read: %d\n", 
        	//nvmeq->qid - 1, (*(nvmeq->tags))->q_size[0], (*(nvmeq->tags))->q_size[1]);

        nvme_process_cq(nvmeq);
        spin_unlock_irq(&nvmeq->q_lock);
        return BLK_STS_OK;
out_cleanup_iod:
        nvme_free_iod(dev, req);
out_free_cmd:
        nvme_cleanup_cmd(req);
        return ret;
}

먼저 request 구조체로 부터 해당 IO의 type을 확인한다.

첫번째 if는 관리자 큐에 대한 처리이다. 관리자 큐는 SQ가 하나밖에 없기 때문에 원래 커널 코드 흐름대로 전달해야 한다.

 

두번째 else if에서는 read이거나, 프로세스의 우선순위가 높은 경우 추가로 생성한 Read SQ, Urgent SQ로 전달하게 된다.

 

세번째 else는 보통/낮은 프로세스의 Write 요청이므로 Write용 SQ로 전달하는데, 이 때에는 원래부터 있던 SQ로 보내게 된다.

 

마지막으로 추가로 생성한 SQ로 IO를 큐잉하는 함수이다.

static void __nvme_submit_cmd_extra(struct nvme_queue *nvmeq,
                                                struct nvme_command *cmd)
{
        struct extra_queue * sq = nvmeq->extra_list[nvmeq->extra_cursor];
        if (current->prio < 120)
                sq = nvmeq->extra_list[1];
        else
                sq = nvmeq->extra_list[0];

        u16 tail = sq->sq_tail_extra;

        /*if (sq->size >= nvmeq->size)
        {
                __nvme_submit_cmd(nvmeq, cmd);
                return;
        }*/

        if (sq->sq_cmds_io_extra)
                memcpy_toio(&sq->sq_cmds_io_extra[tail], cmd, sizeof(*cmd));
        else
                memcpy(&sq->sq_cmds_extra[tail], cmd, sizeof(*cmd));

        if (++tail == nvmeq->q_depth)
                tail = 0;
        if (nvme_dbbuf_update_and_check_event(tail, sq->dbbuf_sq_db_extra,
                                              sq->dbbuf_sq_ei_extra))
                writel(tail, sq->q_db_extra);

        sq->size += 1;
        //if (++nvmeq->extra_cursor >= nvmeq->extra_cnt)
                nvmeq->extra_cursor = 0;
        sq->sq_tail_extra = tail;
}

먼저 프로세스의 우선순위를 확인한다.

우선순위가 높으면 세번째로 생성한 SQ, 즉, Urgent SQ로 보내기 위해 해당 SQ를 얻고,

우선순위가 높지 않으면 해당 IO request는 READ이므로 Read용 SQ를 얻는다.

 

그외 나머지 코드는 원래 __nvme_submit_cmd()와 유사하다.

각 SQ가 소유한 DMA에 nvme_command를 등록하고, doorbell register를 업데이트 하는 것이다.

 

/****/

커널 코드를 수정하면서 복잡한 알고리즘이 적용되지는 않았다.

그러나 커널 코드 흐름 자체를 이해하기 위한 자료가 매우 부실하였기에,

여러 번 코드를 읽고 분석하여야 했다. 아직까지도 전체 코드 흐름을 자세히 이해한 것은 아니지만

전체적인 틀과 몇몇 세부적인 사항만 이해하였다.

아무래도 디바이스 드라이버 코드를 작성하는 것이기에 nvme spec이 이해가 잘 안되었던 부분에 가장 많은 도움이 되었다.