Я запускаю Linux 5.1 на Cyclone V SoC, який є FPGA з двома ядрами ARMv7 в одній мікросхемі. Моя мета - зібрати велику кількість даних із зовнішнього інтерфейсу та передавати (частину) ці дані через сокет TCP. Проблема тут полягає в тому, що швидкість передачі даних дуже висока і може наблизитися до насичення інтерфейсу GbE. У мене працює реалізація, яка просто використовує write()
дзвінки в сокет, але вона перевищує 55 Мб / с; приблизно половина теоретичної межі GbE. Зараз я намагаюся змусити TCP-передачу з нульовою копією працювати на збільшення пропускної здатності, але я натискаю на стіну.
Щоб отримати дані з FPGA в просторі користувача Linux, я написав драйвер ядра. Цей драйвер використовує блок DMA у FPGA для копіювання великої кількості даних із зовнішнього інтерфейсу в пам'ять DDR3, приєднану до ядер ARMv7. У цьому драйвері виділяє пам'ять як набір послідовних буферів 1Мб при зондуванні використання dma_alloc_coherent()
з GFP_USER
, і виставляють їх в призначеному для користувача додатку, впроваджуючи mmap()
на файл в /dev/
і повертаючи адресу з додатком , використовуючи dma_mmap_coherent()
на визначених буферах.
Все йде нормально; програма для користувальницького простору бачить дійсні дані, а пропускна здатність більш ніж достатня при> 360 Мб / с, щоб залишити місце (зовнішній інтерфейс недостатньо швидкий, щоб реально побачити верхню межу).
Щоб реалізувати мережу TCP з нульовою копією, моїм першим підходом було використання SO_ZEROCOPY
сокета:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Однак це призводить до send: Bad address
.
Трохи погуглившись, другий мій підхід полягав у використанні труби, а splice()
потім 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;
}
Тим НЕ менше, результат той же: vmsplice: Bad address
.
Зауважте, що якщо я заміню виклик на vmsplice()
або send()
функцію, яка просто друкує дані, на які вказує buf
(або send()
без MSG_ZEROCOPY
), все працює нормально; Таким чином, дані є доступними для простору користувачів, але vmsplice()
/ send(..., MSG_ZEROCOPY)
дзвінки, здається, не можуть їх обробити.
Що я тут пропускаю? Чи можливий спосіб передачі TCP з нульовою копією з адресою простору користувача, отриманою від драйвера ядра через dma_mmap_coherent()
? Чи є інший підхід, який я міг би використати?
ОНОВЛЕННЯ
Таким чином , я пірнув трохи глибше в sendmsg()
MSG_ZEROCOPY
шлях в ядрі, і виклик , який в кінцевому рахунку зазнає невдачі це get_user_pages_fast()
. Цей виклик повертається, -EFAULT
оскільки check_vma_flags()
знаходить VM_PFNMAP
прапор, встановлений у vma
. Цей прапор, мабуть, встановлюється, коли сторінки відображаються в просторі користувача за допомогою remap_pfn_range()
або dma_mmap_coherent()
. Мій наступний підхід - знайти інший шлях до mmap
цих сторінок.