Available Languages?:

Общие рекомендации

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

  1. пытаются использовать ОСРВ там, где это не нужно;
  2. любой фрагмент программы, который возможно написать с использованием сервисов ОСРВ, пишут, используя сервисы ОСРВ.

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

Не используйте ОСРВ там, где это не нужно
Не злоупотребляйте использованием сервисов ОСРВ
Не забывайте о прерываниях
О глобальных переменных
Как разбить программу на задачи
Как расставлять приоритеты


Не используйте ОСРВ там, где это не нужно

Далеко не все задачи удобно решать с помощью ОСРВ (а в некоторых случаях ОСРВ даже будет мешать). Кроме того, следует помнить, что микроконтроллеры (в особенности те, на которые в первую очередь была ориентирована OSA, т.е. PIC10, PIC12, PIC16) имеют дефицит ресурсов. Некоторые программисты после написания одной-двух программ с использованием ОСРВ так привязываются к ней, что, формулируя для себя очередную задачу еще в проблемно-ориентированных терминах, уже сразу пытаются подогнать эту формулировку под принципы ОСРВ. Т.е., фактически, при проектировании программы пропускается важный этап - выбор инструмента. Это не совсем правильно; вернее - совсем неправильно.

Например, простая задача для PIC10: "пока на входе контроллера есть меандр 1 КГц, горит зеленый светодиод; если частота меандра выходит за пределы допуска +/- 10% - гасим зеленый светодиод и зажигаем красный". Здесь использование ОСРВ совсем не оправдано, более того, просто вредно, поскольку работа планировщика отнимет скоростные ресурсы контроллера и не позволит обеспечить требуемую точность. Тем не менее, некоторые (не буду говорить, кто) создадут 3 независимых задачи: одна следит за меандром, вторая обрабатывает светодиоды, третья следит за первыми двумя - и будут усердно пытаться всю остальную программу оформить и утрамбовать так, чтобы эти три задачи справлялись со своими функциями, причем - в ущерб наглядности текста, прозрачности алгоритма и, наконец, надежности самой программы.

Другой пример - какую-нибудь простую задачу затолкают в малоресурсный контроллер вместе с ОСРВ, на этапе компиляции обнаружат, что памяти (или ROM, или RAM, или и той и другой) не хватает, и начинают вытворять какие-то немыслимые фокусы по оптимизации вплоть до использования машинных кодов в inline-ассемблере (есть такая возможность в HT-PICC), чтобы программу вообще хоть как-то запустить (Михаил, без обид :) ), при этом даже не рассмотрев вариант написания программы без использования ОСРВ.

В ответ на мои рассуждения о ОСРВ и малоресурсных контроллерах мне приводят в пример мою же программу led3, мол, она-то с использованием ОСРВ написана; зачем тогда такой пример, если Вы сами не рекомендуете так делать? Здесь ответ простой. Представьте, что Вы пришли на базар, чтобы купить топор для колки дров. Продавец, демонстрируя свои топоры, одним из их разрубает стальной трос. На топоре при этом - ни зазубрины. Это не значит, что все стальные тросы нужно непременно рубить этими топорами, забыв при этом о специальных для таких целей инструментах, но это кое-что говорит о возможностях этих топоров в работе по дереву. Так же и с этим примером для PIC10. Он приведен не для того, чтобы показать, что любая программа для любого контроллера может и должна быть написана под операционной системой; он приведен для того, чтобы показать, насколько конкретная ОСРВ нетребовательна к ресурсам (в частности, ни jacOS ни Salvo с такой задачей не справятся). Другими словами, этот пример показывает, что само ядро операционной системы будет почти незаметно для программы при использовании контроллера помощнее, например, PIC16F628 (не говоря уже о PIC18 и выше).

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

  • "выполняет возложенные на нее функции";
  • "лишь бы влезла в выбранный контроллер";
  • "использованы продвинутые решения (ОСРВ)".

Важно учитывать также второстепенные:

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

Поэтому, если на этапе проектирования есть сомнения в том, что использование ОСРВ не совсем подходит для решения данной задачи, - лучше работать без нее. Сэкономите и время и нервы.

