ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SPDK] Virtualized I/O with Vhost-user
    논문 정리/Vertical optimization 2019. 10. 2. 02:10

    Introduction

    이 챕터는 vhost가 실제로 어떤 작업을 하는지에 대해 설명한다. 여기서 사용된 코드는 설명을 위해 간략화 되어있으며 API나 구현 참고자료로 사용해서는 안된다. virtio 스펙을 보면 다음과 같은 문구가 잇다.

    virtio와 virtio 스펙은 가상화 환경에서 guest 가 가상 장치에 대해 직관적, 효율적, 표준적이고 확장 가능한 기능을 갖게 함을 목적으로 한다.

    Virtio 장치는 virtqueue를 사용하여 데이터를 효율적으로 전송한다. virtqueue는 single-producer, single-consumer, ring strucuture의 3가지 요소로 구성되어 있으며 일반적인 scatter-gather I/O를 처리한다. QEMU 가상화 환경에서 일반적으로 사용되며 QEMU는 virtual PCI를 제공하고 guest OS는 Virtio PCI 드라이버를 통해 virtual PCI와 통신한다. Virtio를 사용할 경우 모든 IO는 guest OS가 아닌 QEMU가 처리하게 된다.

    Vhost는 inter-process communication (IPC)를 통해 접근 가능한 장치들을 위한 프로토콜이다. Vhost는 Virtio의 virtqueue와 동일한 레이아웃을 사용하여 Vhost 장치가 곧바로 Virtio 장치에 대응 가능하도록 한다. 이를 통해 QEMU 가상화 환경 내의 guest OS가 기존의 Virtio PCI 드라이버를 통해서 SPDK 애플리케이션에 의해 제공되는 Vhost 장치를 별다른 작업 없이 곧바로 사용할 수 있게 한다. 요청의 설정과 I/O submission 알림, I/O completion 인터럽트 처리만 QEMU를 통해 처리된다. 자세한 처리 방식은 #SPDK Optimizations를 참고하라.

    초기의 vhost는 리눅스 커널의 일부로 구현되었으며 ioctl 인터페이스를 통해 유저스페이스 애플리케이션과 통신하였다. SPDK가 vhost 장치로서 동작할 수 있도록 하는 것은 Vhost-user 프로토콜이다.

    Vhost-user 스펙을 보면 아래와 같이 설명되어 있다.

    Vhost-user 프로토콜은 리눅스 커널의 vhost 구현을 조절하는데 사용되는 ioctl 인터페이스를 보완하는 것을 목표로 한다. Vhost-user는 같은 호스트 상의 유저스페이스 애플리케이션과 virtqueue를 공유하기 위한 제어 영역을 구현하고 있다. 유닉스 소켓을 통해 전달하는 데이터에 추가적으로 파일 디스크립터를 전달하여 공유하는 방식을 사용한다.
    프로토콜에서는 master와 slave 양쪽의 통신 방식을 정의한다. Master는 virtqueue를 갖고 이를 공유해주는 쪽을 의미하며, QEMU가 그 예이다. Slave는 공유된 virtqueue를 사용하는 쪽이다. 현재 구현에서는 QEMU를 mater로 사용하며 Snabbswitch와 같이 유저 스페이스에서 동작하는 Ethernet switch 소프트웨어가 slave가 된다. Master와 slave중 어느 쪽이 socket 통신의 서버/클라이언트가 되는지는 상관하지 않는다.

    SPDK의 vhost는 Vhost-user의 slave 서버역할을 하고, unix domain socket을 제공하여 다른 애플리케이션이 연결할 수 있도록 한다.

    QEMU

    Vhost-user 를 사용하는 주요 케이스중 하나가 DPDK를 이용한 네트워크 통신이나 SPDK 오프로딩한 QEMU이다. 아래의 그림은 QEMU 기반의 가상환경 시스템이 SPDK 의 Vhost-SCSI와 통신하는 방식을 표현한다.

    Device initialization

    모든 초기화 및 관리 정보는 Vhost-user 메시지를 통해 교환된다. 초기 연결은 항상 feature negotiation으로 시작된다. Master와 slave 양쪽 모두 자신들이 제공할 수 있는 (구현된) 기능들이 무엇인지 알려준 뒤, 그 중에서 겹치는 기능들을 선택하여 연결을 수립한다. 대부분의 기능들은 구현과 관련되어 있고, 멀티큐 지원 및 라이브 migration과 같은 기능들도 포함한다.

    Negotiation이 끝난 경우, Vhost-user 드라이버를 통해 메모리를 공유하여 SPDK의 vhost device가 해당 메모리에 직접 접근할 수 있도록 한다. 공유되는 메모리는 물리적으로 비연속적인 공간을 나뉘어 질 수 있으므로, 여러 이유로 Vhost-user 스펙에서 나뉘어진 공간의 수를 제한하고 있다. 현재는 8개로 제한되어 있다. 각각의 공유 메모리 영역에 대한 정보를 보낼때는 아래와 같은 정보가 담겨 있다.

    • mmap을 사용하기 위한 파일 디스크립터 (file descriptor)
    • Vhost-user 메시지에서 vring address 변환 등을 사용한 메모리 주소 변환을 위한 user address
    • QEMU의 guest에게는 physical memory로 할당되는 vring 상의 버퍼 address translation인 guest address
    • mmap된 영역으로부터의 위치를 나타내는 user offset
    • size

    Master 는 메모리가 변경될 때마다 (hotplug나 hotremove 등에 의해) 변경된 메모리 정보를 slave로 전송하고 slave는 기존의 메모리 변환 정보를 삭제하고 새 정보를 사용한다.

    Vhost driver는 disk geometry와 같은 장치 정보를 요청할 수도 있다. Vhost-SCSI 같은 경우에는 일반적인 SCSI I/O를 사용하여 실제 장치를 관리하므로 특별히 따로 구현할 필요는 없다.

    초기화 작업이 끝난 이후에는 디바이스가 제공 가능한 최대 queue의 개수를 확인한 뒤 아래와 같은 virtqueue 정보를 slave로 전송한다.

    • unique virtqueue id
    • 가장 마지막에 처리된 vring descriptor의 인덱스
    • vring 주소 (user address space 기준)
    • call descriptor (I/O 가 완료된 이후 master 드라이버에 interrupt를 주기 위함)
    • kick descriptor (I/O 요청을 기다리는 작업을 위한, SPDK에서는 사용되지 않음)

    멀티큐 기능이 활성화된 경우에는 드라이버가 사용하고자 하는 추가적인 큐들에 대해서 polling을 시작한다는 enable 메시지를 전송한다.

    I/O path

    Master 는 공유 메모리 상에 필요한 버퍼를 할당하고 request 정보와 버퍼의 guest address 정보를 virtqueue에 담아서 IO를 요청한다. Virtio-Block request의 구조는 아래와 같다.

    struct virtio_blk_req {
        uint32_t type; // READ, WRITE, FLUSH (read-only)
        uint64_t offset; // offset in the disk (read-only)
        struct iovec buffers[]; // scatter-gather list (read/write)
        uint8_t status; // I/O completion status (write-only)
    };

    Virtio-SCSI의 구조는 아래와 같다.

    struct virtio_scsi_req_cmd {
        struct virtio_scsi_cmd_req *req; // request data (read-only)
        struct iovec read_only_buffers[]; // scatter-gatter list for write I/Os
        struct virtio_scsi_cmd_resp *resp; // response data (write-only)
        struct iovec write_only_buffer[]; // scatter-gatter list for READ I/Os
    };

    Virtqueue는 보통 descriptor의 배열 집합으로 구성되어 있으며, 각각의 IO는 해당하는 디스크립터들의 체인으로 변환된다.

    SPDK Optimizations

    SPDK는 poll 모드를 기본적으로 사용하기 때문에 I/O submission에 대한 notification 부분을 vhost에서 제거하였다. 이를 통해서 vhost server의 throughput을 대폭 증가 시키고, guest가 IO를 요청하는 오버헤드를 감소 시켰다. irqfd, vDPA와 같이 IO completion interrupt의 오버헤드를 줄이기 위한 다른 방식들도 존재하지만 본 문서에서 다루지는 않는다. 가장 높은 성능을 위해서는 poll-mode로 구현된 virtio driver를 사용할 수 있다. 해당 드라이버는 모든 IO completion interrupt를 억제하고 QEMU/KVM의 IO 스택을 완전히 우회하여 오버헤드를 제거한다.

Designed by Tistory.