Я использую 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
.
После небольшого поиска в Google,Мой второй подход состоял в том, чтобы использовать трубу и 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
этим страницам.