Available Languages?:

OSA : Учебник. Урок 5 - Расширенный приоритетый режим

Тема

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

Теория

OSA поддерживает три различных режима обработки приоритетов: неприоритетный, обычный (по умолчанию) и расширенный. Режим выбирается один раз на этапе компиляции и не может быть изменен в ходе выполнения программы. Для выбора режима в файле osacfg.h должна быть определена константа OS_PRIORITY_LEVEL. Рассмотрим, в чем их особенности, достоинства и недостатки.

Неприоритетный режим

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

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

Обычный приоритетный режим

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

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

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

Итак, обычный приоритетный режим имеет два основных недостатка:

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

Расширенный приоритетный режим

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

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

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

Недостатками этого метода являются:

  1. более низкая, чем в обычном приоритетном режиме, скорость работы планировщика (см. Скоростные характеристики);
  2. на каждую задачу выделяютя дополнительно по два байта ОЗУ, определяющие важность и порядковый номер в очереди планировщика.

Теперь рассмотрим работу программы под управлением данных режимов на практике.

Проект

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

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

Следуя инструкциям, описанным в первом уроке, создадим проект в папке "c:\tutor\t5" и назовем его "tutor5.c".

Конфигурирование проекта (osacfg.h)

Запустим утилиту OSAcfg_Tool.exe и создадим новый файл конфигурации для нашего проекта (кнопка "Browse…" и выбор папки проекта в открывшемся диалоговом окне).

Теперь в секции "System" устанавливаем параметр "Tasks" = 4 (у нас будут четыре задачи). Далее нам нужно сказать системе, что мы будем использовать задержки в программе, т.к. без задержек мы на глаз не уследим за происхоящим. Для этого нам нужно включить таймеры задач: в секции "Timers" устанавливаем галочку напротив пункта "Task timers".

Также нам понадобится бинарный семафор: установим количество семафоров в "1" и зададим имя семафору BS_SIGNAL. Это имя будет использоваться нами для обращения к семафору в программе.

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

После этого жмем кнопку "Save" в нижней части экрана, читаем сообщение, что файл успешно сохранен, давим "OK" и выходим из программы конфигуратора по кнопке "Exit".

Убеждаемся, что файл OSAcfg.h создан в папке "c:\tutor\t5".

Описание задач

Для начала опишем задачи, управляющие светодиодами:

void Task_LED1 (void)
{
    for (;;)
    {
        OS_Bsem_Wait(BS_SIGNAL);  // Дожидаемся семафора
        PIN_LED1 ^= 1;           // Изменяем состояние первого светожиода
    }
}

Аналогичным образом будут выглядеть задачи, управляющие светодиодами LED2 и LED3.

Терь опишем задачу, сигнализирующую семафором:

void Task_Signal (void)
{
    for (;;)
    {
        OS_Bsem_Set(BS_SIGNAL);
        OS_Delay(200);
    }
}

Эта задача каждые 200 мс будет сигналить семафором.

В функцию main(), как всегда, добавляем сервисы инициализации, создания задач и запуска планировщика:

void main (void)
{
    // Инициализация периферии
    init();
 
    // Инициализация системы
    OS_Init();
 
    // Создание задач
    OS_Task_Create(3, Task_LED1);
    OS_Task_Create(3, Task_LED2);
    OS_Task_Create(3, Task_LED3);
    OS_Task_Create(3, Task_Signal);
 
    // Разрешение прерываний
    OS_EI();
 
    // Запуск планировщика
    OS_Run();
}

Полный текст программы

Итак, полный текст нашей программы теперь будет выглядеть так:

#include <htc.h>
#include <osa.h>
 
 
 
//------------------------------------------------------------------------------
// Задаем биты конфигурации:
//   - внутренний RC-генератор
//   - отключаем WDT
//   - отключаем низковольтное программирование
//   - отключаем функцию отладки
//------------------------------------------------------------------------------
 
__CONFIG(INTIO & WDTDIS & PWRTEN & MCLRDIS & LVPDIS & UNPROTECT & BORDIS
               & IESODIS & FCMDIS & DEBUGDIS);
 
 
//------------------------------------------------------------------------------
//  Определеяем выводы для управления светодиодами
//------------------------------------------------------------------------------
 
#define PIN_LED1    RD0
#define PIN_LED2    RD1
#define PIN_LED3    RD2
 
