Używam Linuksa 5.1 na SoC Cyclone V, który jest FPGA z dwoma rdzeniami ARMv7 w jednym układzie. Moim celem jest zebranie dużej ilości danych z zewnętrznego interfejsu i przesłanie (części) tych danych przez gniazdo TCP. Wyzwanie polega na tym, że szybkość przesyłania danych jest bardzo wysoka i może zbliżyć się do nasycenia interfejsu GbE. Mam działającą implementację, która po prostu wykorzystuje write()
wywołania do gniazda, ale osiąga maksymalną prędkość 55 MB / s; mniej więcej połowa teoretycznego limitu GbE. Próbuję teraz uruchomić transmisję TCP bez kopiowania, aby zwiększyć przepustowość, ale uderzam o ścianę.
Aby przenieść dane z FPGA do przestrzeni użytkownika Linuksa, napisałem sterownik jądra. Ten sterownik używa bloku DMA w układzie FPGA do kopiowania dużej ilości danych z zewnętrznego interfejsu do pamięci DDR3 podłączonej do rdzeni ARMv7. W tej pamięci sterownik przydziela jak pęczek sąsiadujących buforów 1MB gdy badane przy użyciu dma_alloc_coherent()
z GFP_USER
, i naraża je do stosowania w przestrzeni użytkownika poprzez wdrożenie mmap()
na pliku w /dev/
i powrocie adresu do aplikacji za pomocą dma_mmap_coherent()
na zdefiniowanej przez bufory.
Na razie w porządku; aplikacja działająca w przestrzeni użytkownika widzi prawidłowe dane, a przepustowość jest większa niż wystarczająca przy> 360 MB / s, z wolną przestrzenią (zewnętrzny interfejs nie jest wystarczająco szybki, aby naprawdę zobaczyć, jaka jest górna granica).
Aby zaimplementować sieć TCP z zerową kopią, moim pierwszym podejściem było użycie SO_ZEROCOPY
gniazda:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
To jednak powoduje send: Bad address
.
Po pewnym czasie googlowania, moim drugim podejściem było użycie fajki, a splice()
następnie 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;
}
Jednak wynik jest taki sam: vmsplice: Bad address
.
Zauważ, że jeśli zastąpię wywołanie funkcji vmsplice()
lub send()
funkcji, która po prostu drukuje dane wskazane przez buf
(lub send()
bez MSG_ZEROCOPY
), wszystko działa dobrze; więc dane są dostępne dla przestrzeni użytkownika, ale połączenia vmsplice()
/ send(..., MSG_ZEROCOPY)
wydają się nie być w stanie ich obsłużyć.
Czego tu brakuje? Czy jest jakiś sposób na użycie wysyłania TCP bez kopiowania z adresem przestrzeni użytkownika uzyskanym przez sterownik jądra dma_mmap_coherent()
? Czy mogę zastosować inne podejście?
AKTUALIZACJA
Więc zagłębiłem się nieco głębiej w sendmsg()
MSG_ZEROCOPY
ścieżkę jądra, a wywołanie, które ostatecznie kończy się niepowodzeniem, jest get_user_pages_fast()
. To wywołanie zwraca, -EFAULT
ponieważ check_vma_flags()
znajduje VM_PFNMAP
flagę ustawioną w vma
. Ta flaga jest najwyraźniej ustawiona, gdy strony są mapowane w przestrzeń użytkownika za pomocą remap_pfn_range()
lub dma_mmap_coherent()
. Moje następne podejście polega na znalezieniu innego sposobu na mmap
te strony.