Estoy ejecutando Linux 5.1 en un Cyclone V SoC, que es un FPGA con dos núcleos ARMv7 en un chip. Mi objetivo es reunir muchos datos de una interfaz externa y transmitir (parte de) estos datos a través de un socket TCP. El desafío aquí es que la velocidad de datos es muy alta y podría llegar a saturar la interfaz GbE. Tengo una implementación funcional que solo usa write()
llamadas al socket, pero alcanza un máximo de 55MB / s; aproximadamente la mitad del límite teórico de GbE. Ahora estoy tratando de hacer que la transmisión TCP de copia cero funcione para aumentar el rendimiento, pero estoy golpeando una pared.
Para obtener los datos del FPGA en el espacio de usuario de Linux, he escrito un controlador de kernel. Este controlador utiliza un bloque DMA en el FPGA para copiar una gran cantidad de datos desde una interfaz externa en la memoria DDR3 conectada a los núcleos ARMv7. El controlador asigna esta memoria como un montón de búferes contiguos de 1 MB cuando se prueba dma_alloc_coherent()
con GFP_USER
, y los expone a la aplicación de espacio de usuario implementando mmap()
en un archivo /dev/
y devolviendo una dirección a la aplicación usando dma_mmap_coherent()
los búferes preasignados.
Hasta aquí todo bien; la aplicación de espacio de usuario está viendo datos válidos y el rendimiento es más que suficiente a> 360 MB / s con espacio de sobra (la interfaz externa no es lo suficientemente rápida como para ver realmente cuál es el límite superior).
Para implementar la red TCP de copia cero, mi primer enfoque fue usar SO_ZEROCOPY
en el socket:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Sin embargo, esto da como resultado send: Bad address
.
Después de buscar en Google por un momento, mi segundo enfoque fue usar una tubería y splice()
seguido de 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;
}
Sin embargo, el resultado es el mismo: vmsplice: Bad address
.
Tenga en cuenta que si se sustituye la llamada a vmsplice()
o send()
a una función que sólo imprime los datos apuntado por buf
(o send()
sin MSG_ZEROCOPY
), todo funciona muy bien; entonces los datos son accesibles para el espacio de usuario, pero las llamadas vmsplice()
/ send(..., MSG_ZEROCOPY)
parecen incapaces de manejarlo.
¿Que me estoy perdiendo aqui? ¿Hay alguna forma de usar el envío TCP de copia cero con una dirección de espacio de usuario obtenida de un controlador de kernel dma_mmap_coherent()
? ¿Hay otro enfoque que pueda usar?
ACTUALIZAR
Así que me sumergí un poco más en la sendmsg()
MSG_ZEROCOPY
ruta del núcleo, y la llamada que finalmente falla es get_user_pages_fast()
. Esta llamada regresa -EFAULT
porque check_vma_flags()
encuentra la VM_PFNMAP
bandera establecida en vma
. Aparentemente, este indicador se establece cuando las páginas se asignan al espacio de usuario utilizando remap_pfn_range()
o dma_mmap_coherent()
. Mi próximo enfoque es encontrar otra forma de acceder a mmap
estas páginas.