//------------------------------------------------------------------------------
//  Параметры таймера:
//  - прескейлер = 4,
//  - постскейлер = 1,
//  - предел счета = 250
//
//  Тактовая частота контроллера = 4 МГц.
//
//  Период возникновения прерывания по TMR2 получается
//  равным 4 * 1 * 250 * Tcyc = 1 ms
//
//------------------------------------------------------------------------------
 
#define PR2_CONST       250-1
#define TMR2_PRS        1                           // prs = 4
#define TMR2_POST       0                           // post = 1
#define T2CON_CONST     (TMR2_POST<<3) | TMR2_PRS
 
 
//******************************************************************************
//  Прерывание. Возникает каждую мс
//******************************************************************************
 
void interrupt isr (void)
{
    if (TMR2IF)
    {
        OS_Timer();
        TMR2IF = 0;
    }
}
 
 
 
//******************************************************************************
//  Функции-задачи
//******************************************************************************
 
void Task_LED1 (void)
{
    for (;;)
    {
        OS_Bsem_Wait(BS_SIGNAL);  // Дожидаемся семафора
        PIN_LED1 ^= 1;           // Изменяем состояние первого светожиода
    }
}
 
//-----------------------------------------------------------------------------
 
void Task_LED2 (void)
{
    for (;;)
    {
        OS_Bsem_Wait(BS_SIGNAL);  // Дожидаемся семафора
        PIN_LED2 ^= 1;           // Изменяем состояние второго светожиода
    }
}
 
//-----------------------------------------------------------------------------
 
void Task_LED3 (void)
{
    for (;;)
    {
        OS_Bsem_Wait(BS_SIGNAL);  // Дожидаемся семафора
        PIN_LED3 ^= 1;           // Изменяем состояние третьего светожиода
    }
}
 
//-----------------------------------------------------------------------------
 
void Task_Signal (void)
{
    for (;;)
    {
        OS_Bsem_Set(BS_SIGNAL);
        OS_Delay(200);
    }
}
 
//******************************************************************************
//  Инициализация периферии
//******************************************************************************
 
void init (void)
{
    //------------------------------------------------------------------------------
    //  Настройка портов I/O
    //------------------------------------------------------------------------------
 
    PORTA = 0;
    PORTB = 0;
    PORTC = 0;
    PORTD = 0;
 
    TRISA = 0;
    TRISB = 0;
    TRISC = 0;
    TRISD = 0;
 
    //------------------------------------------------------------------------------
    //  Настройка таймера 2
    //------------------------------------------------------------------------------
 
    PR2 = PR2_CONST;
    T2CON = T2CON_CONST | 0x04;
 
    //------------------------------------------------------------------------------
    //  Настройка прерываний
    //------------------------------------------------------------------------------
 
    PIR1 = 0;
    PIR2 = 0;
    INTCON = 0;
 
    TMR2IE = 1;         // Разрешаем прерываие по TMR2
    PEIE = 1;           // Разрешаем периферийные прерывания
                        // Глобальный бит разрешения прерываний будет
                        // установлен непосредственно перед запуском
                        // планировщика в функции main()
 
}
 
//******************************************************************************
//  main
//******************************************************************************
 
void main (void)
{
    // Инициализация периферии
    init();
 
    // Инициализация системы
    OS_Init();
 
    // Создание задач
    OS_Task_Create(3, Task_Signal);
    OS_Task_Create(3, Task_LED1);
    OS_Task_Create(3, Task_LED2);
    OS_Task_Create(3, Task_LED3);
 
    // Разрешение прерываний
    OS_EI();
 
    // Запуск планировщика
    OS_Run();
}
 
 
//******************************************************************************
//  end of file
//******************************************************************************

Работа программы

Неприоритетный режим

Запустим еще раз конфигуратор OSAcfg_Tool.exe, загрузим в него файл c:\tutor5\osacfg.h и в секции "System" выберем в поле "Priority level" пункт "Disabed". Сохряняем и выходим. Убеждаемся, что в файле osacfg.h появилась строка:

#define OS_PRIORITY_LEVEL  OS_PRIORITY_DISABLED

Соберем проект (Ctlr+F10) и прошьем его в контроллер. Мы увидим, что мигает только один светодиод, которым управляет задача Task_LED1. Рассмотрим подробнее, как работает программа в этом режиме. Для удобства на диаграммах рядом с именем задачи показан ее приоритет.

Начало. При старте все светодиоды погашены, а семафор сброшен (запрещен). Готовой к выполнению является только задача Task_Signal, все остальные ожидают семафор.

