Архитектурные предложения в приложении для Linux - PullRequest
1 голос
/ 23 декабря 2008

Я немного программировал под Windows, но теперь мне нужно написать свое первое приложение для Linux.

Мне нужно поговорить с аппаратным устройством, использующим UDP. Мне нужно отправлять 60 пакетов в секунду размером 40 байт. Если я отправлю менее 60 пакетов в течение 1 секунды, произойдет что-то плохое. Данные для пакетов могут занять некоторое время для генерации. Но если данные не готовы к отправке по сети, можно отправлять те же данные, которые были отправлены в прошлый раз. Компьютер настроен только для командной строки и будет запускать только эту программу.

Я не очень разбираюсь в Linux, поэтому я надеялся получить общее представление о том, как можно настроить приложение для удовлетворения этих требований. Я надеялся на такой ответ, как:

Создайте 2 потока, один для отправки пакетов, а другой для вычислений.

Но я не уверен, что это так просто (возможно, так и есть). Может быть, было бы более надежным создать своего рода демона, который просто отправлял пакеты из общей памяти или что-то в этом роде, и затем другое приложение выполняло бы вычисления? Если это решение с несколькими процессами, какой механизм коммуникации вы бы порекомендовали? Есть ли способ, которым я могу дать своему приложению более высокий приоритет, чем обычно, или что-то подобное?

PS: чем больше пуленепробиваемый, тем лучше!

Ответы [ 10 ]

3 голосов
/ 23 декабря 2008

Я реализовал похожий проект: простое программное обеспечение на встроенном компьютере Linux, отправляющее CAN-сообщения с регулярной скоростью.

Я бы пошел на подход двух потоков. Придайте потоку-отправителю чуть более высокий приоритет и заставьте его отправлять тот же самый блок данных еще раз, если другой поток медленно вычисляет эти блоки.

60 пакетов UDP в секунду в большинстве систем (включая встроенные) довольно расслаблены, поэтому я не стал бы потратить много сил на оптимизацию обмена данными между потоками и отправкой пакетов.

На самом деле, я бы сказал: будь проще ! Если вы действительно единственное приложение в системе, и у вас есть разумный контроль над этой системой, вам нечего получить от сложной схемы IPC и других хитростей. Упрощение поможет вам создать лучший код с меньшим количеством дефектов и меньшим временем, что фактически означает больше времени для тестирования.

1 голос
/ 23 декабря 2008

Я не совсем понял, насколько сложна ваша скорость 60 пакетов / сек. Заполняет ли пакет 60 пакетов в секунду? Или требуется четкий интервал в 1/60 секунды между каждым пакетом?

Это может быть немного не в тему, но другая важная проблема - это то, как вы настраиваете Linux. Я бы сам использовал ядро ​​Linux в режиме реального времени и отключил бы все ненужные сервисы. С другой стороны, существует реальный риск того, что ваше приложение пропустит пакет в какой-то момент, независимо от выбранной вами архитектуры.

В любом случае, два потока должны хорошо работать.

1 голос
/ 23 декабря 2008

Предложение использовать пару потоков звучит так, как будто это сработает, если бремя выполнения вычислений не слишком велико.

Вместо использования pipe(), как предложено Cogsy, я склонен использовать мьютекс для блокировки фрагмента памяти, который вы используете для хранения вывода вашего потока вычислений - используя его как область передачи между потоками .

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

Когда ваш передающий поток был готов отправить пакет, он "попытался бы" заблокировать мьютекс. Если он получит блокировку, возьмите копию буфера передачи и отправьте ее. Если он не получит блокировку, отправьте последнюю копию.

Вы можете контролировать приоритет вашего процесса, используя «nice» и задавая отрицательную корректирующую цифру, чтобы придать ей более высокий приоритет. Обратите внимание, что вам нужно будет сделать это как суперпользователь (либо с правами суперпользователя, либо с помощью 'sudo'), чтобы иметь возможность указывать отрицательные значения.


edit: Забыл добавить - этот хороший учебник по pthreads в linux. Также описано использование мьютексов.

1 голос
/ 23 декабря 2008

Две предложенные вами темы будут работать. Если между ними есть pipe (), то ваш вычислительный поток может предоставлять пакеты по мере их генерирования, в то время как ваш поток сообщений использует select (), чтобы посмотреть, есть ли какие-либо новые данные. Если нет, то он просто отправляет последний из своего кэша.

