Система отправки сообщений на C, которая не нарушает строгое сглаживание и выравнивание
-
18-09-2019 - |
Вопрос
Я пишу встроенную систему управления на C, которая состоит из нескольких задач, которые отправляют сообщения друг другу (я полагаю, довольно распространенная идиома!), но мне трудно разработать механизм, который:
- является аккуратным
- является общим
- является относительно эффективным
- самое главное: не зависит от платформы (в частности, не нарушает строгое сглаживание или проблемы с выравниванием)
Концептуально я хотел бы представить каждый тип сообщения как отдельное определение структуры, и я хотел бы систему со следующими функциями (упрощенными):
void sendMsg(queue_t *pQueue, void *pMsg, size_t size);
void *dequeueMsg(queue_t *pQueue);
где a queue_t
содержит связанный список узлов, каждый из которых имеет char buf[MAX_SIZE]
поле.Система, в которой я нахожусь, не имеет malloc()
реализация, поэтому потребуется создать глобальный пул свободных узлов, а затем одну из следующих (предполагаемые проблемы выделены жирным шрифтом):
sendMsg()
делает лиmemcpy
из входящего сообщения в буфер свободного узла.
Насколько я понимаю, у этого будут проблемы с выравниванием, если вызывающий изdequeueMsg()
делает дальнейшийmemcpy
от возвращаемого значения.- или там будет
void *getFreeBuffer()
функция, которая возвращаетbuf[]
следующего свободного узла, который вызывающий (отправитель) преобразует в соответствующий указатель на тип.
Насколько я понимаю, теперь у этого будут проблемы с выравниванием на входе, и все еще требуетсяmemcpy
послеdequeueMsg()
чтобы избежать проблем с выравниванием на выходе. - или переопределите буфер в
queue_t
узлы как (например)uint32_t buf[MAX_SIZE]
.
Насколько я понимаю, это нарушает строгое сглаживание и не зависит от платформы.
Единственный другой вариант, который я вижу, - это создать объединение всех типов сообщений вместе с char buf[MAX_SIZE]
, но я не считаю это "аккуратным"!
Итак, мой вопрос в том, как это сделать правильно?
Решение
Способ, которым мы справляемся с этим, заключается в том, чтобы наш бесплатный список состоял полностью из выровненных узлов.На самом деле у нас есть несколько бесплатных списков для разных размеров узла, поэтому у нас есть списки, выровненные по границам в 2 байта, 4 байта и 16 байт (наша платформа не заботится о выравнивании размером более одного SIMD-вектора).Любое распределение округляется до одного из этих значений и помещается в правильно выровненный узел.Таким образом, sendMsg всегда копирует свои данные в выровненный узел.Поскольку вы сами составляете бесплатный список, вы можете легко обеспечить выравнивание.
Мы также использовали бы #pragma или declspec, чтобы принудительно выровнять массив char buf[MAX_SIZE] по крайней мере по границе слова внутри структуры узла queue_t.
Это предполагает, конечно, что входные данные выровнены, но если по какой-то причине вы передаете сообщение, которое ожидает чтобы быть (скажем) отклоненным от выравнивания на 3 байта, вы всегда можете определить это с помощью модуля и вернуть смещение в свободный узел.
С этим базовым дизайном у нас есть интерфейсы, которые поддерживают оба варианта 1 и 2 выше.Опять же, мы предполагаем, что входные данные всегда изначально выровнены, поэтому наше удаление из очереди, конечно, возвращает выровненный указатель;но если вам нужны странно выровненные данные в, опять же, просто смещите в свободный узел и верните указатель смещения.
Это позволяет вам работать с void *, таким образом избегая ваших проблем со строгим наложением псевдонимов.(В целом, хотя я думаю, что вам, возможно, придется ослабить свои строгие требования к псевдонимам при написании собственных распределителей памяти, поскольку по своей природе они внутренне размывают типы.)
Другие советы
Я не понимаю, почему 1 представляет проблему выравнивания - до тех пор, пока каждый buf[MAX_SIZE]
элемент выровнен по естественному самому большому одиночному примитивному типу, который встречается в ваших структурах сообщений (вероятно, 32 или 64 бит), тогда не имеет значения, каково содержимое каждого типа сообщения;поскольку он всегда будет выровнен по этому размеру.
Редактировать
На самом деле, все еще проще, чем это.Поскольку каждый элемент в вашей очереди сообщений является MAX_SIZE
по длине, затем предполагая, что вы начинаете каждое сообщение с его собственного buf
(т.е.вы не упаковываете их, если сообщение < MAX_SIZE) тогда каждое сообщение в очереди будет начинаться с границы, по крайней мере, такой же большой, как и оно само, поэтому оно всегда будет правильно выровнено.