Шаг 1. Первой планировщиком просматривается задача Task_Signal, т.к. она была создана первой. Он видит, что она готова к выполнению и передает ей управление. После завершения ее работы семафор BS_SIGNAL установлен в "1".

Шаг 2.

Задача Task_Signal переведена в режим ожидания (OS_Delay), но три задачи, усправляющие светодиодами, становятся готовыми к выполнению. Планировщик берет для проверки следующую по очереди задачу (т.е. Task_LED1), видит, что она готова, и передает ей управление. Вспомним, что сервис OS_Bsem_Wait после того, как задача получает управление, сбрасывает семафор. Задача Task_LED1 изменяет состояние светодиода LED1.

Шаг 3.

После того, как Task_LED1 отработала и переведена в режим ожидания (сервисом OS_Bsem_Wait), семафор вновь сброшен, следовательно, задачи Task_LED2 и Task_LED3 опять переводятся в режим ожидания. Таким образом, на данный момент нет ни одной готовой к выподлнению задачи. И так будет до тех пор, пока не закончится время ожидания в задаче Task_Signal (в нашем примере 100 мс). Задачи и дальше будут проверяться по кругу. Сначала Task_LED2:

Затем Task_LED3, Task_Signal, Task_LED1, снова Task_LED2, снова Task_LED3 и т.д.

Шаг 4.

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

Шаг 5.

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

Шаг 6.

Семафор вновь установлен, и задачи Task_LED1, Task_LED2 и Task_LED3 опять готовы к выполнению. Первой по очереди планировщиком проверяется задача Task_LED1, она и получает управление, т.к. является готовой. Семафор BS_SIGNAL сбрасывается, а свтодиод LED1 меняет свое состояние.

Как видим, шаги 1 и 2 идентичны шагам 5 и 6. И дальше программа будет идти по кругу, а задачи Task_LED2 и Task_LED3 так и не получат управления.

Обычный приоритетный режим

Запустим конфигуратор OSAcfg_Tool.exe, загрузим в него файл c:\tutor5\osacfg.h и в секции "System" выберем в поле "Priority level" пункт "Normal". Сохряняем и выходим. Убеждаемся, что в файле osacfg.h появилась строка:

#define OS_PRIORITY_LEVEL  OS_PRIORITY_NORMAL

Для разнообразия расставим задачам разные приоритеты (в функции main()):

    // Создание задач
    OS_Task_Create(7, Task_Signal);  // Самый низкий приоритет
    OS_Task_Create(5, Task_LED1);
    OS_Task_Create(2, Task_LED2);    // Самый высокий приоритет
    OS_Task_Create(4, Task_LED3);

Соберем проект (Ctlr+F10) и прошьем его в контроллер. Мы увидим, что мигает только один светодиод, которым управляет задача Task_LED2. Рассмотрим подробнее, как работает программа в этом режиме.

Начало.

При старте все светодиоды погашены, а семафор сброшен (запрещен). Готовой к выполнению является только задача Task_Signal, все остальные ожидают семафор.

Шаг 1.

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

Единственной готовой задачей на данный момент является Task_Signal. И хоть она имеет низший приоритет, именно она получит управление, т.к. больше готовых задач нет. Выполнившись, она установила семафор BS_SIGNAL и перейдет в режим ожидания на 100 мс (OS_Delay).

После выполнения задачи очередь сдвигается так, что первой на проверку будет стоять следующая за выполнившейся, а только что выполнившаяся отправляется в конец очередь. Порядок очередь при этом не меняется, т.е., например, задача Task_LED2 всегда будет идти за Task_LED1, а Task_LED3 - всегда за Task_LED2 И т.д. Смысл циклического сдвига будет пояснен ниже (в Эксперименте 3).

Шаг 2.

Планировщик вновь просматривает все 4 задачи, начиная на этот раз со следующей за только что выполнившейся Task_Signal, т.е. с Task_LED1. На данный момент готовы 3 задачи: Task_LED1, Task_LED2 и Task_LED3. Планировщик сравнивает их приоритеты (помним, что 0 - высший приоритет, 7 - низший), вычисляет, что наивысший приоритет из готовых имеет задача Task_LED2, и именно ей передается управление. Эта задача в свою очередь сбрасывает семафор (это делается автоматически сервисом OS_Bsem_Wait) и изменяет состояние светодиода LED2.

После выполнения задачи снова циклически сдвигаются в очереди проверки так, что Task_LED2 оказывается в самом конце очереди. Первой становится та задача, что была за Task_LED2, т.е. Task_LED3.

