Estou executando o Linux 5.1 em um Cyclone V SoC, que é um FPGA com dois núcleos ARMv7 em um chip. Meu objetivo é coletar muitos dados de uma interface externa e transmitir (parte) esses dados através de um soquete TCP. O desafio aqui é que a taxa de dados é muito alta e pode quase saturar a interface GbE. Eu tenho uma implementação funcional que apenas usa write()
chamadas para o soquete, mas atinge o limite de 55 MB / s; aproximadamente metade do limite teórico de GbE. Agora estou tentando fazer com que a transmissão TCP de cópia zero funcione para aumentar a taxa de transferência, mas estou atingindo uma parede.
Para obter os dados do FPGA no espaço de usuário do Linux, escrevi um driver de kernel. Esse driver usa um bloco DMA no FPGA para copiar uma grande quantidade de dados de uma interface externa na memória DDR3 conectada aos núcleos do ARMv7. O driver aloca essa memória como um monte de buffers contíguos de 1 MB quando testados dma_alloc_coherent()
com GFP_USER
, e os expõe ao aplicativo userspace implementando mmap()
em um arquivo /dev/
e retornando um endereço para o aplicativo usando dma_mmap_coherent()
os buffers pré-alocados.
Por enquanto, tudo bem; o aplicativo de espaço do usuário está vendo dados válidos e a taxa de transferência é mais que suficiente em> 360 MB / s, com espaço de sobra (a interface externa não é rápida o suficiente para realmente ver qual é o limite superior).
Para implementar redes TCP de cópia zero, minha primeira abordagem foi usar SO_ZEROCOPY
no soquete:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
No entanto, isso resulta em send: Bad address
.
Depois de pesquisar um pouco, minha segunda abordagem foi usar um pipe e splice()
seguido por vmsplice()
:
ssize_t sent_bytes;
int pipes[2];
struct iovec iov = {
.iov_base = buf,
.iov_len = len
};
pipe(pipes);
sent_bytes = vmsplice(pipes[1], &iov, 1, 0);
if (sent_bytes < 0) {
perror("vmsplice");
return -1;
}
sent_bytes = splice(pipes[0], 0, fd, 0, sent_bytes, SPLICE_F_MOVE);
if (sent_bytes < 0) {
perror("splice");
return -1;
}
No entanto, o resultado é o mesmo: vmsplice: Bad address
.
Observe que, se eu substituir a chamada para vmsplice()
ou send()
para uma função que apenas imprima os dados apontados por buf
(ou send()
sem MSG_ZEROCOPY
), tudo estará funcionando bem; portanto, os dados estão acessíveis ao espaço do usuário, mas as chamadas vmsplice()
/ send(..., MSG_ZEROCOPY)
parecem incapazes de lidar com isso.
O que estou perdendo aqui? Existe alguma maneira de usar o TCP de cópia zero enviando com um endereço de espaço de usuário obtido de um driver de kernel dma_mmap_coherent()
? Existe outra abordagem que eu poderia usar?
ATUALIZAR
Então, eu mergulhei um pouco mais no sendmsg()
MSG_ZEROCOPY
caminho do kernel, e a chamada que eventualmente falha é get_user_pages_fast()
. Essa chamada retorna -EFAULT
porque check_vma_flags()
localiza o VM_PFNMAP
sinalizador definido no vma
. Aparentemente, esse sinalizador é definido quando as páginas são mapeadas no espaço do usuário usando remap_pfn_range()
ou dma_mmap_coherent()
. Minha próxima abordagem é encontrar outro caminho para mmap
essas páginas.