В предыдущих статьях мы рассмотрели многозадачную модель, и выяснили, что каждая задача является квазинезависимой программой. Хотя задачи во встраиваемых системах имеют определенную степень независимости, это не означает, что они не «знают» друг о друге. Некоторые задачи будут действительно изолированы от других, но взаимодействие и синхронизация между ними является распространенным требованием. Этот механизм является одной из ключевых функций ОСРВ. Диапазон функций может варьироваться в зависимости от ОСРВ, поэтому в этой статье мы рассмотрим общедоступные варианты.
Диапазон функций
Существуют три модели межзадачного взаимодействия и синхронизации:
- Сервисы привязаны к задачам: ОСРВ наделяет задачи атрибутами, которые обеспечивают взаимодействие между ними. В качестве примера рассмотрим сигналы.
- Объекты ядра – автономные средства связи или синхронизации. Примеры: флаги событий, почтовые ящики, очереди / каналы, семафоры и мьютексы.
- Обмен сообщениями — рационализированная схема, в которой ОСРВ позволяет создавать объекты сообщений и осуществлять их передачу от одной задачи к другой или нескольким. Это имеет основополагающее значение для архитектуры ядра, и поэтому такая система называется «ОСРВ с обменом сообщениями».
Механизмы, которые идеально подходят для разных процессов, будут различаться. Их возможности могут пересекаться, поэтому стоит задуматься о масштабируемости этих моделей. Например, если приложению требуется несколько очередей, но только один почтовый ящик, можно реализовать почтовый ящик с очередью на один элемент. Этот объект будет не совсем оптимальным, но весь код работы почтового ящика не будет включен в приложение, и, следовательно, масштабируемость уменьшит объем занимаемой памяти ОСРВ.
Общие переменные или области памяти
Упрощенный подход к взаимодействию между задачами — это наличие в системе переменных или областей памяти, которые доступны для всех задач. Этот подход может быть применим к нескольким процессам, несмотря на свою простоту. Необходимо контролировать доступ. Если переменная является просто байтом, то запись в ней или чтение из нее, вероятно, будет атомарной операцией (т. е. непрерывной), но необходимо проявлять осторожность, если процессор разрешает другие операции на байтах памяти, поскольку они могут быть прерываемыми и может возникнуть проблема синхронизации. Один из способов реализации блокировки / разблокировки — отключить прерывания на короткое время.
Если используется область памяти, все равно нужна блокировка. Допускается использование первого байта в качестве блокирующего флага, с учетом, что архитектура памяти предоставляет атомарный доступ к этому байту. Одна задача загружает данные в область памяти, устанавливает флаг и затем ждет его сброса. Другая задача ждет установки флага, выполняет чтение данных и сбрасывает флаг. Использование отключения прерывания в качестве блокировки менее разумно, так как перемещение всего буфера данных может занять некоторое время.
Такое использования общей памяти похоже на реализацию многих межпроцессорных средств связи в многоядерных системах. В некоторых случаях аппаратная блокировка и / или прерывание встроены в межпроцессорный интерфейс общей памяти.
Сигналы
Сигналы являются одним из самых простых механизмов взаимодействия между задачами, предлагаемых традиционными ОСРВ. Они содержат набор битовых флагов (8, 16 или 32, в зависимости от конкретного применения), который связан с конкретной задачей.
Флаг сигнала (или несколько флагов) может быть установлен любой задачей с помощью логической операции «ИЛИ». Флаг (флаги) может прочитать только задача, которая содержит сигнал. Процесс чтения, как правило, деструктивный, то есть флаги также сбрасываются.
В некоторых системах сигналы реализуются более сложным способом, так что специальная функция, назначенная задачей-владельцем сигнала, автоматически выполняется, когда устанавливаются любые сигнальные флаги. Это устраняет необходимость того, чтобы задача контролировала флаги сама. Это несколько похоже на обработчика прерываний.
Группы флагов событий
Группы флагов событий похожи на сигналы тем, что являются бит-ориентированным средством для взаимодействия между задачами. Аналогичным образом они могут содержать 8, 16 или 32 бита. В отличие от сигналов, они являются независимыми объектами ядра и не «принадлежат» к какой-либо конкретной задаче. Любая задача может устанавливать и сбрасывать флаги событий, используя логические операции «ИЛИ» и «И». Аналогично, любая задача может проверить флаги событий с использованием тех же операций. Во многих ОСРВ можно сделать блокирующий вызов API для комбинации флагов событий. То есть задача может быть приостановлена до тех пор, пока не будет установлена конкретная комбинация флагов событий. Также может быть доступна опция «consume» при проверке флагов событий, которая сбрасывает все флаги.
Семафоры
Семафоры являются независимыми объектами ядра, использующиеся для учета ресурсов. Существует два типа семафоров: двоичные (может иметь только два значения) и общие (неограниченное число значений). Некоторые процессоры поддерживают (атомарные) инструкции, которые облегчают быструю реализацию двоичных семафоров. Двоичные семафоры могут быть реализованы как общие семафоры со значением 1.
Любая задача может попытаться присвоить семафор, чтобы получить доступ к ресурсу. Если текущее значение семафора больше 0 (семафор свободен), значение счетчика уменьшается на 1, следовательно, присвоение будет успешным. Во многих ОС можно применить блокирующий механизм для присвоения семафора. Это означает, что задача может находиться в состоянии ожидания до тех пор, пока семафор не будет освобожден другой задачей. Любая задача может освободить семафор, и тогда значение семафора увеличится.
Почтовые ящики
Почтовые ящики являются независимыми объектами ядра, предоставляющие средства для передачи сообщений. Размер сообщения зависит от реализации, но обычно он фиксирован. Типичные размеры сообщений — от одного до четырех элементов размером с указатель. Как правило, указатель на более сложные данные отправляется через почтовый ящик. Некоторые ядра реализуют почтовые ящики таким образом, что данные просто хранятся в обычной переменной и ядро управляет доступом к ней. Почтовые ящики также можно назвать «exchange», хотя это название сейчас встречается редко.
Любая задача может посылать сообщения в почтовый ящик, который затем заполняется. Если задача пытается отправить сообщение в заполненный почтовый ящик, она получит ответ об ошибке. Во многих ОСРВ можно применить блокирующий механизм для отправки в почтовый ящик. Это означает, что задача будет приостановлена до тех пор, пока сообщение в почтовом ящике не будет прочитано. Любая задача может читать сообщения из почтового ящика, после чего он опустошается. Если задача пытается прочитать из пустого почтового ящика, она получит ответ об ошибке. Во многих ОСРВ можно применить блокирующий вызов для чтения из почтового ящика. Это означает, что задача будет приостановлена до тех пор, пока в почтовом ящике не появится новое сообщение.
Некоторые ОСРВ поддерживают функцию «широковещательной рассылки» (broadcast). Это позволяет отправлять сообщения всем задачам, которые в настоящее время приостановлены при чтении определенного почтового ящика.
Некоторые ОСРВ не поддерживают почтовые ящики вообще. Вместо этого рекомендуется использовать очередь с одним элементом. Это функционально эквивалентно, но несет дополнительные накладные расходы на память и время исполнения.
Очереди
Очереди — независимые объекты ядра, предоставляющие механизм для передачи сообщений. Они немного более гибкие и сложные, чем почтовые ящики. Размер сообщения зависит от реализации, но обычно он фиксирован и ориентирован на слово / указатель.
Любая задача может отправлять сообщения в очередь, и это может повторяться до тех пор, пока очередь не заполнится, после чего любые попытки отправки приведут к ошибке. Длина очереди обычно определяется пользователем при ее создании или настройке системы. Во многих ОСРВ можно применить блокирующий механизм для отправки в очередь. То есть если очередь заполнена, задача может быть приостановлена до тех пор, пока сообщение в очереди не будет прочитано другой задачей. Любая задача может считывать сообщения из очереди. Сообщения читаются в том же порядке, в каком они были отправлены (First in — First out, FIFO). Если задача пытается прочитать из пустой очереди, она получит ответ об ошибке. Во многих ОСРВ можно применить блокирующий механизм для чтения из пустой очереди. То есть если очередь пуста, задача может быть приостановлена до тех пор, пока сообщение не будет отправлено в очередь другой задачей.
Скорее всего в ОСРВ будет механизм для отправки сообщения в начало очереди, это называется «jamming». Некоторые ОСРВ также поддерживают функцию «broadcast». Это позволяет отправлять сообщения всем задачам, приостановленным при чтении очереди.
Кроме того, ОСРВ может поддерживать отправку и чтение сообщений переменной длины. Это дает большую гибкость, но влечет за собой дополнительные накладные расходы.
Многие ОСРВ поддерживают другой тип объекта ядра —«канал» (“pipes”). В сущности, канал аналогичен очереди, но обрабатывает байт-ориентированные данные.
Функциональность очередей не представляет интереса, но следует понимать, что они имеют больше накладных расходов на память и время исполнения, чем почтовые ящики, прежде всего потому, что необходимо сохранить два указателя: начало и конец очереди.
Мьютексы
Мьютексы (взаимоисключающие семафоры) — независимые объекты ядра, которые ведут себя очень похожим на обычные двоичные семафоры образом. Они немного сложнее семафоров и включают концепцию временного владения (ресурса, доступ к которому контролируется). Если задача присваивает мьютекс, только эта же задача может его снова освободить: мьютекс (и, следовательно, ресурс) временно принадлежит задаче.
Мьютексы предоставляются не всеми ОСРВ, но регулярный двоичный семафор довольно просто адаптировать. Необходимо написать функцию «mutex obtain», которая присваивает семафор и назначает идентификатор задачи. Затем дополнительная функция «mutex release» проверяет идентификатор вызывающей задачи и освобождает семафор только в том случае, если она соответствует сохраненному значению, иначе вернет ошибку.
Когда мы работали над нашей собственной операционной системой реального времени ОСРВ МАКС (ранее уже публиковал статьи о ней), наша команда «наткнулась» на блог Колина Уоллса (Colin Walls), эксперта в области микроэлектроники и встроенного ПО компании Mentor Graphics. Статьи показались интересными, переводили их для себя, но, чтобы не «писать в стол» решили опубликовать. Надеюсь, они будут вам также полезны. Если так, то дальше планируем опубликовать все переведенные статьи серии.
Об авторе: Колин Уоллс уже более тридцати лет работает в сфере электронной промышленности, значительную часть времени уделяя встроенному ПО. Сейчас он – инженер в области встроенного ПО в Mentor Embedded ( подразделение Mentor Graphics). Колин Уоллс часто выступает на конференциях и семинарах, автор многочисленных технических статей и двух книг по встроенному ПО. Живет в Великобритании. Профессиональный блог Колина: blogs.mentor.com/colinwalls, e-mail: colin_walls@mentor.com
Читайте первую, вторую, третью, четвертую статьи, опубликованные ранее.