Возможно, я немного упростил вопрос ...

0 голосов
/ 08 января 2009

Спасибо всем, я буду использовать любой совет. Я хотел бы выбрать больше ответов, чем 1!

Для любознательных. У меня нет источника для устройства, его система заблокирована. Я еще не провёл достаточно тестов, чтобы увидеть, насколько разборчивы 60 пакетов в секунду. Вот и все их ограниченные документы говорят: «60 пакетов в секунду». Из-за природы устройства, пакетные пакеты будут плохой вещью. Я думаю, что мне удастся избежать отправки более 60 секунд, чтобы компенсировать случайные пропущенные пакеты.

0 голосов
/ 28 декабря 2008

Я согласен с подходом двух потоков. У меня также было бы два статических буфера и общее перечисление. Отправляющий поток должен иметь эту логику.

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

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

0 голосов
/ 28 декабря 2008

Следуйте давним рекомендациям Unix: сделайте это простым и модульным, отделите действия и позвольте ОС выполнять за вас как можно больше работы.

Многие ответы здесь находятся на правильном пути, но я думаю, что они могут быть еще проще:

  • Используйте два отдельных процесса: один для создания данных и записи их в стандартный вывод, а другой для чтения данных из стандартного ввода и их отправки. Пусть базовые библиотеки ввода / вывода обрабатывают буферизацию потока данных между процессами, и пусть ОС работает с управлением потоками.

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

  • Затем заставьте отправителя читать данные из стандартного ввода - вы можете перенаправить данные из файла, например, "отправитель <текстовые данные" </p>

  • Затем создайте источник данных и направьте его вывод отправителю, например, "производитель | отправитель".

Теперь у вас есть возможность создавать новых производителей по мере необходимости, не связываясь со стороной отправителя. Этот ответ предполагает одностороннюю связь.

Если вы ответите максимально просто, вы добьетесь большего успеха, особенно если вы еще не очень хорошо разбираетесь в системах на базе Linux / Unix. Это прекрасная возможность изучить новую систему, но не переусердствуйте. Легко переходить к сложным ответам, когда инструменты доступны, но зачем использовать бульдозер, когда простого кельмы достаточно. Мьютекс, семафоры, разделяемая память и т. Д. - все это полезно и доступно, но добавляет сложности, которая вам может и не понадобиться.

0 голосов
/ 23 декабря 2008

Я бы держался подальше от потоков и использовал процессы и (возможно) сигналы и файлы. Поскольку вы говорите, что «плохие вещи» могут случиться, если вы не отправите их, вам следует избегать блокировок и условий гонки. И это проще сделать с отдельными процессами и данными, сохраненными в файлах.

Что-то вроде одного процесса, сохраняющего данные в файл, затем переименовывающего его и запускающего заново. А другой процесс берет текущий файл и отправляет его содержимое раз в секунду.

В отличие от Windows, вы можете копировать (перемещать) файл, пока он открыт.

0 голосов
/ 23 декабря 2008

Работают два потока, вам нужно убедиться, что вы заблокировали свою общую структуру данных, чтобы поток-отправитель не видел ее на полпути обновления.

60 в секунду не кажется слишком сложным.

Если вы действительно беспокоитесь о планировании, установите политику планирования потока отправки на SCHED_FIFO и mlockall () его память. Таким образом, ничто не сможет остановить отправку пакета (они могут все же выйти поздно, хотя, если в это же время отправляются другие данные)

Должно быть какое-то допуск устройства - 60 пакетов в секунду - это нормально, но каково допуск устройства? 20 в секунду? Если устройство выйдет из строя, если оно его не получит, я отправлю их в три раза быстрее, чем требуется.

0 голосов
/ 23 декабря 2008

Я опубликовал этот ответ, чтобы проиллюстрировать совершенно иной подход к «очевидному», в надежде, что кто-то обнаружит, что он именно то, что ему нужно. Я не ожидал, что он будет выбран как лучший ответ! Относитесь к этому решению с осторожностью, потому что есть потенциальные опасности и проблемы параллелизма ...

Вы можете использовать системный вызов 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++)
                ;
}
...