Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694

Ettus UHD (USRP Hardware Driver). Работа с кольцевым буфером

10.09.2025, 11:24. Показов 4987. Ответов 16
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Доброго времени суток, уважаемые форумчане. Возник такой вопрос, что сам я не очень понимаю даже как начать думать чтоб додуматься почему так происходит.

Преамбула такая: есть сервер с двумя Intel Xeon Gold 5415+, 256GB RAM и двумя сетевухами Intel XL710 PCIe x8 10 Gigabit Quad-Port - то есть 4 порта по 10GB каждый. Сервер работает с девайсами которые дают сетевой поток (в максимуме) 153.6e6 x 8 x 4 = 4915200000 байт, собственно этот поток надо прочитать (записать в RAM) для дальнейшей обработки.

Для работы с устройством используется драйвер Ettus UHD (USRP Hardware Driver) родной для этих девайсов предоставляющий набор инструментов для обнаружения устройств, установки параметров и, собственно, чтения/передачи данных. В частности драйвер предоставляет объект потока через который вызывается функция чтения данных из потока.

Собственно непонятка вот в чем:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using sample_t = std::complex<float>;
using Partition = std::pair<std::vector<sample_t *, size_t>;
 
// определенние количества приемных каналов устройства
size_t num = m_device->get_rx_num_channels();
// задаем максимальное (для данного устройства) количество сэмплов (частоту дискртизации)
size_t sample_rate = m_device->get_master_clock_rate(); // == 153600000;
 
// инициализация приемного потока устройства
/* здесь задаются параметры потока, в данном случае не суть какие */
uhd::streamargs_t sargs = ...;
// получаем поток 
uhd::rx_streamer::sptr stream = m_device->get_rx_stream(sargs);
 
// выделяем память под приемный буфер
/* здесь покажу проще: не буду строить весь колцевой буфер, покажу только одно его звено,
также в реальном коде используется numa.h и память выделяется 
в соотвествии с ядром на котором крутится поток чтения с устройства.*/
std::vector<sample_t> data(sample_rate * num);
 
// далее разбираем буфер на указатели так, как их принимает recv:
/*recv принимает массив указателей на начало буферов для каждого канала устройства
в моем случае у устройства может быть открыто до 4-х каналов*/
std::vector<sample_t *> buffs;
for (size_t i = 0; i < num; ++i) {
    buffs.push_back(&data[i * sample_rate]);
}
 
// далее разбираем буферы на партиции 
/* размер партиции соотвествует максимальному количеству cэмплов на пакет
и определяется драйвером */
size_t psize = stream->get_max_num_samps();
size_t sr = sample_rate;
 
std::vector<Partition> partitions;
while (sr) {
    Partition part{};
    for (size_t chan = 0; chan < num; ++i) {
        part.first.push_back(buffs[chan] + sample_rate - sr);
    }
 
    if (sr >= psize) {
        part.second = psize;
        sr -= psize;
    } else {
        part.second = sr;
        sr = 0;
    }
 
    partitions.pus_back(part);
}
 
/* дальнейший код выполняется в выделенном потоке чтения
и фиксирован на одном ядре*/
 
uhd::rx_metadata_t err;
// вариант 1: вычитываем с устройства все сэмплы за раз
size_t readed = 0;
readed = streamer->recv(buffs, sample_rate, err);
// в этом случае recv возвращает меньше чем запрошено через sample_rate,
// а в err записывается ERROR_CODE_OVERFLOW, то есть сервер не успевает
// вычитать все данные с устройства
 
// вариант 2:
readed = 0;
for (size_t i = 0; i < partitions.size() && readed < sample_rate && err.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE; ++i) {
    readed += stream->recv(partitions[i].first, partitions[i].second, err);
}
// результат аналогичен первому варианту
 
// вариант 3:
for (size_t i = 0; i < partitions.size() && readed < sample_rate && err.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE; ++i) {
    readed += stream->recv(partitions[i % 16].first, partitions[i % 16].second, err);
}
// в этом случае все работает идеально readed == sample_rate, err.error_code() == ERROR_CODE_NONE
// но за исключением того факта что данные перетираются в начале буфера, то есть в конце концов
// имеем в начале буфера 16 последних полученных партиций
Понятно что естесственным решением будет выделить короткий буфер читать в него по третьему варианту и организовать параллельный поток
который будет перекладывать данные из короткого буфера в основной, но (!!!) будет ли это работать (в натуре пока не эксперемнтировал) по-хорошему нужно (да и в принципе хочется) понять почему так происходит: какая разница передаю я в recv все партиции или только 16? При этом при увеличении количества партиций (более 64) или при увеличении размера партиции (более 2e+12) снова появляется переполнение.
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
10.09.2025, 11:24
Ответы с готовыми решениями:

