Я довольно много программировал в Windows, но теперь мне нужно написать свое первое приложение для Linux.
Мне нужно поговорить с аппаратным устройством, используя UDP. Мне нужно отправить 60 пакетов в секунду размером 40 байт. Если я отправлю менее 60 пакетов в течение 1 секунды, произойдут неприятности. Создание данных для пакетов может занять некоторое время. Но если данные не готовы к отправке по сети, можно отправить те же данные, которые были отправлены в прошлый раз. Компьютер настраивается только из командной строки и будет запускать только эту программу.
Я мало что знаю о Linux, поэтому надеялся получить общее представление о том, как можно настроить приложение для удовлетворения этих требований. Я надеялся на такой ответ:
Сделайте 2 потока, один для отправки пакетов, а другой для вычислений.
Но я не уверен, что это так просто (возможно, это так). Может быть, было бы надежнее создать своего рода демона, который просто отправлял пакеты из общей памяти или чего-то еще, а затем другое приложение выполняло вычисления? Если это решение для нескольких процессов, какой механизм связи вы бы порекомендовали? Можно ли как-нибудь придать своему приложению больший приоритет, чем обычно или что-то подобное?
PS: чем более пуленепробиваемый, тем лучше!





Два потока, как вы предложили, подойдут. Если между ними есть pipe (), то ваш вычислительный поток может предоставлять пакеты по мере их генерации, в то время как ваш коммуникационный поток использует select (), чтобы увидеть, есть ли какие-либо новые данные. Если нет, то он просто отправляет последний из своего кеша.
Возможно, я немного упростил проблему ...
Я сделал аналогичный проект: простое программное обеспечение на встроенном компьютере Linux, отправляющее сообщения CAN с постоянной скоростью.
Я бы выбрал двухпоточный подход. Дайте отправляющему потоку немного более высокий приоритет и заставьте его снова отправлять тот же блок данных, если другой поток медленно вычисляет эти блоки.
60 UDP-пакетов в секунду - это довольно непринужденно для большинства систем (включая встроенные), поэтому я бы не стал тратить много времени на оптимизацию обмена данными между потоками и отправку пакетов.
На самом деле я бы сказал: будь проще! Если вы действительно единственное приложение в системе, и у вас есть разумный контроль над этой системой, вам нечего получить от сложной схемы IPC и других уловок. Сохранение простоты поможет вам создавать лучший код с меньшим количеством дефектов и за меньшее время, что на самом деле означает больше времени для тестирования.
Предложение использовать пару потоков звучит так, как будто это поможет, если бремя выполнения вычислений не слишком велико.
Вместо использования pipe(), как предлагает Cogsy, я был бы склонен использовать мьютекс для блокировки фрагмента памяти, который вы используете для хранения вывода вашего вычислительного потока, используя его в качестве области передачи между потоками.
Когда ваш вычислительный поток готов к выводу в буфер, он захватит мьютекс, выполнит запись в буфер передачи и освободит мьютекс.
Когда ваш поток передачи был готов отправить пакет, он «пытался» заблокировать мьютекс. Если он получит блокировку, возьмите копию буфера передачи и отправьте ее. Если он не получит блокировку, отправьте последнюю копию.
Вы можете контролировать приоритет вашего процесса, используя "nice" и указав отрицательное значение корректировки, чтобы придать ему более высокий приоритет. Обратите внимание, что вам нужно будет сделать это как суперпользователь (либо как root, либо с помощью sudo), чтобы иметь возможность указывать отрицательные значения.
edit: Забыл добавить - это - хороший учебник по pthreads в Linux. Также описывается использование мьютексов.
Я не совсем понял, насколько сложны ваши требования в 60 пакетов / сек. Удовлетворяет ли требование пакет из 60 пакетов в секунду? Или требуется резкий интервал в 1/60 секунды между каждым пакетом?
Это может быть немного не по теме, но еще одна важная проблема - это то, как вы настраиваете Linux. Я бы сам использовал ядро Linux в реальном времени и отключил все ненужные службы. В противном случае существует реальный риск того, что ваше приложение когда-нибудь пропустит пакет, независимо от того, какую архитектуру вы выберете.
В любом случае, два потока должны работать хорошо.
Я опубликовал этот ответ, чтобы проиллюстрировать совершенно другой подход к «очевидному», в надежде, что кто-то обнаружит, что это именно то, что им нужно. Я не ожидал, что это будет лучший ответ! Относитесь к этому решению с осторожностью, потому что есть потенциальные опасности и проблемы с параллелизмом ...
Вы можете использовать системный вызов setitimer (), чтобы SIGALRM (сигнал тревоги) отправлялся вашей программе через определенное количество миллисекунд. Сигналы - это асинхронные события (немного похожие на сообщения), которые прерывают выполняющуюся программу, чтобы позволить обработчику сигнала запуститься.
Набор обработчиков сигналов по умолчанию устанавливается ОС при запуске вашей программы, но вы можете установить собственный обработчик сигналов с помощью sigaction ().
Итак, все, что вам нужно, - это один поток; используйте глобальные переменные, чтобы обработчик сигнала мог получить доступ к необходимой информации и отправить новый пакет или повторить последний пакет, если это необходимо.
Вот вам пример:
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
int ticker = 0;
void timerTick(int dummy)
{
printf("The value of ticker is: %d\n", ticker);
}
int main()
{
int i;
struct sigaction action;
struct itimerval time;
//Here is where we specify the SIGALRM handler
action.sa_handler = &timerTick;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
//Register the handler for SIGALRM
sigaction(SIGALRM, &action, NULL);
time.it_interval.tv_sec = 1; //Timing interval in seconds
time.it_interval.tv_usec = 000000; //and microseconds
time.it_value.tv_sec = 0; //Initial timer value in seconds
time.it_value.tv_usec = 1; //and microseconds
//Set off the timer
setitimer(ITIMER_REAL, &time, NULL);
//Be busy
while(1)
for(ticker = 0; ticker < 1000; ticker++)
for(i = 0; i < 60000000; i++)
;
}
Я думал, что общий совет состоит в том, чтобы обработчики сигналов были очень простыми? I / O (printf и отправка пакетов UDP), безусловно, следует избегать?
Да, потому что может возникнуть странное взаимодействие, если обработчик запускается, пока основная программа выполняет ввод-вывод, а также из-за проблем с переносимостью. Но пока это работает в целевой системе, это не вызывает большого беспокойства.
Да, и вы всегда можете временно заблокировать сигналы в основной программе, если собираетесь сделать что-то рискованное.
Разве вы не можете использовать 'pause ()', чтобы позволить операциям взять на себя управление до следующего сигнала. Ожидание в ожидании - обычно ужасное решение.
Ожидание - НИКОГДА не решение. Я просто использовал его для имитации занятости фактически, что, по-видимому, и будет делать программа спрашивающего.
Два потока будут работать, вам нужно будет убедиться, что вы заблокировали свою общую структуру данных, чтобы поток отправки не видел ее на полпути через обновление.
60 в секунду звучит не слишком сложно.
Если вас действительно беспокоит планирование, установите политику планирования отправляющего потока на SCHED_FIFO и mlockall () его память. Таким образом, ничто не сможет остановить его отправку пакета (они все равно могут выйти поздно, хотя, если в то же время по сети отправляются другие вещи)
Должен быть некоторый допуск устройства - 60 пакетов в секунду нормально, но каков допуск устройства? 20 в секунду? Если устройство выйдет из строя, если оно не получит его, я бы отправил их в три раза быстрее, чем требуется.
Я бы держался подальше от потоков и использовал процессы и (возможно) сигналы и файлы. Поскольку вы говорите, что "плохие вещи" могут произойти, если вы не отправите сообщение, вам нужно избегать блокировок и условий гонки. И это проще сделать с отдельными процессами и данными, сохраненными в файлах.
Что-то вроде того, как один процесс сохраняет данные в файл, затем переименовывает его и запускает заново. А другой процесс выбирает текущий файл и отправляет его содержимое раз в секунду.
В отличие от Windows, вы может копируете (перемещаете) файл, пока он открыт.
Следуйте давним лучшим практикам Unix: сохраняйте простоту и модульность, разделяйте действия и позвольте ОС делать за вас столько работы, сколько возможно.
Многие ответы здесь на правильном пути, но я думаю, что они могут быть еще проще:
Используйте два отдельных процесса: один для создания данных и записи их в стандартный вывод, а другой для чтения данных из стандартного ввода и их отправки. Позвольте базовым библиотекам ввода-вывода обрабатывать буферизацию потока данных между процессами, а операционная система должна заниматься управлением потоками.
Сначала создайте базовый отправитель, используя цикл таймера и буфер фиктивных данных, и отправьте его на устройство с нужной частотой.
Затем заставьте отправителя читать данные из stdin - вы можете перенаправить данные из файла, например "отправитель <текстовые данные"
Затем создайте производитель данных и направьте его вывод отправителю, например "производитель | отправитель".
Теперь у вас есть возможность создавать новых производителей по мере необходимости, не связываясь со стороной отправителя. Этот ответ предполагает одностороннюю связь.
Делая ответ как можно более простым, вы добьетесь большего успеха, особенно если вы еще не очень хорошо владеете системами на основе Linux / Unix. Это отличная возможность изучить новую систему, но не переусердствуйте. Когда инструменты доступны, легко переходить к сложным ответам, но зачем использовать бульдозер, когда простого шпателя достаточно. Мьютексы, семафоры, разделяемая память и т. д. Полезны и доступны, но добавляют сложности, которые могут вам и не понадобиться.
Я согласен с двухпотоковым подходом. У меня также было бы два статических буфера и общее перечисление. У отправляющего потока должна быть такая логика.
loop
wait for timer
grab mutex
check enum {0, 1}
send buffer 0 or 1 based on enum
release mutex
end loop
Другой поток будет иметь такую логику:
loop
check enum
choose buffer 1 or 0 based on enum (opposite of other thread)
generate data
grab mutex
flip enum
release mutex
end loop
Таким образом, у отправителя всегда есть действующий буфер на все время отправки данных. Только поток генератора может изменить указатель буфера, и он может это сделать, только если отправка не выполняется. Кроме того, перестановка перечисления никогда не должна занимать столько циклов, чтобы очень долго задерживать поток отправителя с более высоким приоритетом.
Спасибо всем, я воспользуюсь всеми советами. Хотел бы я выбрать больше ответов, чем 1!
Для любопытных. У меня нет источника для устройства, это пропиетарная заблокированная система. Я еще не провел достаточно тестов, чтобы увидеть, насколько требовательны 60 пакетов в секунду. Все их ограниченные документы говорят «60 пакетов в секунду». Однако из-за природы устройства пачки пакетов будут плохой вещью. Думаю, мне удастся посылать более 60 в секунду, чтобы компенсировать случайные пропущенные пакеты.
Поскольку вы не будете знать, прибывают ли пакеты UDP (это ненадежный протокол), как узнать, что пришли 60 +/- пакетов, которые вы отправили? Что произойдет, если за секунду будет отправлен 61 пакет? А как насчет 59? Другие числа?