Sto eseguendo Linux 5.1 su un SoC Cyclone V, che è un FPGA con due core ARMv7 in un chip. Il mio obiettivo è quello di raccogliere molti dati da un'interfaccia esterna e trasmettere (parte di) questi dati attraverso un socket TCP. La sfida qui è che la velocità dei dati è molto alta e potrebbe avvicinarsi a saturare l'interfaccia GbE. Ho un'implementazione funzionante che utilizza solo le write()
chiamate al socket, ma supera a 55 MB / s; circa la metà del limite teorico GbE. Ora sto cercando di far funzionare la trasmissione TCP zero-copia per aumentare il throughput, ma sto colpendo un muro.
Per ottenere i dati dall'FPGA nello spazio utente di Linux, ho scritto un driver del kernel. Questo driver utilizza un blocco DMA nell'FPGA per copiare una grande quantità di dati da un'interfaccia esterna nella memoria DDR3 collegata ai core ARMv7. Il driver alloca questa memoria come un gruppo di buffer contigui da 1 MB quando viene sondato usando dma_alloc_coherent()
con GFP_USER
, ed espone questi all'applicazione dello spazio utente implementando mmap()
un file /dev/
e restituendo un indirizzo all'applicazione usando dma_mmap_coherent()
sui buffer preallocati.
Fin qui tutto bene; l'applicazione per lo spazio utente sta visualizzando dati validi e la velocità effettiva è più che sufficiente a> 360 MB / s con spazio libero (l'interfaccia esterna non è abbastanza veloce per vedere quale sia il limite superiore).
Per implementare la rete TCP zero-copia, il mio primo approccio è stato quello di utilizzare SO_ZEROCOPY
sul socket:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Tuttavia, questo si traduce in send: Bad address
.
Dopo aver cercato su Google per un po ', il mio secondo approccio era usare una pipe e splice()
seguito da 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;
}
Tuttavia, il risultato è lo stesso: vmsplice: Bad address
.
Nota che se sostituisco la chiamata a vmsplice()
o send()
a una funzione che stampa solo i dati puntati da buf
(o send()
senza MSG_ZEROCOPY
), tutto funziona perfettamente; quindi i dati sono accessibili allo spazio utenti, ma le chiamate vmsplice()
/ send(..., MSG_ZEROCOPY)
sembrano incapaci di gestirli.
Cosa mi sto perdendo qui? Esiste un modo per utilizzare l'invio TCP zero-copy con un indirizzo spazio utente ottenuto da un driver del kernel dma_mmap_coherent()
? C'è un altro approccio che potrei usare?
AGGIORNARE
Quindi mi sono tuffato un po 'più a fondo nel sendmsg()
MSG_ZEROCOPY
percorso nel kernel e la chiamata che alla fine fallisce è get_user_pages_fast()
. Questa chiamata ritorna -EFAULT
perché check_vma_flags()
trova il VM_PFNMAP
flag impostato in vma
. Questo flag è apparentemente impostato quando le pagine sono mappate nello spazio utente usando remap_pfn_range()
o dma_mmap_coherent()
. Il mio prossimo approccio è trovare un altro modo per mmap
queste pagine.