Критическая секция в программе предназначена для захвата одной задачей какого-либо ресурса, если к этому ресурсу имеют доступ другие задачи. Таким ресурсом может быть, например, модуль периферии, внешняя память, наконец, процессорное время.
Вообще-то, в кооперативной RTOS редко встречаются конфликтые ситуации, т.к. передачей управления планировщику (а следовательно, и остальным задачам, осуществляется вручную), тем не менее такие ситуации возможны. Поэтому в OSA реализована возможность защиты критических секций.
Для того, чтобы пользоваться сервисами работы с критическими секциями, в OSAcfg.h нужно определить константу OS_ENABLE_CRITICAL_SECTION
Для защиты критических секций есть два сервиса:
OS_EnterCriticalSection - вход в критическую секцию и OS_LeaveCriticalSection - выход из критической секции.
При входе в критическую секцию запрещаются все прерывания (их текущее состояние запоминается во внутренних системных флагах), а задача, вызвавшая сервис, становится единственной готовой к выполнению, вне зависимости от ее приоритета. При этом допустима передача управления планировщику.
Текущее состояние флагов GIEx сохраняется во внутреннюю переменную системы. После чего флаги GIEx сбрасываются. Поэтому, если два раза подряд вызвать сервис OS_EnterCriticalSection, то в первый раз она сохранит текущее состояние влагов GIEx, а во второй - нулевое состояние, т.е. на момент второго вызова флаги уже были сброшены. В этом случае сервис OS_LeaveCriticalSection восстановит флаги, сохраненные во втором вызове, т.е. нулевые. Поэтому следует избегать двойного вызова сервисы входа в критическую секцию.
Использовать задержки OS_Delay, таймеры, а также ожидать события с таймаутами можно только в том случае, если вызов сервиса OS_Timer находится вне прерывания, иначе система будет ждать до бесконечности. (Либо после входа в критическую секцию разрешать прерывания сервисом OS_EI, если логика программы позволяет в данной ситуации оставить прерывания.) Избежать этого можно, продублировав вызов OS_Timer в основном цикле (там же, где вызывается OS_Sched).
void interrupt int_routine (void) { OS_EnterInt(); if (TMR2IF) // Эта часть программы не отработает в { // критической секции TMR2IF = 0; OS_Timer(); // Этот вызов обрабатывается вне критической секции } OS_LeaveInt(); } void Task1 (void) { for (;;) { . . . OS_EnterCriticalSection(); OS_Delay(100); OS_LeaveCriticalSection(); . . . } } void main (void) { OS_Init(); OS_Task_Create(0, Task1); for (;;) { OS_Sched(); if (OS_IsInCriticalSection()) { if (TMR2IF) { TMR2IF = 0; OS_Timer(); // Этот вызов обрабатывается в критической секции } } } }
Все задачи, кроме той, которая вызвала сервис OS_EnterCriticalSection(), блокируются, даже если их приоритет выше и они готовы к выполнению. Поэтому, если после перехода в критическую секцию мы начинаем ждать какое-то событие, которое устанавливается в другой задаче (например, ждем освобождение ресурса, который занят другой задачей), то мы его никогда не дождемся. Избежать этого можно, если сначала дождаться события, а затем войти в критическую секцию.
void Task1 (void) { for (;;) { . . . // Неправильный подход OS_EnterCriticalSection(); OS_Bsem_Wait(BS_USART_FREE); . . . // Правильный подход OS_Bsem_Wait(BS_USART_FREE); OS_EnterCriticalSection(); . . . } }
Сервис | Аргументы | Описание | Свойства |
---|---|---|---|
bool OS_IsInCriticalSection | | Возвращает 1, если одна из задач находится в критической секции. | |
OS_EnterCriticalSection | | Вход в критическую секцию | |
OS_LeaveCriticalSection | | Выход из критической секции |