Шаг 3.

Планировщик просматривает все задачи, начиная с Task_LED3, и видит, что готовых к выполнению нет (т.к. все ждут семафор, а Task_Signal выдерживает паузу). Поэтому управление ни одна задача не получает.

После проверки все задачи циклически сдвигаются на 1, и следующая проверка начнется с задачи Task_Signal. Далее они будет проверятся и проверяться, пока не закончится время задержки в задаче Task_Signal (т.е. в течение 100 мс).

Шаг 4.

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

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

Шаг 5.

Этот шаг получается идентичным шагу 2. Т.е. снова из трех готовых к выполнению задач будет выбрана та, чей приоритет выше, т.е. Task_LED2. Она снова сбросит семафор и снова изменит состояние светодиода.

Шаг 6.

Дальше идем по кругу, т.е. шаг 6 идентичен шагу 3.

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

Эксперимент 1. Перестановка приоритетов

Только что мы наблюдали, что при работе программы мигает только один светодиод, которым управляет задача Task_LED2. Попробуем поменять приоритеты задач:

    // Создание задач
    OS_Task_Create(7, Task_Signal);  // Самый низкий приоритет
    OS_Task_Create(5, Task_LED1);
    OS_Task_Create(2, Task_LED2);
    OS_Task_Create(0, Task_LED3);    // Самый высокий приоритет

Теперь самый высокий приоритет у задачи Task_LED3. Запустим программу и убедимся, что теперь мигает только светодиод LED3. Принцип работы тот же, что и описан выше, только на шагах 2 и 5 планировщиком для запуска выбирается не Task_LED2, а Task_LED3 как более приоритетная.

Эксперимент 2. Равные приоритеты

Как поведет себя программа, если все приоритеты будет одинаковыми?

    // Создание задач
    OS_Task_Create(3, Task_Signal);  // Все приоритеты равны
    OS_Task_Create(3, Task_LED1);
    OS_Task_Create(3, Task_LED2);
    OS_Task_Create(3, Task_LED3);

Соберем эту программу (Ctrl+F10) и запустим на выполнение. Теперь мы видим, что работает только светодиод LED1. Почему? Потому что здесь мы наткнулись на недостаток обычного приоритетного режима, описанный выше: "получение управления только одной задачей из всех равноприоритетных, ожидающих одно и то же событие". Дело в том, что при сравнении приоритетов готовых задач играет роль последеовательность, в которой планировщик рассматривает задачи и преимущество отдается той, которая была рассмотрена первее. В нашем случае получается, что после работы Task_Signal (т.е. сразу после того момента, как был установлен семафор), первой в очереди оказывается именно Task_LED1. При равных приоритетах Task_LED1, Task_LED2 и Task_LED3 преимущество отдается той, которая была рассмотрена первой, т.е. Task_LED1. Этого недостатка лишен расширенный приоритетный режим.

Эксперимент 3. Замена OS_Bsem_Wait на OS_Yield

Попробуем теперь при равных приоритетах задач заменить сервис OS_Bsem_Wait на OS_Yield. Т.е. отменяем ожидание несколькими задачами одного и того же условия.

void Task_LED1 (void)
{
    for (;;)
    {
        //OS_Bsem_Wait(BS_SIGNAL);
        OS_Yield();
        PIN_LED1 ^= 1;           // Изменяем состояние первого светожиода
    }
}

по аналогии перепишем так же задачи Task_LED2 и Task_LED3. Соберем программу и запустим ее на выполнение. Мы увидим, что теперь загорелись все три светодиода. На самом деле они не горят постоянно, а очень быстро мигают (ожидания семафора-то теперь нет, следовательно нет паузы в 100мс, выдерживаемой между установками семафора).

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

Теперь проделаем еще один небольшой эксперимент: одной из задач понизим приоритет:

    // Создание задач
    OS_Task_Create(3, Task_Signal);  // Все приоритеты равны
    OS_Task_Create(3, Task_LED1);
    OS_Task_Create(5, Task_LED2);    // А у этой задачи приоритет ниже
    OS_Task_Create(3, Task_LED3);

Соберем программу и запустим ее. Теперь мы видим, что горят (т.е. очень быстро мигают) только светодиоды LED1 и LED3, а LED2 потушен. Почему? Потому что здесь мы наткнулись на еще один недостаток обычного приоритетного режима: "блокировка низкоприоритетных задач высокоприоритетными". Пока есть готовые к выполнению высокоприоритетные задачи (а в нашем случае они есть), низкоприоритеные не смогут получить управление. Этого недостатка лишен расширенный приоритетный режим.

