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_ZEROCOPYgniazda:
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, -EFAULTponieważ check_vma_flags()znajduje VM_PFNMAPflagę 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 mmapte strony.