Есть религиозные войны "Си & асм". Изучение перепалок на эту тему позволяет сделать вывод, что самые ярые "вояки" - это те, которые с противоположной стороной вопроса не очень знакомы. Возможно, на подходе религиозные войны "RTOS & !RTOS". Я предлагаю не участвовать на передовой, ударяя себя пяткой в грудь и крича, что RTOS - сила, а осваивать и ОСРВ, и суперлуп, и прерывания (есть и такой вариант), и выбирать оптимальный для решения конкретной задачи.

(К списку общих рекомендаций)


Не злоупотребляйте использованием сервисов ОСРВ

В присланных мне проектах я также замечал маниакальное использование сервисов ОСРВ везде: где нужно и где не нужно. Складывается такое ощущение, что некоторые программисты рассматривают ОСРВ как язык программирования, просто еще более высокого уровня, чем Си. И как при программировании на Си избегают использования встроенного ассемблера, так и при программировании под ОСРВ избегают использования самого Си. В результате такого подхода исходный текст программы сильно пестрит изобилием сервисных вызовов, которое просто затрудняет чтение программы. Применительно к OSA, возможно, есть и моя вина в том, что некоторые так пишут программы. OSA предоставляет изобилие сервисов для, в общем-то, простых операций, не требующих каких-то сложных пассажей с передачей управления ядру (достаточно посмотреть на сервисы установки/сброса флагов). По существу же, большое количество сервисов - это просто формализация простых операций в терминах конкретной ОС. Применительно к флагам эта формализация звучит так: есть сервисы ожидания флагов, значит должны быть и сервисы установки/сброса флагов (хотя по сути - это простые побитовые операции and и or) и их проверки ( == и != ). Т.е. есть некий объект ОС (флаг), следовательно, ОС должна обеспечить программиста сервисами по всевозможным операциям с объектом. На деле же получается довольно трудночитаемый текст.

Однако, это не единственная причина такого яростного пичкания программы системными сервисами. Тут какой-никакой здравый смысл присутствует. Это вроде как стандартизация, все-таки флаг - объект ОС, и неизвестно, как там нутро этой ОС устроено и как оно его обрабатывает (не все же программисты заглядывают в исходники ОС, даже если они открыты). Есть еще одна причина: мышление в контексте ОСРВ заставляет людей местами необдуманно использовать сервисы ОС там, где это совсем не к месту.

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

    OS_Csem_SetValue(csem, 10);
    do {
        /*...*/
        OS_Csem_Accept(csem);
    } while (OS_Csem_Check(scem));
    i = 10;
    do {
        /*...*/
    } while (--i);

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

Еще могу привести такой пример. В одной из присланных мне программ была подпрограмма приема данных по последовательному интерфейсу (не аппаратному). Там была заведена переменная mask, которая имела только один установленный бит и циклически сдвигалась после приема каждого импульса. А принимаемый байт записывался в системную переменную типа OST_FLAG. В результате код имел несколько комичный вид:

    // CLC - ножка CLOCK
    // DIO - ножка DATA
    // (прим. - В.Т.)
 
    OS_Cond_Wait(CLC);
 
    if (DIO)  OS_Flag_Set_1(data, mask);
    else      OS_Flag_Set_0(data, mask);
 
    mask <<= 1;
    if (!mask) ...

Налицо совершенно бессмысленное использование сервиса OS_Flag_Set_x, которое в данном случае не только не наглядно, но еще и сбивает с толку. Куда нагляднее выглядел бы код:

    OS_Cond_Wait(CLC);
 
    if (DIO)  data |=  mask;
    else      data &= ~mask;
 
    mask <<= 1;
    if (!mask) ...

Еще в одной присланной мне программе человек так увлекся бинарными семафорами, что на них построил всю логику работы программы. У него было под 40 этих семафоров, и в результате он в них запутался, т.к. его программа выглядела уже не как последовательность действий, а как какая-то МДНФ на ПЛМ, которая хоть и выполняет свою функцию, но без пристальнейшего вглядывания имеет совсем непонятную логику работы. И модификация такой программы становится весьма затруднительной (Семен, это я о тебе :) ).

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

Поэтому я подведу итог:

Сервисы ОСРВ должны применяться по принципу "если без них никак", а не по принципу "если можно как-то извернуться и сделать это на сервисах ОС, то надо сделать именно так". Программа не должна выглядеть как номенклатурный список складского учета, где все товары имеют идентификационный код, начинающийся с "OS_", она должна быть наглядной и понятной.

(К списку общих рекомендаций)


Не забывайте о прерываниях

Также распространенной ошибкой при проектировании является отказ от использования кода в прерываниях. Многие программисты пытаются всю программу представить непременно в виде задач ОС, полагая при этом, что прерывание не оформить как отдельную задачу, и, следовательно, внутри него ничего делать нельзя. В большинстве программ, присланных мне, код прерыввания выглядел примерно одинаково:

void interrupt isr (void)
{
    OS_EnterInt();
    if (T0IF)
    {
       T0IF = 0;
       OS_Timer();
    }
    OS_LeaveInt();
}

Разница была только в используемом таймере. Все же остальные обработчики прерываний были вынесены в отдельные задачи и выглядели примерно так:

void Task_Iinterrupt_INTF (void)
{
    for (;;)
    {
        OS_Cond_Wait(INTF);
        INTF = 0;
        /* Далее следует ко добработки прерывания */
    }
}

Неэффективность такого подхода очевидна:

  • во-первых, теряются преимущества использования системы прерываний, а именно - скорость реакции на событие, вывод контроллера из sleep-режима, маскирование;
  • во-вторых, появляются несколько лишних задач, каждая из которых требует ресурсы: память и время (об этом будет сказано чуть ниже в разделе "Как разбить программу на задачи")

ОСРВ призвана упростить процесс проектирования и написания программы, а не заменить какие-то аппаратные модули микроконтроллера (в частности - контроллер прерваний). Поэтому не нужно стесняться использовать систему прерываний, укоряя себя за то, что "это же не по правилам РТОС!". Все по правилам.

В дополнение напомню, что ОСРВ OSA имеет целый набор сервисов для работы внутри прерываний, позволяя вести обмен информацией с остальной программой в терминах ОСРВ.

(К списку общих рекомендаций)


О глобальных переменных

Вопрос использования глобальных переменных довольно спорный. Одни утверждают, что глобальные переменные - это зло, т.к. их наличие усложняет переносимость отдельных функций, а также не исключает возможные коллизии из-за модификаций глобальной переменной в разных модулях и т.д. Другие говорят, что использование глобальных переменных сокращает ресурсозатраты на обмен информацией. Точно сказать, кто прав в этом вопросе, я думаю, нельзя. И те и другие по-своему правы. Нужно рассматривать каждый конкретный случай в отдельности, при этом следует принимать во внимание не только задачу, под которую пишется программа, но еще и специфику используемого контроллера, и особенности используемого компилятора, и сколько программистов трудятся над задачей и т.д. и т.п.

В идеале программа не должна содержать глобальных переменных. В реальности же нам приходится искать компромисс между структурностью программы и наличием свободных ресурсов (памяти и скорости). Создавая программу с помощью ОСРВ, можно, конечно, все обмены данными между задачами делать с помощью объектов ОС: семафоров, сообщений, очередей и пр. (все эти объекты, вообще-то, сами по себе являются глобальными переменными, но они имеют четкую формализацию в терминах используемой ОС, а потому сильно упрощают перенос функций в другие программы и практически исключают коллизии с модификацией переменных разными модулями). Тем не менее, такой подход не всегда оправдан по нескольким причинам:

  1. одна переменная может использоваться сразу несколькими задачами (например, переменная, показывающая текущий режим работы устройства); при каждом изменении такой переменной пришлось бы сообщать об этом сразу нескольким задачам, что накладно по ресурсам;
  2. программа окажется сильно перегруженной системными вызовами, что затруднит ее читабельность;
  3. усложняется логика самой программы;
  4. наконец, сами сервисы требуют под себя какие-то ресурсы, и с увеличением сервисных вызовов у нас уменьшается память под саму программу.

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

(К списку общих рекомендаций)


Как разбить программу на задачи

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

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

void Task_Relay1 (void)
{
    for (;;)
    {
        OS_Bsem_Wait(BS_RELAY1);
        RELAY1 = 1;
        OS_Delay(100);
        RELAY1 = 0;
    }
}