Расширенный приоритетный режим

Вернем программу в исходное состояние, т.е. расставим всем задачам равные приоритеты и восстановим вызовы сервисов OS_Bsem_Wait, убрав вызовы OS_Yield.

Запустим утилиту OSAcfg_tool.exe и в секции "System" выберем в поле "Priority level" пункт "Extended". Сохряняем и выходим. Убеждаемся, что в файле osacfg.h появилась строка:

#define OS_PRIORITY_LEVEL  OS_PRIORITY_EXTENDED

Теперь собираем программу (Ctrl+F10) и прошиваем контроллер. О чудо! Мигают все три светодиода. Разберемся, как работает программа в этом режиме. Обращу ваше внимание на то, что в данном случае рядом с именем задачи будет стоять не приоритет, а ее важность (как уже было написано, эта характеристика меняется в ходе выполнения программы; чем цифра больше, тем важность выше).

Начало.

При старте все светодиоды погашены, а семафор сброшен (запрещен). Готовой к выполнению является только задача Task_Signal, все остальные ожидают семафор.

Шаг 1. Здесь все так же, как и о бычном приоритетном режиме, т.к. готова только одна задача Task_Signal. Она устанавливает семафор и переходит в режим ожидания задержки.

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

Шаг 2.

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

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

Шаг 3.

Нет ни одной готовой к выполнению программы, т.к. семафор сброшен, а задача, его устанавливающая, выдерживает паузу. Однако обратим внимание на важности задач Task_LED2 и Task_LED3: они теперь увеличились на 1, повысив вероятность быть избранными планировщиком в следующий раз.

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

Шаг 4.

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

Обратим внимание на то, как изменится очередь после выполнения этого шага: Task_LED2 и Task_LED3 остались на своих местах, т.к. они являются предшествующими только что выполненной; Task_Signal ставится в конец очереди, а все последующие (т.е. Task_LED1) сдвигаются на 1 позицию ближе к началу очереди.

Шаг 5.

На данный момент готовы три задачи: Task_LED1, Task_LED2 и Task_LED3. Но теперь планировщик руководствуется не приоритетами, как в обычном приоритетном режиме, а другой характеристикой - важностью. Задача Task_LED1 отпадает сразу, т.к. имеет самую маленькую важность. А из задач, имеющих одинаковые значения важности, будет выбрана та, которая была рассмотрена планировщиком первее, т.е. Task_LED2. Она же сбросит семафор и изменит состояние светодиода LED2.

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

Шаг 6.

Таким образом, на данный момент самой важной является та из трех ожидающих семафор, которая еще не получала управление, т.е. Task_LED3.

Шаг 7.

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

Так же на схеме видно, что Task_LED3, стоящая в очереди до только что выполненной задачи, остается на своем месте, а Task_LED1 и Task_LED2 сдвигаются на одну позицию ближе к началу очереди. Task_Signal, как обычно, перемещается в конец очереди.

Шаг 8.

Итак снова готовы к выполнению все три задачи, ожидающие семафор. Но самой важной из них является Task_LED3, она и получит управление, сбросит семафор и изменит состояние своего светодиода.

После выполнения ее важность делается минимальной, а сама задача ставится в конец очереди. Важности задач Task_LED1 и Task_LED2 увеличиваются.

Шаг 9.

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

Таким образом, мы видим, что в расширенном приоритетном режиме нет блокированных задач.

Эксперимент 1. Изменение приоритетов

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

    // Создание задач
    OS_Task_Create(3, Task_Signal);  //
    OS_Task_Create(1, Task_LED1);    // Высший приоритет
    OS_Task_Create(3, Task_LED2);    // Средний приоритет
    OS_Task_Create(5, Task_LED3);    // Нисший приоритет

Соберем программу (Ctlr+F10) и прошьем ее в контроллер. Теперь мы видим, что светдиод LED1 мигает быстрее всех, а LED3 - медленнее всех. Это означает, что все задачи получают управление, но те, которые имеют более высокий приоритет, получают его чаще.

Не вдаваясь особо в детали, скажу, что важность увеличивается не на 1, как схематически показано в примере, а на число пропорциональное приоритету. Т.е. Важность задачи Task_LED1 будет расти быстрее, чем у Task_LED2.

Заключение

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

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

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

 
osa/tutorial/tutor5.txt · Последние изменения: 19.01.2011 16:18 От osa_chief
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki