Pular para o conteúdo

Rede (Modelo de Driver de NIC)

A categoria de Drivers já cobre armazenamento, entrada, e vídeo; uma network interface controller (NIC) é um tipo diferente de dispositivo dos três, mas os drivers por trás de virtio-net, e1000, e da maioria das outras NICs convergem para o mesmo formato geral: um par de anéis de descritor, um para transmissão e um para recepção, que um driver preenche e esvazia em vez de emitir comandos individuais por pacote da forma que ATA PIO emite um comando por setor.

Um anel TX é um array de descritores, cada um apontando para um buffer de dados de pacote que o driver já preparou, que o driver entrega ao controlador avançando um índice de produtor; o controlador percorre o anel independentemente, transmitindo cada pacote por sua vez e marcando seu descritor como concluído uma vez que os dados de fato saíram pelo fio.

struct nic_tx_desc {
uint64_t buffer_addr; // endereço físico dos dados do pacote
uint16_t length;
uint8_t status; // definido pela NIC uma vez transmitido
uint8_t cmd; // definido pelo driver: fim de pacote, etc.
};
void nic_transmit(struct nic *dev, void *packet, size_t len) {
struct nic_tx_desc *desc = &dev->tx_ring[dev->tx_tail];
desc->buffer_addr = virt_to_phys(packet);
desc->length = len;
desc->cmd = TX_CMD_EOP;
dev->tx_tail = (dev->tx_tail + 1) % TX_RING_SIZE;
mmio_write32(dev->regs + TDT_OFFSET, dev->tx_tail); // notifica a NIC
}

Um anel RX funciona na direção oposta: o driver o pré-preenche com buffers vazios e prontos para receber antes de qualquer tráfego sequer chegar, e o controlador preenche um conforme cada pacote chega pelo fio, gerando uma interrupção para dizer ao driver que um buffer agora contém dados reais em vez de o driver precisar sondar por chegadas.

Como o anel RX só tem tantos buffers quanto o driver colocou nele, um handler de interrupção precisa fazer duas coisas para todo pacote recebido, não só uma: entregar o conteúdo do buffer preenchido para qualquer código que processe pacotes recebidos, e imediatamente repor aquele slot do anel com um buffer novo e vazio antes de o controlador dar a volta de novo até ele.

void nic_rx_interrupt(struct nic *dev) {
while (dev->rx_ring[dev->rx_head].status & RX_STATUS_DONE) {
struct nic_rx_desc *desc = &dev->rx_ring[dev->rx_head];
process_packet(phys_to_virt(desc->buffer_addr), desc->length);
desc->buffer_addr = virt_to_phys(alloc_packet_buffer()); // repõe
desc->status = 0;
dev->rx_head = (dev->rx_head + 1) % RX_RING_SIZE;
}
}

Um driver que esquece o passo de reposição, ou repõe um buffer devagar demais, esgota o anel: uma vez que todo descritor RX está marcado como concluído e nenhum é reposto, o controlador não tem mais lugar algum para escrever o próximo pacote chegando e precisa descartá-lo, uma fonte real e comum de perda de pacote sob carga que não tem nada a ver com a rede em si e tudo a ver com quão rápido o próprio handler de interrupção do driver mantém o anel abastecido com buffers novos.

VirtIO já cobre a estrutura de virtqueue da qual os próprios anéis TX e RX do virtio-net são de fato construídos, e um driver visando ele herda toda vantagem que esse design genérico já fornece sobre emular um pedaço específico de hardware físico com fidelidade: sem peculiaridades de registrador específicas de fabricante, sem modos de compatibilidade legados carregados de gerações anteriores de chip, e um formato de descritor projetado desde o início para ser simples de implementar corretamente tanto para o guest quanto para o host. Esse é exatamente o motivo pelo qual virtio-net costuma ser o primeiro driver de rede que um kernel hobbyist escreve: é o dispositivo de rede que todo hypervisor comum já expõe a uma máquina virtual por padrão, e seu driver é uma camada comparativamente fina sobre a mecânica de virtqueue que VirtIO já cobre por completo, em vez de um esforço de engenharia reversa do zero contra a interface de registrador de um chip real.

Este artigo para na fronteira do driver deliberadamente: tudo acima dele, enquadramento Ethernet, ARP, IP, TCP/UDP, e o resto de uma pilha de rede que interpreta o que os bytes de um buffer recebido de fato significam, é um assunto separado e consideravelmente maior, fora do escopo aqui. Uma NIC é descoberta através do PCI da mesma forma que qualquer outro dispositivo PCI, com sua BAR mapeada tanto como registradores MMIO (o caso comum para um controlador moderno) quanto ocasionalmente portas de I/O para um dispositivo mais antigo ou simples, antes de a configuração de anel sequer poder começar; um driver precisa configurar o local físico (ou virtual) dos dois anéis, seus tamanhos, e habilitar a lógica de transmissão e recepção do controlador através de sua própria sequência de inicialização específica de dispositivo antes de qualquer pacote poder ser enviado ou recebido.

  1. ^ Intel, PCIe GbE Controllers Open Source Software Developer’s Manual (a família e1000): uma interface de anel de descritor de hardware real representativa, documentada por completo.
  • PCI: como uma NIC é descoberta e seus registradores mapeados antes de a configuração de anel poder começar.
  • VirtIO: a estrutura de virtqueue sobre a qual os próprios anéis TX/RX do virtio-net são diretamente construídos.
  • ATA/IDE em Modo PIO: um modelo de driver contrastante, construído em torno de polling por comando em vez de anéis de descritor.