Но что у нас получается на практике? Если программа управляет десятком таких силовых выходов, то у нас заводится десять таких задач. С виду - все красиво и аккуратно. Но не следует забывать, что каждая активная задача в ОСРВ требует несколько байт оперативной памяти, где хранится ее текущее состояние, адрес возврата, ее таймер (все эти параметры хранятся в так называемом дескрипторе задачи). Например, для PIC16 размер дескриптора задачи 5 байт (он может быть и меньше, но мы рассматриваем общий случай), и для десяти задач потребуется 50 байт ОЗУ, что для PIC16 - просто расточительство. Кроме того, еще одна проблема, возникающая при большом количестве задач, - увеличивается время работы планировщика, т.е. то время, за которое планировщик успеет перебрать все задачи, определить, какие из них готовы к выполнению, и из этих готовых найти самую важную (т.е. имеющую высший приоритет).

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

Можно привести еще пример (тоже из реальной жизни): на экран нужно было выводить информацию разного характера. Для этого были созданы 4 задачи, каждая из которых выводила на экран свой параметр. Каждая задача ожидала от головной задачи свое сообщение. В результате, помимо того, что для 4-х задач функции вывода на экран являлись разделяемым ресурсом, так еще и головная задача была озадачена головной болью по работе с четырьмя сообщениями. Кроме того, такой подход накладывает ограничение на содержание выводимых на экран данных, ибо для вывода 5-го параметра потребовалась бы 5-я задача (и 5-е сообщение), для 6-го - 6-я и т.д.

Вторая крайность - это когда программист пытается все, что не является обработкой кнопок, затолкать в одну задачу. Она занимается и приемом даных по USART, и записью данных в EEPROM, и … да чем только не занимается! Зато информацию о кнопках получает извне. Зато есть многозадачность! Бессмысленность такого подхода очевидна (тем не менее, он часто встречается): нет преимущества использования ОСРВ. В чем преимущество параллельности выполнения задач, если задача всего одна? В чем преимущество очередей сообщений, если задача сообщения отсылает сама себе? Какова будет реакция на событие, если задача занимается сразу всем? Про читабельность кода, написанного с таким подходом, я вообще молчу. Единственное, что полезного можно ухватить от ОСРВ в таком случае, - это удобство формирования задержек (OS_Delay - и все!).

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

Для начала приведу очевидный пример: сначала программа была написана под конкретный LCD-индикатор. По прошествии какого-то времени после запуска ее в серию в продаже появился новый менее дорогой и более функциональный индикатор, и руководство фирмы приняло решение следующую версию устройства делать на нем. Очевидно, что если при написании программы все, относящееся к функциям вывода на экран, было вынесено в отдельный модуль (отдельную задачу), то и модификация программы будет минимальной. Т.е. меняем только функции работы с LCD и задачу вывода информации на экран (единственную задачу, которая работает с функциями LCD). Для остальной программы такая замена будет незаметной: она как отсылала свои сообщения в очередь, так и отсылает, а что там вытворяет с ними задача вывода на экран, - это уже их не касается.

Другими словами: если какой-то внешний модуль допускает замену, то управление этим модулем нужно выносить в отдельную задачу. К таким модулям, помимо индикаторов, можно также отнести внешнюю память, протоколы обмена данными, клавиатуру, GPS- и GSM-модули и т.д.

Рассмотрим теперь такой пример. Допустим, программист работает в фирме, которая разрабатывает, скажем, системы доступа: домофоны, кодовые замки, пульты дистанционного управления и пр.

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

Итак, если предполагается в будущем какой-то кусок текущей программы использовать еще где-то, то такой кусок было бы удобно оформить в виде отдельной задачи.

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

При вынесении каких-то функций в отдельную задачу нужно предварительно прикинуть, сможет ли задача при такой организации обеспечить реакцию на событие в течение определенного времени. Это время может быть различным в зависимости от события. Например, если пользователь нажимает на кнопку, то он хочет увидеть реакцию устройства сразу же, и задержка всего в секунду уже недопустима. Если событие - это превышение температуры в комнате на 2 градуса выше какой-то нормы, то тут допустима задержка реакции в 5 секунд или в минуту - разницы особой нет. Т.е. задача должна быть спроектирована таким образом, чтобы ее выполнение не мешало ей же реагировать на события. Например, у нас есть задача, управляющая несколькими силовыми приборами: свет, насосы, обогреватель. Получая сообщение о том, что нужно на 10 секунд включить насос, она в течение этих 10 секунд может оказаться не в состоянии обработать другие приходящие ей сообщения. Если логика работы такой задачей проста, то эту проблему можно обойти, добавив в программу несколько таймеров и булевых переменных. Однако же, если логика посложнее, то тогда, наверное, есть смысл разбить эту задачу на две или более.

