Что такое критическая секция
FreeRTOS: мьютексы и критические секции
Мьютексы.
Рисунок 4. После окончания работы таск В возвращает жетон и в результате другие таски могут получить доступ к ресурсу.
Для создания мьютекса используется специальная API функция:
Которая возвращает созданный мьютекс, или NULL, если недостаточно памяти.
Следующий код показывает механизм работы с мьютексом:
Критические секции.
Что если по ходу работы логики Вашего кода, необходим короткий участок кода, выполнение которого не должно быть прервано переключением на другой таск (примером такого участка может послужить запись какого-либо значения в порт). Для этого в FreeRTOS используются специальные макросы:
Во время выполнения этого участка кода не может быть произведено переключение контекста (т.е. переключение на другую задачу), но могут приходить прерывания на версиях FreeRTOS с поддержкой вложенных прерываний, но не все, а только те у которых приоритет выше константы configMAX_SYSCALL_INTERRUPT_PRIORITY.
Другой подход построения критических секций — приостановка работы планировщика. Разница лишь в том, что критическая секция, построенная на макросах, защищает выполняемый участок кода от совместного доступа других тасков, и прерываний, а критическая секция, построенная на приостановке планировщика — только от других тасков.
Для этого используют 2 API функции:
При этом значение, которое возвращает функция xTaskResumeAll(); указывает на необходимость выполнения принудительного переключения контекста с помощью taskYIELD();
Инверсия приоритетов и тупики.
В прошлой статье я вскользь упомянул о gatekeeper task, как о решении проблемы инверсии приоритетов и попадания тасков в тупик. Здесь я бы более подробно хотел описать, что это такое.
Инверсия приоритетов — это ситуация при которой, Task A имеющий более высокий приоритет, чем Task B ожидает завершения его работы т.к. он первым захватил „жетон» (мьютекс).
Пожалуй, на этом все, интересно было бы услышать поправки по всему циклу, дабы скорректировать статьи, и в дальнейшем ссылаться на них.
Для дальнейшего изучения я бы, прежде всего, посоветовал прочитать оригинальное руководство «Using the FreeRTOS real time kernel», информация из которого использовалась при написании статей, а также документацию на официальном сайте.
Алгоритмы синхронизации
Критическая секция
Здесь критический участок для каждого процесса – от операции «Обнаруживает, что хлеба нет» до операции «Возвращается в комнату» включительно. В результате отсутствия взаимоисключения мы из ситуации «Нет хлеба» попадаем в ситуацию «Слишком много хлеба». Если бы этот критический участок выполнялся как атомарная операция – «Достает два батона хлеба», то проблема образования излишков была бы снята.
Время | Студент 1 | Студент 2 | Студент 3 |
---|---|---|---|
17-05 | Приходит в комнату | ||
17-07 | Обнаруживает,что хлеба нет | ||
17-09 | Уходит в магазин | ||
17-11 | Приходит в комнату | ||
17-13 | Обнаруживает, что хлеба нет | ||
17-15 | Уходит в магазин | ||
17-17 | Приходит в комнату | ||
17-19 | Обнаруживает,что хлеба нет | ||
17-21 | Уходит в магазин | ||
17-23 | Приходит в магазин | ||
17-25 | Покупает 2 батона на всех | ||
17-27 | Уходит из магазина | ||
17-29 | Приходит в магазин | ||
17-31 | Покупает 2 батона на всех | ||
17-33 | Уходит из магазина | ||
17-35 | Приходит в магазин | ||
17-37 | Покупает 2 батона на всех | ||
17-39 | Уходит из магазина | ||
17-41 | Возвращается в комнату | ||
17-43 | |||
17-45 | |||
17-47 | Возвращается в комнату | ||
17-49 | |||
17-51 | |||
17-53 | Возвращается в комнату |
Сделать процесс добывания хлеба атомарной операцией можно было бы следующим образом: перед началом этого процесса закрыть дверь изнутри на засов и уходить добывать хлеб через окно, а по окончании процесса вернуться в комнату через окно и отодвинуть засов. Тогда пока один студент добывает хлеб, все остальные находятся в состоянии ожидания под дверью ( таблица 5.2).
Время | Студент 1 | Студент 2 | Студент 3 |
---|---|---|---|
17-05 | Приходит в комнату | ||
17-07 | Достает два батона хлеба | ||
17-43 | Приходит в комнату | ||
17-47 | Приходит в комнату |
В общем случае структура процесса, участвующего во взаимодействии, может быть представлена следующим образом:
Оставшаяся часть этой лекции посвящена различным способам программной организации пролога и эпилога критического участка в случае, когда очередность доступа к критическому участку не имеет значения.
Национальная библиотека им. Н. Э. Баумана
Bauman National Library
Персональные инструменты
Критическая секция
При параллельном программировании одновременный доступ к общим ресурсам может привести к неожиданному или ошибочному поведению программы, поэтому части программы, в которых имеется доступ к общему ресурсу защищаются специальной секцией. Этот защищенная секция называется критической секцией. Она не может выполняться несколькими процессами. Как правило, критическая секция обращается к общему ресурсу, например структуре данных, периферийному устройству или сетевому соединению, которые не будут работать корректно в контексте нескольких одновременных доступов. [Источник 1]
Содержание
Критическая секция
Необходимость Критической секции
Различные коды или процессы могут иметь одну и ту же переменную или другие ресурсы, которые необходимо прочитать или записать, но результаты которых зависят от порядка, в котором происходят действия. Например, если переменная «x» должна быть прочитана процессом A, и процесс B должен записывать ту же переменную «x» в то же время, процесс A может получить либо старое, либо новое значение «x».
В таких случаях важна критическая секция. В приведенном выше случае, если A необходимо прочитать обновленное значение «x», выполнение процесса A и процесса B одновременно может не дать требуемых результатов. Чтобы предотвратить это, переменная «х» защищена критической секцией. Сначала, B получает доступ к секции. Когда B заканчивает запись значения, A получает доступ к критической секции, и переменная «x» может быть прочитана.
Контролируя, какие переменные изменяются внутри и снаружи критической секции, предотвращается одновременный доступ к общей переменной. Критическая Секция обычно используется, когда многопоточная программа должна обновлять несколько взаимосвязанных переменных без отдельного потока, вносящего конфликтующие изменения в эти данные. Так же критическая секция может использоваться для обеспечения того, чтобы доступ к общему ресурсу, например к принтеру, мог быть доступен только одним процессом за раз.
Реализация критической секции
Чтобы избежать любого изменения управлением процессора внутри критической секции в унифицированных процессорных системах можно просто, отключить прерывания при входе в критический раздел, избегая системных вызовов, которые могут вызвать переключение контекста внутри раздела и восстановления прерываний с возвратом в первоначальное состояние на выходе. Любой выполняющийся поток, войдя в любую критическую секцию в любой точке системы, с данной реализацией предотвратит предоставление времени но обработку процессором(CPU) любому другому потоку, и, следовательно, от входа в любую другую критическую секцию или, любой другой код до тех пор, пока исходный процесс не покинет свою критическую секцию.
Этот грубый подход можно улучшить с помощью семафоров. Чтобы войти в критическую секцию, поток должен попасть в семафор, который он освобождает при выходе из секции. Другие потоки не могут войти в критическую секцию одновременно с исходным потоком, но могут свободно управлять процессором и выполнять другой код, включая другие критические секции, которые защищены различными семафорами. Блокировка семафора также имеет ограничение по времени, чтобы предотвратить условие взаимоблокировки, при котором шлюз занимается одним процессом в течение бесконечного времени, останавливая другие процессы, которые должны использовать общий ресурс, защищенный критической секцией.
Критические секции уровня ядра
Как правило, критические секции предотвращают миграцию процессов и потоков между процессорами и вытесняющими многозадачностями с помощью прерываний других процессов и потоков.
Критические секции часто позволяют вложение. Вложение позволяет вводить в и выводить из многократных критических секций с небольшими затратами.
Если планировщик прервет текущий процесс или поток в критической секции, то планировщик будет или позволять выполнение процесса или потока до конца в критической секции, или назначит процесс или поток для другой части. Планировщик не будет мигрировать процесс или поток к другому процессору, и это не назначит другой процесс или поток,для выполнения, в то время пока текущий процесс или поток находится в критической секции.
Так как критические секции могут выполняться только на процессоре, в который они были введены, синхронизация требуется только в пределах выполняющего процессора. Это позволяет входить в и выходить из критической секции за почти нулевые затраты. Никакая межпроцессорная синхронизация не требуется, только синхронизация потока команд. Большинство процессоров обеспечивают необходимое количество синхронизации простым актом прерывания текущего режима выполнения. Это позволяет критическим секциям в большинстве случаев быть не чем счетчиком входов в критическую секцию на процессор.
Повышение производительности включает в себя выполнение ожидающих прерываний при выходе из всех критических разделов и возможность запуска планировщика при выходе из всех критических секций.Кроме того, ожидающие прерывания могут быть переданы другим процессорам для выполнения.
Критические секции не должны использоваться как долговременный фиксирующий примитив. Критические секции должны быть достаточно короткими, чтобы их можно было вводить, выполнять и выходить без каких-либо прерываний, возникающих из аппаратного обеспечения и планировщика.
Критические секции уровня ядра являются основой проблемой локаута программного обеспечения.
Критические секции в разных языках
Тут имеется схожий с C++ API, но под другими именами.
Критические секции представлены классом System.Threading.Monitor, вместо ::EnterCriticalSection() есть Monitor.Enter(object), а вместо ::LeaveCriticalSection() Monitor.Exit(object), где object – это любой объект C#. Т.е. каждый объект где-то в потрохах CLR (Common Language Runtime) имеет свою собственную критическую секцию либо заводит ее по необходимости. Типичное использование этой секции выглядит так:
Примечание: CLR – это не только C#, все это применимо и к другим языкам, использующим CLR.
MC++ (управляемый C++)
Delphi
Практически все, что верно для C++, верно и для Delphi. Критические секции представлены объектом TCriticalSection. Собственно, это такая же обертка.
Кроме того, в Delphi присутствует специальный объект TMultiReadExclusiveWriteSynchronizer с названием, говорящим само за себя.
Критические секции
Автор: Павел Блудов
The RSDN Group
Источник: RSDN Magazine #6-2004
Опубликовано: 14.03.2005
Исправлено: 10.12.2016
Версия текста: 1.2
Введение
Именно тут приходят на помощь критические секции. Перепишем наш пример.
Работа с критическими секциями
Что же происходит внутри критических секций и как они устроены? Прежде всего, следует отметить, что критические секции – это не объекты ядра операционной системы. Практически вся работа с критическими секциями происходит в создавшем их процессе. Из этого следует, что критические секции могут быть использованы только для синхронизации в пределах одного процесса. Теперь рассмотрим критические секции поближе.
Структура RTL_CRITICAL_SECTION
СОВЕТ Не стоит экономить на критических секциях. Много cэкономить все равно не получится. В поле RecursionCount хранится количество повторных вызовов ::EnterCriticalSection() из одной и той же нити. Действительно, если вызвать ::EnterCriticalSection() из одной и той же нити несколько раз, все вызовы будут успешны. Т.е. вот такой код не остановится навечно во втором вызове ::EnterCriticalSection(), а отработает до конца. Действительно, критические секции предназначены для защиты данных от доступа из нескольких ниток. Многократное использование одной и той же критической секции из одной нити не приведет к ошибке. Это вполне нормальное явление. Следите, чтобы количество вызовов ::EnterCriticalSection() и ::LeaveCriticalSection() совпадало, и все будет хорошо. Поле OwningThread содержит 0 для никем не занятых критических секций или уникальный идентификатор нити-владельца. Это поле проверяется, если при вызове ::EnterCriticalSection() поле LockCount после увеличения на единицу оказалось больше нуля. Если OwningThread совпадает с уникальным идентификатором текущей нити, то RecursionCount просто увеличивается на единицу и ::EnterCriticalSection() возвращается немедленно. Иначе ::EnterCriticalSection() будет дожидаться, пока нить, владеющая критической секцией, не вызовет ::LeaveCriticalSection() необходимое количество раз. Поле LockSemaphore используется, если нужно подождать, пока критическая секция освободится. Если LockCount больше нуля, и OwningThread не совпадает с уникальным идентификатором текущей нити, то ждущая нить создает объект ядра (событие) и вызывает ::WaitForSingleObject( LockSemaphore ). Нить-владелец, после уменьшения RecursionCount, проверяет его, и если значение этого поля равно нулю, а LockCount больше нуля, то это значит, что есть как минимум одна нить, ожидающая, пока LockSemaphore не окажется в состоянии «случилось!». Для этого нить-владелец вызывает ::SetEvent(), и какая-то одна ( только одна ) из ожидающих ниток пробуждается и получает доступ к критическим данным. WindowsNT/2k генерирует исключение, если попытка создать событие не увенчалась успехом. Это верно как для функций ::Enter/LeaveCriticalSection(), так и для ::InitializeCriticalSectionAndSpinCount() с установленным старшим битом параметра SpinCount. Но только не в WindowsXP. Разработчики ядра этой операционной системы поступили по-другому. Вместо генерации исключения, функции ::Enter/LeaveCriticalSection(), если не могут создать собственное событие, начинают использовать заранее созданный глобальный объект. Один на всех. Таким образом, в случае катастрофической нехватки системных ресурсов, программа под управлением WindowsXP ковыляет какое-то время дальше. Действительно, писать программы, способные продолжать работать после того, как ::EnterCriticalSection() сгенерировала исключение, чрезвычайно сложно. Как правило, если программистом и предусмотрен такой поворот событий, то дальше вывода сообщения об ошибке и аварийного завершения программы дело не идет. Как следствие, WindowsXP игнорирует старший бит поля LockCount.
|