Работа с кольцевым буфером
Здравствуйте, пытаюсь написать программу для usb осциллографа Hantek6204bc, понял, что если читать...

Очередь с кольцевым буфером
Добрый день. Решаю вобщем одну задачку, но сомневаюсь в правильности и оптимальности кода. ...

Создание и работа с кольцевым списком
Здраствуйте, нужна помощь с созданием кольцевого списка, а также выполнение задания с ним....

16
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
11.09.2025, 10:18
Когда вы делаете один большой вызов recv(), драйвер пытается обработать все данные сразу и буфер переполняется. Частые вызовы recv() не дают переполняться буферу + сетевая карта чаще генерирует прерывания и ядро ОС чаще планирует ваш поток.
0
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
11.09.2025, 12:16  [ТС]
andrey_f, если бы так, вариант №2 работал бы лучше чем вариант №1, а он работает также.
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
11.09.2025, 12:37
Вы передаете все партиции, скорее всего драйвер обрабатывает их как единый большой запрос, что приводит к тем же проблемам, что и с первым вариантом.
0
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
11.09.2025, 13:30  [ТС]
andrey_f, возможно, тогда драйвер должен где-то в недрах условно "склеивать" адреса, исходники драйвера открыты, но расковырять что там на самом низком уровне происходит трудновато: вариантов устройств множество и функция чтения множество раз переопределена, однако видно что в конечном итоге драйвер получает буфер как указатель на указатель на void и, теоретически, может их как-то кешировать. Однако сейчас у меня рабочий вариант такой что каждый нексус (звено кольцевого буфера) выделяется отдельным небольшим куском (4 канала по 2000 сэмплов) нексус передается в recv целиком и до 80 нексусов в кольце работает стабильно, от 96 и выше начинаются проблемы. Память выделяется динамически, так что "законных" способов её склеить нет, хотя не увидев в исходниках драйвера как именно он формирует запрос к сетевым буферам точно сказать ничего не могу, возможно внутри он перезарзмечает буферы под свои нужды, но на уровне описания и внешних интерфейсов не предъявляет никаких требований к разбивке, лишь в примерах идущих в комплекте с драйвером есть использование функции virtual size_t uhd::rx_streamer::get_max_num_samps(void) const = 0
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
11.09.2025, 14:13
Драйвер работает с DMA-буферами сетевой карты. Каждый вызов recv() соответствует одному или нескольким DMA-транзакциям. Сетевая карта имеет ограниченное количество дескрипторов DMA (обычно 1024-4096 (по данным Google)). Каждый буфер, передаваемый в recv(), требует одного или нескольких дескрипторов.
Цитата Сообщение от Annemesski Посмотреть сообщение
от 96 и выше начинаются проблемы
Дескрипторы заканчиваются. Так же небось фрагментация памяти начинается из-за большого кол-ва буферов...

Добавлено через 24 минуты
Как вы память выделяете для всего этого? Каждый раз запрос к аллокатору делаете?
1
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
11.09.2025, 14:35  [ТС]
Цитата Сообщение от andrey_f Посмотреть сообщение
Каждый раз запрос к аллокатору делаете?
при инициализации устройства создается заданное количество нексусов, на каждый нексус отдельно выделяется память через numa_alloc_onnode - по сути это mmap обернутый политиками гарантирующими что физически память будет мапится на RAM непосредственно связанную с физическим процом, кроме этого по сути ничем не отличается от malloc. Далее при старте потока память прогревается и далее в цикле вызывается recv и сама память никак не перевыделяется и не изменяется, меняются только указатели.
Цитата Сообщение от andrey_f Посмотреть сообщение
Драйвер работает с DMA-буферами сетевой карты.
Да в эту сторону тоже копаю: вручную выставлено по 4 очереди Tx/Rx на каждый адаптер и прерывания распределены равномерно по ядрам (так чтобы шина PCIe на которой стоит сетевой адаптер непосредственно была связана с процом на ядре которого крутится поток чтения). Также сама шина разогнана
Code
1
# setpci -v -d 8086:1572 e6.b=2e
8086:1572 - это адрес шины на которой сетевухи стоят - это все заметно улучшило производительность.
Цитата Сообщение от andrey_f Посмотреть сообщение
Каждый буфер, передаваемый в recv(), требует одного или нескольких дескрипторов.
Вот сейчас копаю на тему /etc/security/limits.conf
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
11.09.2025, 14:49
У вас сколько дескрипторов на текущий момент?
Bash
1
ethtool -g eth0
Может имеет смысл по максимуму их проставить?
Цитата Сообщение от Annemesski Посмотреть сообщение
Вот сейчас копаю на тему /etc/security/limits.conf
/etc/sysctl.conf еще это можно тоже посмотреть

Добавлено через 4 минуты
Цитата Сообщение от Annemesski Посмотреть сообщение
при инициализации устройства создается заданное количество нексусов, на каждый нексус отдельно выделяется память через numa_alloc_onnode - по сути это mmap обернутый политиками гарантирующими что физически память будет мапится на RAM непосредственно связанную с физическим процом, кроме этого по сути ничем не отличается от malloc. Далее при старте потока память прогревается и далее в цикле вызывается recv и сама память никак не перевыделяется и не изменяется, меняются только указатели.
если использовать архитектуру с memory pool?
1
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
11.09.2025, 15:07  [ТС]
Цитата Сообщение от andrey_f Посмотреть сообщение
Code
1
ethtool -g eth0
Bash
1
2
3
4
5
6
7
8
9
10
11
Current:
RX:                 4096
RX mini:            n/a
RX jumbo:           n/a
TX:                 4096
RX Buf Len:         n/a
CQE Size:           n/a
TX Push:            off
RX Push:            off
TX push buf len:    n/a
TCP data split:     n/a
выставляю if-up скриптом
Bash
1
2
# ethtool -G $IFACE rx 4096 tx 4096
# ethtool -L $IFACE combined 4
Цитата Сообщение от andrey_f Посмотреть сообщение
/etc/sysctl.conf еще это можно тоже посмотреть
Да тут тоже настраивал:
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
net.core.rmem_max=33554432
net.core.wmem_max=33554432
net.core.rmem_default=33554432
net.core.wmem_default=33554432
 
net.ipv4.tcp_rmem = 10000000 10000000 10000000
net.ipv4.tcp_wmem = 10000000 10000000 10000000
net.ipv4.tcp_mem = 10000000 10000000 10000000
 
net.ipv4.udp_rmem_min=16384
net.ipv4.udp_wmem_min=16384
net.ipv4.udp_mem = 8388608 12582912 16777216
 
net.core.netdev_max_backlog = 300000
 
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
часть параметров sysctl из рекомендаций к дарйверу.

Цитата Сообщение от andrey_f Посмотреть сообщение
memory pool
изначально так и делал: весь кольцевой буфер одним блобом который разбирал на указатели - работает эквипенисульно с нынешней реализацией.
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
11.09.2025, 15:53
Цитата Сообщение от Annemesski Посмотреть сообщение
Да тут тоже настраивал:
можно еще вот так сделать, чтоб убрать лишний CPU load
Bash
1
2
3
net.ipv4.tcp_sack = 0
net.ipv4.tcp_dsack = 0
net.ipv4.tcp_fack = 0
Цитата Сообщение от Annemesski Посмотреть сообщение
/etc/security/limits.conf
тут что у вас?

Добавлено через 23 минуты
Цитата Сообщение от andrey_f Посмотреть сообщение
Дескрипторы заканчиваются
не мониторили их в процессе выполнения ?
Bash
1
watch -n 1 'ethtool -S eth0 | grep -E "(rx_|tx_)*desc"'
или
Bash
1
watch -n 1 'ethtool -S eth0 | grep -E "(drop|error|fifo)"'
+ с параметрами еще можно поиграться
Bash
1
2
3
ethtool -C eth0 rx-usecs 0 rx-frames 0
ethtool -C eth0 tx-usecs 0 tx-frames 0
ethtool -K eth0 gro off gso off tso off  # Для low-latency
1
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
11.09.2025, 16:41  [ТС]
Цитата Сообщение от andrey_f Посмотреть сообщение
убрать лишний CPU load
заметного эффекта не дало, но оставлю в конфиге

Цитата Сообщение от andrey_f Посмотреть сообщение
тут что у вас?
пока немного:
Bash
1
2
3
4
5
6
7
8
9
10
@usrp - rtprio 99 # это прописывает драйвер при установке 
#usrp - группа в котрой тусует юзер от которого прога запускается
 
@usrp soft priority 90
 
* soft core 0
* hard core unlimited
 
* soft nofile 8192
* hard nofile 65536
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 1028725
max locked memory           (kbytes, -l) 8192
max memory size             (kbytes, -m) unlimited
open files                          (-n) 8192
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 99
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 128000
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited
сейчас медитирую по поводу max locked memory, pipe size и virtual memory
Цитата Сообщение от andrey_f Посмотреть сообщение
не мониторили их в процессе выполнения ?
по маске *desc вывод пустой, (error|drop|info) все по нулям
Цитата Сообщение от andrey_f Посмотреть сообщение
+ с параметрами еще можно поиграться
попробую
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
12.09.2025, 09:50
Цитата Сообщение от Annemesski Посмотреть сообщение
пока немного:
еще можно добавить
Bash
1
2
3
4
5
6
7
8
9
10
11
# Увеличиваем лимиты locked memory
* soft memlock unlimited
* hard memlock unlimited
 
# Увеличиваем лимиты количества файловых дескрипторов
* soft nofile 1000000
* hard nofile 1000000
 
# Увеличиваем лимиты стека
* soft stack unlimited
* hard stack unlimited
прерывания как распределены ?
Bash
1
cat /proc/interrupts | grep eth0
на конкретные ядра можно закрепить
Bash
1
2
3
for irq in $(grep eth0 /proc/interrupts | awk -F: '{print $1}'); do
    echo 0000FF00 > /proc/irq/$irq/smp_affinity
done
0
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
12.09.2025, 10:33  [ТС]
Цитата Сообщение от andrey_f Посмотреть сообщение
Bash
1
ethtool -C eth0 rx-usecs 0 rx-frames 0
Этот параметр я пытался настраивать, но он походу жестко зашит в i40e.ko - стоит значение 50 и никакое другое непринимает: Invalid argument.
Цитата Сообщение от andrey_f Посмотреть сообщение
Bash
1
# Увеличиваем лимиты locked memory
Да, это только что попробовал, и в коде прямо залочил черезе mlock - лучше не стало, хуже тоже.
nofile и stack - заметного эффекта не дали.
pipe size изменить не могу
Цитата Сообщение от andrey_f Посмотреть сообщение
прерывания как распределены ?
Прерывания распределяет if-up скрипт: каждая из 4 очередей интерфейса на одельном ядре того проца на котором шина сетевого адаптера: по счетчикам равномерно растут где-то три, где-то две очереди, пробовал распределять 2, 4, 8 очередей - наилучшая производительность при 4 очередях.

В общем на текущий момент получается успешно вычитать 80 нексусов по 2000 сэмплов - не бог весть что, но с этим уже можно работать, спасибо andrey_f, если будут еще направления куда копнуть, буду очень спасибо
0
 Аватар для andrey_f
884 / 537 / 228
Регистрация: 21.02.2011
Сообщений: 5,705
12.09.2025, 10:42
Цитата Сообщение от Annemesski Посмотреть сообщение
направления куда копнуть
huge pages
0
267 / 199 / 30
Регистрация: 26.11.2022
Сообщений: 869
12.09.2025, 11:23
Если в вашем коде вариант номер три работает то зачем вы вообще в драйвер и настройки полезли? Скормили бы другие буферы.
0
 Аватар для Annemesski
2674 / 1336 / 480
Регистрация: 08.11.2016
Сообщений: 3,694
12.09.2025, 12:15  [ТС]
Цитата Сообщение от Aledveu Посмотреть сообщение
зачем вы вообще в драйвер и настройки полезли?
маловато данных получалось, сейчас хотя бы есть пространство для маневров, что-нибудь с фрагментацией и двойной буферизацией.
0
267 / 199 / 30
Регистрация: 26.11.2022
Сообщений: 869
12.09.2025, 14:10
ОС,если не указано ничего специального, выделяет адресное пространство, а дальше постранично идёт отображение лигичекого адреса на физический. Только на уровне ядра ОС можно выделить нужный блок памяти - линейный, непрерывный с фиксированным положением в памяти - чтобы железка через ПДП могла туда писать. Этим драйвер и занимается.
когда вы выделяете память через malloc - вы получаете двойную буферизацию. сначала железка передаёт в блок памяти внутри ядра, а потом процессор копирует это в вашу память. при этом после malloc никаких гарантий что физическая память была выделена - могло быть выделено только адресное пространство, а ос по мере обращения будет выделять страницы. так что при первом обращении к выделенной памяти могут быть небольшие затыки. обычно на глаз это не заметно.

а у вас может и вообще тройная буферизация - ибо USRP Hardware Driver это же не драйвер сетевухи, а прослойка между сетевым стеком ОС и вашей программой. может там своих буферов полно ))

Также при работе с аппараратурой может быть куча требования по страничному выравниванию буферов. так что память выделяется заранее, чтобы она была доступна, а не лениво выделялась. и в процессе работы никакого динамического выделения/освобождения.

я бы начал с исследования затыков - если они есть, причины, где узкие места и прочее.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
12.09.2025, 14:10
Помогаю со студенческими работами здесь

Ошибка - Driver not loaded Driver not loaded
Собрал драйвер MySQL. Пытаюсь запустить простое приложение - подключиться к базе и выполнить sql -...

QODBC (MS SQL SERVER) . Driver not loaded Driver not loaded
Собрал себе драйвер QODBC. mingw x86. QSqlDatabase::drivers() возвращает (QSQLITE, QMYSQL,...

QSqlError("", "Driver not loaded", "Driver not loaded")
QSqlDatabase sdb = QSqlDatabase::addDatabase(&quot;QSQLITE&quot;); ...

На машине клиента "QSQLITE" выдает: Driver not loaded Driver not loaded
#include &lt;QSqlDatabase&gt; #include &lt;QSqlQuery&gt; #include &lt;QSqlRecord&gt; #include &lt;QSqlError&gt;...

Кольцевой буфер
Здравствуйте, уважаемые пользователи. Мне поставили задачу, которую я один решить не могу и поэтому...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
17
Ответ Создать тему
Новые блоги и статьи
Доступность команды формы по условию
Maks 07.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "СписаниеМатериалов", разработанного в конфигурации КА2. Задача: сделать доступной кнопку (команда формы "ЗавершитьСписание") при. . .
Уведомление о неверно выбранном значении справочника
Maks 06.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "НарядПутевка", разработанного в конфигурации КА2. Задача: уведомлять пользователя, если в документе выбран неверный склад. . .
Установка Qt Creator для C и C++: ставим среду, CMake и MinGW без фреймворка Qt
8Observer8 05.04.2026
Среду разработки Qt Creator можно установить без фреймворка Qt. Есть отдельный репозиторий для этой среды: https:/ / github. com/ qt-creator/ qt-creator, где можно скачать установщик, на вкладке Releases:. . .
AkelPad-скрипты, структуры, и немного лирики..
testuser2 05.04.2026
Такая программа, как AkelPad существует уже давно, и также давно существуют скрипты под нее. Тем не менее, прога живет, периодически что-то не спеша дополняется, улучшается. Что меня в первую очередь. . .
Отображение реквизитов в документе по условию и контроль их заполнения
Maks 04.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеСпецтехники", разработанного в конфигурации КА2. Данный документ берёт данные из другого нетипового документа. . .
Фото всей Земли с борта корабля Orion миссии Artemis II
kumehtar 04.04.2026
Это первое подобное фото сделанное человеком за 50 лет. Снимок называют новым вариантом легендарной фотографии «The Blue Marble» 1972 года, сделанной с борта корабля «Аполлон-17». Новое фото. . .
Вывод диалогового окна перед закрытием, если документ не проведён
Maks 04.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "СписаниеМатериалов", разработанного в конфигурации КА2. Задача: реализовать программный контроль на предмет проведения документа. . .
Программный контроль заполнения реквизитов табличной части документа
Maks 02.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "СписаниеМатериалов", разработанного в конфигурации КА2. Задача: 1. Реализовать контроль заполнения реквизита. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru