Сообщения в VDK, как и в других многопоточных системах (VDK это библиотека для организации многозадачной среды реального времени, RTOS [3]), предназначены для организации взаимодействия между потоками - синхронизации между ними, обмена информацией, организации общего доступа к одному ресурсу. Один поток может блокироваться на очереди сообщений (в ожидании появления сообщения), другие потоки могут передавать в эту очередь свои сообщения. Подробнее см. [2].
В этой статье приведен перевод описания API-функций VDK из даташита [1].
[CreateMessage]
// Прототип C:
VDK_MessageID VDK_CreateMessage(int inPayloadType,
u32 inPayloadSize,
void *inPayloadAddr);
// Прототип C++:
VDK::MessageID VDK::CreateMessage(int inPayloadType,
u32 inPayloadSize,
void *inPayloadAddr);
Создает и инициализирует объект сообщения. Возвращаемое значение представляет собой идентификатор нового сообщения (см. тип данных MessageID). Значения, передаваемые в CreateMessage(), могут быть прочитаны вызовом GetMessagePayload(), и могут быть сброшены вызовом FreeMessagePayload(). Вызвавший функцию CreateMessage поток становится владельцем нового сообщения.
Параметры:
inPayloadType определяемое пользователем мзначение, которое может использоваться для передачи в принимающий сообщение поток дополнительной информации о сообщении и/или его полезной нагрузке. Это значение не используется или не модифицируется ядром, кроме отрицательных значений типа полезной нагрузки inPayloadType, которые зарезервированы для использования в VDK. Положительные значения типа полезной нагрузки зарезервированы для использования кодом приложения. Рекомендуется, чтобы адрес (inPayloadAddr) и размер полезной нагрузки (inPayloadSize) всегда интерпретировались одинаково для каждого отдельного типа сообщения.
inPayloadSize длина буфера полезной нагрузки в самых малых единицах памяти процессора (sizeof(char), т. е. для Blackfin это байт). Когда у inPayloadSize значение 0, ядро подразумевает, что inPayloadAddr не указатель, и в этом параметре может храниться произвольное значение одинакового с указателем размера.
inPayloadAddr указатель на начало данных, передаваемых в сообщении.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Возвращаемое значение: новый MessageID в случае успеха и UINT_MAX в случае ошибки.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kErrorMallocBlock показывает, что нет свободных блоков в пуле системной памяти, используемом для выделения сообщений.
[DestroyMessage]
// Прототип C:
void VDK_DestroyMessage(VDK_MessageID inMessageID);
// Прототип C++:
void VDK::DestroyMessage(VDK::MessageID inMessageID);
Уничтожает объект сообщения. Уничтожить сообщения может только тот поток, который является владельцем этого сообщения. Подразумевается, что память полезной нагрузки сообщения освобождается потоком пользователя. DestroyMessage() не освобождет полезную нагрузку, так что если память не освобождается, это может привести к утечке памяти.
Параметры:
inMessageID идентификатор сообщения, которое должно быть уничтожено.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kInvalidMessageOwner показывает, что поток пытается уничтожить сообщение с inMessageID, которым он не владеет.
kMessageInQueue показывает, что сообщение отправлено в поток, ThreadID которого на данный момент неизвестен, и сообщение должно быть убрано из очереди сообщений вызовом PendMessage().
[DestroyMessageAndFreePayload]
// Прототип C:
void VDK_DestroyMessageAndFreePayload(VDK_MessageID inMessageID);
// Прототип C++:
void VDK::DestroyMessageAndFreePayload(VDK::MessageID inMessageID);
Уничтожает объект сообщения и его полезную нагрузку. Уничтожить сообщения может только тот поток, который является владельцем этого сообщения. Если полезная нагрузка коммутационного типа (marshalled-типа, т. е. установлен бит знака кода типа полезной нагрузки inPayloadType), то полезная нагрузка освобождается функцией коммутации типа с кодом RELEASE.
inMessageID идентификатор сообщения, которое должно быть уничтожено.
Влияние на планировщик: не запускает планировщик.
Детерминизм: неопределенное время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kInvalidMessageOwner показывает, что поток пытается уничтожить сообщение с inMessageID, которым он не владеет.
kMessageInQueue показывает, что сообщение отправлено в поток, ThreadID которого на данный момент неизвестен, и сообщение должно быть убрано из очереди сообщений вызовом PendMessage().
Другие ошибки могут быть обработаны функцией коммутации, предоставленной пользователем, или вызванными ею функциями.
[ForwardMessage]
// Прототип C:
void VDK_ForwardMessage(VDK_ThreadID inRecipient,
VDK_MessageID inMessageID,
VDK_MsgChannel inChannel,
VDK_ThreadID inPseudoSender);
// Прототип C++:
void VDK::ForwardMessage(VDK::ThreadID inRecipient,
VDK::MessageID inMessageID,
VDK::MsgChannel inChannel,
VDK::ThreadID inPseudoSender);
Эта функция идентична PostMessage() с тем лишь исключением, что атрибут отправителя сообщения имеет значение аргумента inPseudoSender (псевдоотправитель), а не идентификатор текущего потока.
Функция ForwardMessage полезна при использовании закольцовывания сообщений между двумя потоками (т. е. полученное сообщение возвращается отправителю, а не уничтожается), и в цикл должен быть прозрачно вставлен третий поток.
Путем опроса атрибута отправителя (с помощью функции GetMessageReceiveInfo()), и затем передачи его в качестве аргумента inPseudoSender в функцию ForwardMessage(), этот третий поток может гарантировать, что сообщение будет возвращено исходному отправителю вместо его самого.
Параметры:
inRecipient идентификатор ThreadID потока, который принимает сообщение.
inMessageID идентификатор MessageID отправленного сообщения. Сообщение должно быть создано перед своей отправкой. Этот параметр - значение, возвращаемое из функции CreateMessage().
inChannel FIFO в очереди сообщений получателей, к которой добавляется сообщение. Значение inChannel может быть в диапазоне от kMsgChannel1 до kMessageChannel15 (см. MsgChannel).
inPseudoSender идентификатор ThreadID, сохраненный в атрибуте отправителя сообщения.
Влияние на планировщик: не вызывает блокирование потока, однако запускает планировщик и может привести к переключению контекста.
Детерминизм: неопределенное время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageChannel показывает, что inChannel не является значением из диапазона в MsgChannel.
kUnknownThread показывает, что inRecipient не является допустимым ThreadID.
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kInvalidMessageRecipient показывает, что у потока inRecipient нет очереди сообщений, потому что функционал сообщений для этого потока не разрешен.
kInvalidMessageOwner показывает, что поток, пытающийся послать сообщение с inMessageID, не является текущим владельцем. Значение ошибки это ThreadID владельца.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
[FreeMessagePayload]
// Прототип C:
void VDK_FreeMessagePayload(VDK_MessageID inMessageID);
// Прототип C++:
void VDK::FreeMessagePayload(VDK::MessageID inMessageID);
Если полезная нагрузка указанного объекта сообщения коммутационного типа (marshalled-типа, т. е. установлен бит знака в коде типа полезной нагрузки inPayloadType), то полезная нагрузка освобождается без уничтожения самого объекта сообщения. Эту полезную нагрузку может уничтожить только поток-владелец сообщения. Полезная нагрузка освобождается вызовом функции коммутации типов с кодом RELEASE.
Атрибуты типа полезной нагрузки, размера и адреса объекта сообщения все устанавливаются в ноль.
Параметры:
inMessageID идентификатор MessageID сообщения, полезная нагрузка которого уничтожается.
Влияние на планировщик: не запускает планировщик.
Детерминизм: неопределенное время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kInvalidMessageOwner показывает, что поток, пытающийся удалить сообщение с inMessageID, не является текущим владельцем.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
Другие ошибки могут быть обработаны функцией коммутации, предоставленной пользователем, или вызванными ею функциями.
[GetMessageDetails]
// Прототип C:
void VDK_GetMessageDetails(VDK_MessageID inMessageID,
VDK_MessageDetails *pOutMessageDetails,
VDK_PayloadDetails *pOutPayloadDetails);
// Прототип C++:
void VDK::GetMessageDetails(VDK::MessageID inMessageID,
VDK::MessageDetails *pOutMessageDetails,
VDK::PayloadDetails *pOutPayloadDetails);
Возвратит полный набор атрибутов, связанных с объектом сообщения. Возвращаемые результаты поделены на информацию о сообщении (канал channel, отправитель sender и получатель target) и на информацию о полезной нагрузке (тип, размер и адрес).
Смысл атрибутов сообщения соответствует аргументам из последнего переданного сообщения. Смысл значений полезной нагрузки специфичен для приложения, и соответствует аргументам, переданным в CreateMessage().
Только тот поток, который является владельцем сообщения, может проанализировать атрибуты его полезной нагрузки. Если другие потоки вызовут функцию GetMessageDetails, то произойдет ошибка, и содержимое *pOutMessageDetails и *pOutPayloadDetails останется не измененным.
Параметры:
inMessageID идентификатор MessageID, который указывает на опрашиваемое сообщение.
pOutMessageDetails указатель на структуру типа MessageDetails, где содержатся поля channel, sender и target. У канала тип MsgChannel, и у sender и target тип ThreadID. Значение pOutMessageDetails может быть NULL, в этом случае не будет возвращена информация о сообщении.
pOutPayloadDetails указатель на структуру типа PayloadDetails, где содержатся поля type, size и address, описывающие полезную нагрузку сообщения. Это та же самая информация, что и запрашивается функцией GetMessagePayload(). Значение pOutPayloadDetails может быть NULL, в этом случае не будет возвращена информация о полезной нагрузке
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kInvalidMessageOwner показывает, что поток не является текущим владельцем сообщения с inMessageID.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
[GetMessagePayload]
// Прототип C:
void VDK_GetMessagePayload(VDK_MessageID inMessageID,
int *outPayloadType,
u32 *outPayloadSize,
void **outPayloadAddr);
// Прототип C++:
void VDK::GetMessagePayload(VDK::MessageID inMessageID,
int *outPayloadType,
u32 *outPayloadSize,
void **outPayloadAddr);
Возвращает атрибуты, связанные с полезной нагрузкой сообщения: тип полезной нагрузки, её размер и адрес.
Смысл и значение этих атрибутов зависят от приложения, и соответствуют аргументам, переданным в CreateMessage(). Только тот поток, который является владельцем сообщения, может проанализировать атрибуты полезной нагрузки. Если другие потоки вызовут функцию GetMessagePayload, то произойдет ошибка, и содержимое outPayloadType, outPayloadSize и outPayloadAddr останется неизменным.
Параметры:
inMessageID идентификатор MessageID сообщения, указывающий опрашиваемое сообщение.
outPayloadType значение, специфическое для приложения, которое может использоваться для описания содержимого полезной нагрузки. Отрицательные значения типа полезной нагрузки зарезервированы для использования VDK.
outPayloadSize обычно используется для размера полезной нагрузки в самых малых из возможных единицах памяти процессора (sizeof(char), для процессора Blackfin это байт).
*outPayloadAddr обычно ссылка на указатель, который определяет начало буфера полезной нагрузки. Однако если размер полезной нагрузки ноль, то адрес полезной нагрузки может использоваться для данных, определяемых пользователем.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageOwner показывает, что поток не является текущим владельцем сообщения с inMessageID.
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
[GetMessageReceiveInfo]
// Прототип C:
void VDK_GetMessageReceiveInfo(VDK_MessageID inMessageID,
VDK_MsgChannel *outChannel,
VDK_ThreadID *outSender);
// Прототип C++:
void VDK::GetMessageReceiveInfo(VDK::MessageID inMessageID,
VDK::MsgChannel *outChannel,
VDK::ThreadID *outSender);
Возвращает параметры, связанные со способом получения сообщения.
Только тот поток, который владеет сообщением, может вызвать для него эту функцию. Если другой поток вызовет GetMessageReceiveInfo, произойдет ошибка, и переменные outChannel и outSender не будут содержать правильную информацию.
Параметры:
inMessageID идентификатор MessageID, указывающий опрашиваемое сообщение.
outChannel указатель на переменную типа MsgChannel, идентифицирующую канал очереди сообщений принимающего потока, в который было отправлено сообщение.
outSender указатель на переменную типа ThreadID, идентифицирующую поток, который отправил сообщение.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageOwner показывает, что поток не является текущим владельцем сообщения с inMessageID.
kInvalidMessageID показывает, что inMessageID не является допустимым MessageID.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
[InstallMessageControlSemaphore]
// Прототип C:
void VDK_InstallMessageControlSemaphore(VDK_SemaphoreID inSemaphore);
// Прототип C++:
void VDK::InstallMessageControlSemaphore(VDK::SemaphoreID inSemaphore);
Настраивает семафор со счетчиком, чтобы регулировать выделение и освобождение объектов сообщений маршрутизируемыми потоками. Начальное значение семафора должно быть установлено в количество свободных сообщений, зарезервированных для использования входящими маршрутизируемыми потоками. На семафоре блокируются маршрутизируемые потоки перед каждым выделением сообщения, и семафор публикуется (исходящими маршрутизируемыми потоками) после каждого освобождения сообщения. При условии, что значение семафора всегда меньше или равно количеству свободных сообщений, выделение маршрутизируемыми потоками сообщений никогда не потерпит неудачу (хотя маршрутизируемые потоки могут блокироваться на ожидании семафора), с ожиданием, когда станет доступным свободное сообщение.
Параметры:
inSemaphore идентификатор SemaphoreID инсталлируемого семафора.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок не поддерживается.
[MessageAvailable]
// Прототип C:
bool VDK_MessageAvailable(u32 inMessageChannelMask);
// Прототип C++:
bool VDK::MessageAvailable(u32 inMessageChannelMask);
Позволяет потоку использовать модель опроса (вместо модели блокировки) для ожидания появления сообщений в его очереди сообщений. Эта функция вернет TRUE, если последующий вызова PendMessage() на том же канале не заблокирует поток.
Параметры:
inMessageChannelMask задает каналы приема. Установленные биты соответствуют каналам приема, очищенные биты соответствуют каналам, которые игнорируется.
Если в маске каналов установлен флаг VDK::kMsgWaitForAll, то запрос работает по логике AND вместо логики OR. По умолчанию только одно сообщение - на любом из каналов приема, обозначенных в маске каналов приема - требуется для возврата TRUE. Флаг VDK::kMsgWaitForAll для возврата TRUE требует, чтобы находилось как минимум одно сообщение в очереди на каждом из обозначенных каналов приема.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Возвратит TRUE, если доступно сообщение, иначе возвратит FALSE.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageChannel покажет, что inMessageChannelMask недопустимая маска.
kInvalidThread покажет, что у текущего потока нет очереди сообщений, потому что он не разрешен для поддержки функции сообщений.
[PendMessage]
// Прототип C:
VDK_MessageID VDK_PendMessage(u32 inMessageChannelMask,
VDK_Ticks inTimeout);
// Прототип C++:
VDK::MessageID VDK::PendMessage(u32 inMessageChannelMask,
VDK::Ticks inTimeout);
Получает сообщение из очереди сообщений потока. Если не задана в качестве таймаута константа kDoNotWait, вызов функции PendMessage() блокирует выполнение потока до тех пор, пока в очереди не появится допустимое сообщение. Если указана константа kDoNotWait, и нет заданного маской сообщения, то PendMessage() вернет UINT_MAX, и поток продолжит свое выполнение.
Маска каналов позволяет задать, какие каналы (от kMsgChannel1 до kMsgChannel15) проверять на предмет появления входящих сообщений. Можно использовать функцию MessageAvailable(), чтобы опросить присутствие допустимого сообщения вместо использования блокирующего поведения функции PendMessage().
Дополнительно флаг VDK::kMsgWaitForAll может быть наложен операцией OR на маску каналов, что меняет условие проверки наличия сообщений. В этом случае для возврата из блокировки до истечения таймаута в очереди каждого из указанных маской каналов должно присутствовать как минимум одно сообщение. Сообщения выбираются из очередей сначала из самых младших из указанных маской каналов (kMsgChannel1, затем kMsgChannel2, ...). Как только MessageID было возвращено функцией PendMessage(), это сообщение больше не находится в очереди, и его владельцем становится вызвавший функцию PendMessage поток.
Если поток не возобновил выполнение в интервале тиков inTimeout, точка повторного запуска выполнения потока переносится в функцию ошибки, и поток становится доступным для управления планировщиком. Это поведение можно поменять наложением по OR на таймаут константы VDK_kNoTimeoutError на языке C или VDK::kNoTimeoutError на языке C++. В этом случае не будет запускаться обработка ошибок при таймауте, и функция просто выполнит возврат после того, как поток станет доступным для управления планировщиком. Если в качестве таймаута inTimeout передан 0, то поток может бесконечно ожидать появления сообщения.
Параметры:
inMessageChannelMask задает каналы приема. Установленные биты соответствуют каналам приема, очищенные биты соответствуют каналам, которые игнорируется. Этот параметр не может быть равным 0.
Если в маске каналов установлен флаг VDK::kMsgWaitForAll, то блокировка работает по логике AND вместо логики OR. По умолчанию для выхода из блокировки до истечения таймаут достаточно только одного сообщения на очереди любого из заданных маской каналов. Флаг VDK::kMsgWaitForAll для разблокировки до таймаута требует, чтобы находилось как минимум одно сообщение на очереди каждого из указанных маской каналов приема.
inTimeout значение меньшее INT_MAX указывает максимальную длительность (таймаут) времени в тиках Ticks, в течение которого поток блокируется в ожидании приема требуемого сообщения (сообщений). На это значение таймаута может операцией OR может быть наложена константа kNoTimeoutError, если при таймауте не должна запускаться обработка ошибки. Значение kDoNotWait указывает, что функция не должна блокировать выполнение потока, если нет доступных сообщений.
Влияние на планировщик: запускает планировщик и может привести к переключению контекста.
Детерминизм: всегда одно и то же время, если не требуется блокировка.
В случае успеха возвратит идентификатор ожидаемого сообщения, иначе возвратит UINT_MAX.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kDbgPossibleBlockInRegion показывает, что PendMessage() была вызвана в необслуживаемом (unscheduled) регионе кода [6], что потенциально может привести к конфликту планирования выполнения потоков (deadlock, мертвая блокировка).
kInvalidMessageChannel показывает, что inMessageChannelMask не задает корректную группу каналов для маски.
kMessageTimeout показывает, что истек таймаут до того, как поток извлек сообщение из своей очереди сообщений. Эта ошибка не обрабатывается, если на таймаут операцией OR была наложена константа kNoTimeoutError.
kBlockInInvalidRegion показывает, что PendMessage() пытается блокировать поток в необслуживаемом регионе, что приведет к конфликту планировщика (deadlock, мертвая блокировка).
kInvalidTimeout показывает, что в качестве таймаута inTimeout не указано ни INT_MAX, ни (0 | kNoTimeouterror).
kInvalidThread покажет, что у текущего потока нет очереди сообщений, потому что он не разрешен для поддержки функции сообщений.
[PostMessage]
// Прототип C:
void VDK_PostMessage(VDK_ThreadID inRecipient,
VDK_MessageID inMessageID,
VDK_MsgChannel inChannel);
// Прототип C++:
void VDK::PostMessage(VDK::ThreadID inRecipient,
VDK::MessageID inMessageID,
VDK::MsgChannel inChannel);
Добавляет сообщение с идентификатором inMessageID в очередь сообщений потока с идентификатором inRecipient на канале inChannel. Функция PostMessage() не блокирующая - выполнение вызвавшего потока немедленно продолжится после возврата из неё без ожидания для запуска потока, принимающего это сообщение, или для подтверждения нового сообщения в его очереди. Сообщение считается доставленным, когда произойдет выход из PostMessage(). Отправить сообщение может только тот поток, кто является владельцем этого сообщения.
В момент доставки владение сообщением и связанной с ним полезной нагрузки переходит от потока-отправителя к потоку-получателю сообщения. Как только сообщение доставлено, все ссылки на полезную нагрузку в памяти, которые могут содержаться в потоке-отправителе, являются недействительными. Права на чтение и запись памяти и ответственность за освобождение этой памяти для полезной нагрузки переходят на поток-получатель вместе с правом владения сообщением.
Параметры:
inRecipient идентификатор ThreadID потока-получателя сообщения.
inMessageID идентификатор MessageID отправляемого сообщения. Перед отправкой сообщение должно быть создано вызовом функции CreateMessage() (этот параметр - значение возврата и функции CreateMessage).
inChannel FIFO в очереди сообщений принимающего потока, куда добавляется сообщение. Это значение в диапазоне от kMsgChannel1 до kMsgChannel15 (см. MsgChannel).
Влияние на планировщик: запускает планировщик и может привести к переключению контекста.
Детерминизм: время выполнение неопределенное.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageChannel покажет, что inChannel не значение канала (см. MsgChannel).
kUnknownThread покажет, что inRecipient недопустимый ThreadID.
kInvalidMessageID покажет, что inMessageID недопустимый MessageID.
kInvalidMessageRecipient показывает, что у потока inRecipient нет очереди сообщений, потому что функционал сообщений для этого потока не разрешен.
kInvalidMessageOwner покажет, что поток пытается отправить сообщение, которым в настоящий момент не владеет. Значение ошибки это ThreadID потока-владельца сообщения.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
kInvalidTargetDSP покажет, что получатель сообщения находится на ядре, которое не декларировано на закладке VDK
Kernel. Эта ошибка обрабатывается только в случае отправки сообщения между ядрами процессора (multiprocessor messaging).
[SetMessagePayload]
// Прототип C:
void VDK_SetMessagePayload(VDK_MessageID inMessageID,
int inPayloadType,
u32 inPayloadSize,
void *inPayloadAddr);
// Прототип C++:
void VDK::SetMessagePayload(VDK::MessageID inMessageID,
int inPayloadType,
u32 inPayloadSize,
void *inPayloadAddr);
Установит значения в заголовке сообщения, который описывает полезную нагрузку сообщения. Смысл этих значений зависит от приложения, и соответствует аргументам, переданным в функцию CreateMessage(). Функция SetMessagePayload перезапишет существующие значения в сообщении. Только тот поток, который является владельцем этого сообщения, может установить атрибуты его полезной нагрузки.
Параметры:
inMessageID задает сообщение, которое модифицируется (см. MessageID).
inPayloadType тип полезной нагрузки - значение, специфичное для приложения, которое может использоваться для описания содержимого полезной нагрузки. Отрицательные значения inPayloadType зарезервированы для использования VDK.
inPayloadSize показывает размер полезной нагрузки в самых малых возможных единицах памяти процессора (sizeof(char), для процессоров Blackfin это байт).
inPayloadAddr это обычно указатель на начало буфера полезной нагрузки. Если в качестве inPayloadSize указано значение 0, то inPayloadAddr может быть любым значением соответствующего размера.
Влияние на планировщик: не запускает планировщик.
Детерминизм: всегда одно и то же время.
Обработка ошибок поддерживается только для библиотек с функционалом full instrumentation и error-checking:
kInvalidMessageOwner покажет, что поток пытается изменить атрибуты полезной нагрузки сообщения сообщения, которым в настоящий момент не владеет.
kInvalidMessageID покажет, что в аргументе inMessageID недопустимый MessageID.
kMessageInQueue показывает, что сообщение отправлено потоку, ThreadID которого в настоящий момент неизвестен, и сообщение должно быть удалено из очереди сообщений вызовом PendMessage().
[Типы данных]
MessageID
Тип, используемый для хранения уникального идентификатора сообщения.
enum MessageID
{
/* Определяется средой IDDE в заголовке VDK.h. */
};
Перечисление MessageID в VDK.h будет пустым. Все сообщения динамически выделяются и получают ID типа MessageID, чтобы компилятор мог выполнить проверку типов и предотвратить возникновение возможных ошибок.
// Определение типа на языке C:
typedef enum MessageID VDK_MessageID;
// Определение типа на языке C++:
typedef enum MessageID VDK::MessageID;
MsgChannel
Перечисляющий каналы тип, на которых может быть отправлено сообщение, или на которых ожидается появление сообщения.
// Определение типа на языке C:
enum VDK_MsgChannel
{
VDK_kMsgWaitForAll = 1 << 15,
VDK_kMsgChannel1 = 1 << 14,
VDK_kMsgChannel2 = 1 << 13,
VDK_kMsgChannel3 = 1 << 12,
VDK_kMsgChannel4 = 1 << 11,
VDK_kMsgChannel5 = 1 << 10,
VDK_kMsgChannel6 = 1 << 9,
VDK_kMsgChannel7 = 1 << 8,
VDK_kMsgChannel8 = 1 << 7,
VDK_kMsgChannel9 = 1 << 6,
VDK_kMsgChannel10 = 1 << 5,
VDK_kMsgChannel11 = 1 << 4,
VDK_kMsgChannel12 = 1 << 3,
VDK_kMsgChannel13 = 1 << 2,
VDK_kMsgChannel14 = 1 << 1,
VDK_kMsgChannel15 = 1 << 0
};
// Определение типа на языке C++:
enum VDK::MsgChannel
{
VDK::kMsgWaitForAll = 1 << 15,
VDK::kMsgChannel1 = 1 << 14,
VDK::kMsgChannel2 = 1 << 13,
VDK::kMsgChannel3 = 1 << 12,
VDK::kMsgChannel4 = 1 << 11,
VDK::kMsgChannel5 = 1 << 10,
VDK::kMsgChannel6 = 1 << 9,
VDK::kMsgChannel7 = 1 << 8,
VDK::kMsgChannel8 = 1 << 7,
VDK::kMsgChannel9 = 1 << 6,
VDK::kMsgChannel10 = 1 << 5,
VDK::kMsgChannel11 = 1 << 4,
VDK::kMsgChannel12 = 1 << 3,
VDK::kMsgChannel13 = 1 << 2,
VDK::kMsgChannel14 = 1 << 1,
VDK::kMsgChannel15 = 1 << 0
};
MessageDetails
Структура, где скомбинированы три атрибута, описывающие самое последнее отправленное сообщение.
// Определение типа на языке C:
typedef struct
{
VDK_MsgChannel channel;
VDK_ThreadID sender;
VDK_ThreadID target;
} VDK_MessageDetails;
// Определение типа на языке C++:
typedef struct
{
VDK::MsgChannel channel;
VDK::ThreadID sender;
VDK::ThreadID target;
} VDK::MessageDetails;
ThreadID
Тип, используемый для хранения уникального идентификатора потока.
enum ThreadID
{
/* Определяется средой IDDE в заголовке VDK.h. */
};
Перечисление в VDK.h будет содержать только те идентификаторы ID, которые были заданы для потоков в момент загрузки (boot time). Эти идентификаторы были заранее определены на закладке VDK Kernel проекта. Любые динамически созданные потоки получат ID такого же типа, чтобы компилятор мог выполнить проверку типов и предотвратить возникновение возможных ошибок.
// Определение типа на языке C:
typedef enum ThreadID VDK_ThreadID;
// Определение типа на языке C++:
typedef enum ThreadID VDK::ThreadID;
Существует два значения, которые являются исключительно не идентификаторами потока, однако используемых в библиотеке VDK:
0xC0000000 означает уровень ядра (kernel level). 0x80000000 означает уровень прерываний (interrupt level).
Ticks
Тип для единицы системного времени. Тики происходят со строго определенными интервалами времени, в этих интервалах срабатывают прерывания, генерируемые аппаратным таймером.
// Определение типа на C:
typedef unsigned int VDK_Ticks;
// Определение типа на C++:
typedef unsigned int VDK::Ticks;
[Как использовать очереди сообщений]
Несмотря на подробное описание API-функций сообщений, сходу непонятно, как с этим богатым функционалом работать - возможностей довольно много. Однако первое, что стоит заметить - к сожалению, сообщения нельзя использовать для обмена данными между доменом прерываний и доменом потоков. Т. е. сообщения можно использовать только для синхронизации между потоками.
Самый обычный сценарий - один поток блокируется на очереди сообщений (вызывает PendMessage), другой поток или другие потоки передают в эту очередь сообщения (вызывается PostMessage). Блокировка на очереди сообщений может быть на неопределенное время, либо с таймаутом, либо без таймаута. Более сложный сценарий подразумевает перенаправление сообщений (ForwardMessage) или возврат сообщения из принимающего потока в передающий.
Здесь я рассмотрю простой пример использования сообщений. В этом сценарии сообщение создается передающим потоком, передается принимающему потоку, принимающий поток принимает сообщение, обрабатывает сообщение и удаляет его. Чтобы очередь сообщений не переполнилась, необходимо обеспечить условие, что принимающий поток всегда имеет процессорное время для выборки сообщения из очереди.
Итак, организация обмена между потоками с помощью сообщений, процесс по шагам:
1. Настройка свойств Thread Types. Для принимающего потока необходимо разрешить возможность работы с сообщениями. Это делается на закладке Kernel проекта VDK, раздел Threads -> Thread Types. Разверните свойства нужного типа потока, и измените его свойство Message Enabled на true. Это разрешит прием сообщений потоком (по умолчанию для потока функционал сообщений запрещен, т. е. свойство Message Enabled установлено в false).
2. Настройка свойств Messages. В разделе Messages настраивается длина очереди сообщений принимающего потока и куча, где эта очередь создается.
Проверьте параметр Maximum Messages. Числовое значение этого параметра определяет глубину очереди FIFO для сообщений, т. е. сколько сообщений можно опубликовать вызовами PostMessage без соответствующих вызовов PendMessage.
Если в проекте используется несколько куч, то можно выбрать кучу для очереди сообщений в параметре Messages -> Messages Heap.
3. Идентификатор принимающего потока. Поток, принимающий сообщение, должен проинициализировать глобальную переменную с идентификатором потока, который ему соответствует. Этот идентификатор будут использовать другие потоки, чтобы отправлять сообщения вызовами PostMessage. В примере ниже принимающий поток RXthread инициализирует глобальную переменную RXthreadID своим идентификатором с помощью вызова GetThreadID():
VDK::ThreadID RXthreadID;
void RXthread::Run()
{
RXthreadID = VDK::GetThreadID();
while (1)
{
...
4. Блокировка в ожидании сообщений. Поток, принимающий сообщения, должен блокироваться на очереди сообщений в ожидании появления сообщения. Для этого используется функция PendMessage.
Для первого параметра PendMessage необходимо выбрать канал, через который будет осуществляться обмен сообщениями. Всего существует возможность передачи сообщений через 15 отдельных каналов, от kMsgChannel15 до kMsgChannel1. Можно ожидать появления сообщения по нескольким каналам сразу, если в вызове PendMessage объединить их в маску операцией OR. Чем меньше номер канала, тем большим приоритетом он обладает, т. е. самым первым будет извлекаться сообщение из канала с самым меньшим номером, даже если оно было отправлено позже. Есть и другие возможности, см. описание API-функций выше.
Для второго параметра PendMessage необходимо выбрать, как долго будет блокироваться принимающий поток в ожидании сообщений. Тут есть несколько вариантов. Можно выбрать бесконечное время ожидания, тогда значением параметра будет INT_MAX. Можно отключить блокировку, чтобы PendMessage немедленно делала возврат, и по результату возврата проверять наличие сообщений. Можно указать таймаут в тиках, тогда блокировка PendMessage будет осуществляться на это указаное время. Если на таймаут наложить константу kNoTimeoutError, то в случае таймаута вызов обработчика ошибки потока осуществляться не будет.
В этом простом примере используется один канал kMsgChannel15, на нем поток должен заблокироваться в ожидании сообщений на 100 тиков системы. Если сообщение придет до этого таймаута, то возврат из PendMessage произойдет раньше. На таймаут 100 наложена маска kNoTimeoutError, чтобы не вызывалась функция ошибки потока. Если результат возврата из PendMessage не равен UINT_MAX, то было успешно принято сообщение. Иначе цикл while(1) перейдет на следующую итерацию, и произойдет повторная блокировка в ожидании сообщения.
void RXthread::Run()
{
RXthreadID = VDK::GetThreadID();
while (1)
{
VDK::MessageID msg = PendMessage(kMsgChannel15,
VDK::kNoTimeoutError | 100);
if (UINT_MAX == msg)
continue;
...
Примечание: если Вы хотите указать время таймаута не в тиках, а в миллисекундах, то используйте функцию GetTickPeriod(). Она возвратит длительность одного типа в миллисекундах.
5. Обработка принятого сообщения. После того, как сообщение принято, нужно его обработать. Это делается вызовом GetMessagePayload. В первом параметре функции GetMessagePayload передается ID принятого сообщения. Во втором параметре передается указатель на 32-битную переменную, куда будет записано значение, переданное в PostMessage (оно может быть произвольным, используемым в приложении). Третий параметр - указатель на переменную, куда будет записан размер переданной полезной нагрузки в байтах. Четвертый параметр - ссылка на переменную-указатель, куда будет записан адрес, по которому можно получить данные буфера полезной нагрузки.
В примере ниже через переменную ID сообщения передается через переменную msg, переменная what_to_do используется для кода необходимых действий по обработке, переменная size получит размер полезной нагрузки, а указатель pPayload будет загружен адресом буфера полезной нагрузки.
...
if (UINT_MAX == msg)
continue;
int what_to_do;
unsigned size;
void *pPayload;
VDK::GetMessagePayload(msg, &what_to_do, &size, &pPayload);
switch (what_to_do)
{
case 1:
// Действия по обработке кода 1:
...
break;
case 2:
// Действия по обработке кода 2:
...
break;
...
}
VDK::DestroyMessage(msg);
}// while (1)
}
Обратите внимание, что после обработки сообщение удаляется с помощью вызова DestroyMessage.
6. Передача сообщения. Передача сообщения принимающему потоку осуществляется в любом другом потоке с помощью вызовов CreateMessage и PostMessage.
Сначала с помощью CreateMessage передающий поток создает объект сообщения и получает его идентификатор типа MessageID. В этом примере при создании сообщения в неё передается код действия what_to_do и размер полезной нагрузки payloadsize, на которую указывает адрес payload:
VDK::MessageID msg = VDK::CreateMessage(what_to_do,
payloadsize,
payload);
После этого сообщение передается в очередь принимающего потока с помощью вызова PostMessage. В первом параметре передается идентификатор принимающего потока потока RXthreadID (этот идентификатор был получен на шаге 3), во втором параметре указывается идентификатор созданного объекта сообщения, и в третьем параметре указывается канал, на котором передается сообщение.
VDK::PostMessage(RXthreadID, msg, kMsgChannel15);
Стоит отметить, что использование очередей довольно эффективно по сравнению с использованием семафоров. Непонятно, с чем это связано, но факт - после того, как я переделал синхронизацию с помощью семафоров на синхронизацию с помощью сообщений, загрузка процессора принимающим потоком уменьшилась почти в 10 раз - с 9.9% на 1.2%.
[Ссылки]
1. VisualDSP++ 5.0 Kernel (VDK) User’s Guide site:analog.com. 2. VDK: сигналы, взаимодействие потоков и ISR (синхронизация). 3. Обзор VisualDSP++ Kernel RTOS (VDK). 4. VDK: API семафоров. 5. VDK: API мьютексов. 6. Специфика использования VDK для процессоров Blackfin. |