Я использую 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
этим страницам.