В одном из присланных мне проектов был реализован интересный подход, который тоже можно взять на вооружение. В программе было описано около 20 функций-задач. Но в один момент времени могли работать только 4. 3 из них работали постоянно, а 4-я в зависимости от текущего режима все время пересоздавалась. Преимущество такого подхода - скорость работы планировщика (чем меньше активных задач, тем планировщик OSA работает быстрее). Недостаток - нужно очень внимательно следить за создаваемыми/удаляемыми задачами, а так же за тем, чтобы при удалении они не забывали освобождать занимаемые ресурсы.

Попробую подытожить рекомендации. Написанная программа должна обладать следующими свойствами:

  • она должна правильно выполнять возложенные на нее функции. Это свойство к разбиению программы на задачи отношения, в общем-то, не имеет, но если программа не обладает этим свойством, то нет смысла рассматривать все остальные. Поэтому для полноты картины это свойство оставим на первом месте.
  • она должна успевать обрабатывать события в заданный промежуток времени. Следовательно, при проектировании задач должно быть учтено время работы самой задачи.
  • она должна быть дружественной программисту. Не только в том смысле, что программист при написании соблюдал все правила оформления исходных текстов (отступы, пробелы, пустые строки, выровненные комментарии и т.п.), но и в том, чтобы, глядя в программу, можно было бы прочитать алгоритм работы устройства.
  • она должна быть модульной. Т.е., во-первых, иметь возможность быстрой замены одного модуля другим (как в приведенном выше примере про LCD-индикаторы), и во-вторых, оставлять возможность использовать уже написанные куски кода в других приложениях.
  • она не должна содержать слишком много простых задач или слишком мало сложных.

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

(К списку общих рекомендаций)


Как расставлять приоритеты

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

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

Тем не менее, в программах под кооперативными ОСРВ бывают случаи, когда приоритетность выручает.

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

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

В большинстве же случаев в программах под кооперативной ОСРВ приоритеты задачам не нужны. Однако, если все же решили использовать приоритетный режим, то как тогда правильно расставить приоритеты?

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

Высокоприоритетными задачами нужно делать те, которые действительно критичные ко времени реакции. Представьте себе устройство управления автоматическими воротами. Задачами этого устройство являются: прием по радиоканалу команд от ПДУ на открытие/закрытие, управление двигателями дверей, а также - аварийный реверс двигателей, если на пути створок дверей оказалось препятствие. Последняя задача является критичной ко времени. Например, оператор нажал копку на ПДУ - ворота открылись, машина поехала, а оператор нажал кнопку закрытия ворот раньше времени. В результате створки ворот упрутся в кузов автомобиля. Чем быстрее устройство сообразит, что надо дать дверям обратный ход, тем дешевле будут последствия.

Не следует давать высокий приоритет задачам, которые долго выполняются. Например, в программе одновременно оказались готовыми к выполнению задача включения реле и задача преобразования Фурье. Очевидно, что вторая заберет ресурсы контроллера на долгое время, поэтому есть смысл пропустить вперед быструю задачу включения реле, после чего уже можно отдавать управление вычислителю.

Ко всему сказанному можно добавить, что большинство ОСРВ (OSA - не исключение) позволяют менять приоритет задач в ходе выполнения программы, что может в некоторых случаях оказаться полезным. Например, нет смысла давать высокий приоритет задаче записи на flash-карту в фотоаппарате, если сам фотоаппарат находится в режиме просмотра снимков, и запись в данный момент ему нужна только для сохранения каких-то единичных параметров, вроде текущей яркости экрана, или номера последнего просмотренного снимка. И наоборот, задачу записи во flash-память лучше сделать самой приоритетной во время сохранения снимка, пожертвовав при этом скоростью вывода на экран, скоростью обработки кнопок и т.д.

Вот, собственно, основные рекомендации по расстановке приоритетов. В целом эта задача так же не тривиальна, как и разбиение программы на задачи, и в каждом случае нужно решать ее отдельно. Но рекомендации, приведенные выше, помогут упростить этот процесс.

(К списку общих рекомендаций)

 
osa/articles/rtos_usage_2.txt · Последние изменения: 23.02.2010 23:08 От osa_chief
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki