Ich verwende Linux 5.1 auf einem Cyclone V SoC, einem FPGA mit zwei ARMv7-Kernen in einem Chip. Mein Ziel ist es, viele Daten von einer externen Schnittstelle zu sammeln und diese Daten (einen Teil davon) über einen TCP-Socket zu streamen. Die Herausforderung hierbei ist, dass die Datenrate sehr hoch ist und die GbE-Schnittstelle nahezu gesättigt sein könnte. Ich habe eine funktionierende Implementierung, die nur write()
Aufrufe an den Socket verwendet, aber 55 MB / s erreicht. ungefähr die Hälfte der theoretischen GbE-Grenze. Ich versuche jetzt, die TCP-Übertragung ohne Kopie zum Laufen zu bringen, um den Durchsatz zu erhöhen, aber ich stoße an eine Wand.
Um die Daten aus dem FPGA in den Linux-User-Space zu bringen, habe ich einen Kernel-Treiber geschrieben. Dieser Treiber verwendet einen DMA-Block im FPGA, um eine große Datenmenge von einer externen Schnittstelle in den DDR3-Speicher zu kopieren, der an die ARMv7-Kerne angeschlossen ist. Der Treiber weist diesen Speicher als eine Reihe zusammenhängender 1-MB-Puffer zu, wenn er dma_alloc_coherent()
mit verwendet wird GFP_USER
, und stellt diese der Userspace-Anwendung zur Verfügung, indem er mmap()
eine Datei in implementiert /dev/
und eine Adresse an die Anwendung dma_mmap_coherent()
zurückgibt , die die vorab zugewiesenen Puffer verwendet.
So weit, ist es gut; Die User-Space-Anwendung sieht gültige Daten und der Durchsatz ist mehr als ausreichend bei> 360 MB / s mit genügend Platz (die externe Schnittstelle ist nicht schnell genug, um wirklich zu erkennen, wie hoch die Obergrenze ist).
Um ein TCP-Netzwerk ohne Kopie zu implementieren, war mein erster Ansatz die Verwendung SO_ZEROCOPY
auf dem Socket:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Dies führt jedoch zu send: Bad address
.
Nachdem ich ein bisschen gegoogelt hatte, bestand mein zweiter Ansatz darin, eine Pfeife zu verwenden, splice()
gefolgt von 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;
}
Das Ergebnis ist jedoch dasselbe : vmsplice: Bad address
.
Beachten Sie, dass alles einwandfrei funktioniert , wenn ich den Aufruf einer Funktion vmsplice()
oder send()
einer Funktion ersetze, die nur die Daten druckt, auf die buf
(oder eine send()
ohne MSG_ZEROCOPY
) zeigt. Daher sind die Daten für den Benutzerbereich zugänglich, aber die vmsplice()
/ send(..., MSG_ZEROCOPY)
-Aufrufe scheinen nicht in der Lage zu sein, damit umzugehen.
Was fehlt mir hier? Gibt es eine Möglichkeit, TCP-Sendungen ohne Kopie mit einer Benutzerbereichsadresse zu verwenden, die von einem Kerneltreiber über erhalten wurde dma_mmap_coherent()
? Gibt es einen anderen Ansatz, den ich verwenden könnte?
AKTUALISIEREN
Also bin ich etwas tiefer in den sendmsg()
MSG_ZEROCOPY
Pfad im Kernel eingetaucht, und der Aufruf, der schließlich fehlschlägt, ist get_user_pages_fast()
. Dieser Aufruf wird zurückgegeben, -EFAULT
da check_vma_flags()
das VM_PFNMAP
in der gesetzte Flag gefunden wird vma
. Dieses Flag wird anscheinend gesetzt, wenn die Seiten mit remap_pfn_range()
oder in den Benutzerbereich abgebildet werden dma_mmap_coherent()
. Mein nächster Ansatz ist es, einen anderen Weg zu mmap
diesen Seiten zu finden.