Pular para o conteúdo

VirtIO e a Virtqueue

Todo dispositivo virtio (rede, bloco, GPU, e outros) compartilha uma única estrutura por baixo independentemente do que de fato faz: a virtqueue, um anel de descritores configurado uma vez e reutilizado para toda transferência subsequente entre guest e host. O PCI já identifica 0x1AF4 como o vendor ID que o QEMU e a maioria dos hypervisors usam para essa família de dispositivo; este artigo cobre a própria virtqueue, o mecanismo do qual todo dispositivo virtio específico (virtio-net, virtio-blk, e assim por diante) é construído no fim das contas.

O núcleo de uma virtqueue é um array de tamanho fixo de descritores, cada um descrevendo um único buffer: seu endereço físico de guest, seu comprimento, um campo de flags, e, quando uma requisição abrange mais de um buffer, um índice next encadeando-o a outro descritor na mesma tabela.

struct virtq_desc {
uint64_t addr; // endereço físico de guest do buffer
uint32_t len;
uint16_t flags; // VIRTQ_DESC_F_NEXT, F_WRITE, F_INDIRECT
uint16_t next; // próximo índice de descritor, se F_NEXT estiver definido
};

Um descritor sozinho é só uma descrição de buffer; nada na tabela por si só diz quais descritores atualmente representam uma requisição real e pendente versus quais estão simplesmente não usados. Essa contabilidade é o que os dois anéis ao redor da tabela de descritores existem para fornecer.

O available ring é escrito pelo guest e lido pelo host: ele lista, em ordem, o índice do primeiro descritor (ou a cabeça de uma lista encadeada de descritores) para toda requisição que o guest preparou e quer que o host processe. O used ring roda na direção oposta, escrito pelo host e lido pelo guest, listando quais cadeias de descritores o host terminou de processar e quantos bytes ele de fato escreveu de volta para cada uma, então o guest sabe que uma dada requisição completou sem precisar sondar o próprio estado de cada descritor individualmente.

Uma entrada do available ring apontando para um par de descritores encadeados, cuja cabeça o used ring referencia de novo assim que o host termina de processá-loAvailable ring(guest escreve)Tabela de descritores(memória compartilhada)Used ring(host escreve)011230livre1buf A, len next → 22buf B, len (cauda)3livre01id 1len 51223
struct virtq_avail {
uint16_t flags;
uint16_t idx; // próximo slot que o guest vai escrever
uint16_t ring[]; // índices de cabeça de descritor
};
struct virtq_used_elem {
uint32_t id; // cabeça de cadeia de descritor que completou
uint32_t len; // bytes que o host de fato escreveu
};
struct virtq_used {
uint16_t flags;
uint16_t idx; // próximo slot que o host vai escrever
struct virtq_used_elem ring[];
};

Submeter uma requisição do lado do guest significa preencher um ou mais descritores, escrever o índice do descritor de cabeça no próximo slot livre de avail.ring, incrementar avail.idx, e então notificar o dispositivo (comumente através de uma escrita dedicada em MMIO ou porta) de que há trabalho novo disponível; o host, independentemente, observa used.idx avançar para saber que uma requisição previamente submetida de fato completou e é seguro ler o resultado que o buffer do descritor correspondente agora contém. Os dois anéis são estruturas de produtor único e apenas anexação, dando a volta em seu tamanho fixo em vez de crescer, o que é o que mantém atualizar qualquer um deles uma operação rápida e sem lock tanto do lado do guest quanto do host.

Por que isso é mais simples do que emular hardware real

Seção intitulada “Por que isso é mais simples do que emular hardware real”

Um emulador de dispositivo reproduzindo fielmente a semântica de registrador de uma NIC física ou controlador de disco (timers de coalescência de interrupção, formatos de descritor de DMA herdados de décadas de silício real, peculiaridades específicas de fabricante) precisa replicar toda essa complexidade em software só para ser indistinguível do chip real que está fingindo ser. Virtio contorna isso por completo não fingindo ser dispositivo físico algum: o formato da virtqueue é projetado desde o início para ser eficiente de implementar dos dois lados de uma fronteira de máquina virtual, não para corresponder ao layout de registrador de um chip particular, o que é o que torna um driver virtio-net ou virtio-blk consideravelmente mais simples de escrever corretamente do que um driver equivalente para hardware emulado com fidelidade física total, e parte do motivo pelo qual virtio-net especificamente costuma ser o primeiro driver de rede que um kernel hobbyist implementa, assunto que seu próprio artigo dedicado cobre do lado da rede em vez do lado da virtqueue no qual este se concentra.

Um dispositivo pode expor mais de uma virtqueue ao mesmo tempo (virtio-net convencionalmente usa filas separadas para transmissão e recepção, por exemplo), cada uma dimensionada e indexada independentemente, descoberta e configurada através do próprio espaço de configuração do dispositivo em vez de uma única fila compartilhada servindo todo propósito. A flag VIRTQ_DESC_F_INDIRECT permite que um único descritor aponte para uma tabela adicional de descritores em outro lugar na memória em vez de encadear através da tabela principal diretamente, útil para uma requisição que de outra forma precisaria de mais descritores encadeados do que caberiam confortavelmente dado o tamanho fixo da fila. Como o guest e o host observam as atualizações de anel um do outro sem uma barreira de memória completa automaticamente imposta pelo próprio transporte em toda configuração, um driver precisa seguir as regras de ordenação documentadas pela especificação (escrever o conteúdo de um descritor antes de publicar seu índice no available ring, por exemplo) em vez de presumir que execução em ordem comum é suficiente em toda plataforma na qual virtio possa rodar.

  1. ^ OASIS, Virtual I/O Device (VIRTIO) Specification: a especificação formal que define o layout da virtqueue e o protocolo de anéis available/used descrito acima.
  • PCI: o vendor ID e o espaço de configuração através dos quais um dispositivo virtio é descoberto.
  • Rede: virtio-net, um dispositivo concreto construído sobre a virtqueue que este artigo cobre.