C 语言的消息调度系统不会破坏严格的别名和对齐
一个相当常见的习惯用法!),但是我很难设计一种机制,该机制:
- 简洁
- 通用
- 我正在用 C 语言编写一个嵌入式控制系统,该系统由多个任务组成,这些任务相互发送消息(我相信这是 相对高效
- 最重要的是:与平台无关(具体来说,不违反严格别名或对齐问题)
从概念上讲,我想将每种消息类型表示为单独的结构定义,并且我会就像具有以下功能(简化)的系统:
void sendMsg(queue_t *pQueue, void *pMsg, size_t size);
void *dequeueMsg(queue_t *pQueue);
其中 queue_t
包含节点的链接列表,每个节点都有一个 char buf[MAX_SIZE]
字段。 我所在的系统没有 malloc()
实现,因此需要一个全局空闲节点池,然后是以下之一(感知到的问题以粗体显示):
sendMsg()
将传入消息的memcpy
存储到空闲节点的缓冲区中。
我的理解是,这将存在对齐问题,除非调用者dequeueMsg()
对返回值执行进一步的memcpy
。- 否则将会有一个
void *getFreeBuffer()
函数它返回下一个空闲节点的buf[]
,调用者(发送者)将其转换为适当的类型指针。
我的理解是,这现在将在进入时出现对齐问题,并且在dequeueMsg()
之后仍然需要一个memcpy
以避免在退出时出现对齐问题。 - 或重新定义
中的缓冲区code>queue_t
节点作为(例如)uint32_t buf[MAX_SIZE]
。
我的理解是,这违反了严格的别名,并且不独立于平台。
我能看到的唯一其他选项是创建所有消息类型与 char buf[MAX_SIZE] 的联合,但我不认为这是“整洁”!
所以我的问题是,如何正确地做到这一点?
I'm writing an embedded control system in C that consists of multiple tasks that send messages to each other (a fairly common idiom, I believe!), but I'm having a hard time designing a mechanism which:
- is neat
- is generic
- is relatively efficient
- most importantly: is platform independent (specifically, doesn't violate strict-aliasing or alignment issues)
Conceptually, I'd like to represent each message type as a separate struct definition, and I'd like a system with the following functions (simplified):
void sendMsg(queue_t *pQueue, void *pMsg, size_t size);
void *dequeueMsg(queue_t *pQueue);
where a queue_t
comprises a linked list of nodes, each with a char buf[MAX_SIZE]
field. The system I'm on doesn't have a malloc()
implementation, so there'll need to be a global pool of free nodes, and then one of the following (perceived issues in bold):
sendMsg()
does amemcpy
of the incoming message into the buffer of a free node.
My understanding is that this will have alignment issues unless the caller ofdequeueMsg()
does a furthermemcpy
on the return value.- or there'll be a
void *getFreeBuffer()
function which returns thebuf[]
of the next free node, which the caller (the sender) will cast to the appropriate pointer-to-type.
My understanding is that this will now have alignment issues on the way in, and still requires amemcpy
afterdequeueMsg()
to avoid alignment issues on the way out. - or redefine the buffer in
queue_t
nodes as (e.g.)uint32_t buf[MAX_SIZE]
.
My understanding is that this violates strict aliasing, and isn't platform-independent.
The only other option I can see is to create a union of all the message types along with char buf[MAX_SIZE]
, but I don't count this as "neat"!
So my question is, how does one do this properly?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我不明白为什么 1 会出现对齐问题 - 只要每个
buf[MAX_SIZE]
元素与消息结构中出现的自然最大单个基元类型对齐(可能是32位或64位),那么每种消息类型的内容是什么并不重要; 因为它总是与该尺寸对齐。编辑
实际上,比这更简单。 由于消息队列中的每个元素的长度都是
MAX_SIZE
,因此假设您在其自己的buf
中启动每条消息(即,如果消息; MAX_SIZE) 那么队列中的每条消息都将从至少与自身一样大的边界开始,因此它将始终正确对齐。
I don't understand why 1 presents an alignment issue - as long as each
buf[MAX_SIZE]
element is aligned to the natural largest single primitive type that occurs in your message structs (probably 32 or 64bit), then it doesn't matter what the contents of each message type is; as it will always be aligned to that size.Edit
Actually, it's even simpler than that. As each element in your message queue is
MAX_SIZE
in length, then assuming that you start each message in it's ownbuf
(i.e. you don't pack them if a message is < MAX_SIZE) then every message in the queue will start on a boundary at least as big as itself, therefore it will always be correctly aligned.我们处理这个问题的方法是让我们的空闲列表完全由对齐的节点组成。 事实上,我们对于不同大小的节点有多个空闲列表,因此我们有在 2 字节、4 字节和 16 字节边界上对齐的列表(我们的平台不关心大于一个 SIMD 向量的对齐)。 任何分配都会四舍五入到这些值之一,并放入正确对齐的节点中。 因此,sendMsg 总是将其数据复制到对齐的节点中。 由于您自己编写空闲列表,因此您可以轻松地强制对齐。
我们还可以使用 #pragma 或 declspec 强制 char buf[MAX_SIZE] 数组与queue_t 节点结构内的至少一个字边界对齐。
当然,这假设输入数据已对齐,但如果由于某种原因您传入的消息预计与对齐相差(比方说)3个字节,那么您始终可以使用模数并返回自由节点的偏移量。
通过该底层设计,我们拥有支持上述选项 1 和 2 的接口。 同样,我们的前提条件是输入数据始终是本机对齐的,因此我们的出队当然会返回一个对齐的指针; 但如果您需要奇怪地对齐数据,只需偏移到空闲节点并返回偏移指针即可。
这使您可以处理 void * ,从而避免严格的别名问题。 (一般来说,我认为在编写自己的内存分配器时可能需要放松严格的别名要求,因为本质上它们会在内部模糊类型。)
The way we deal with this is to have our free list consist entirely of aligned nodes. In fact we have multiple free lists for different sizes of node, so we have lists that are aligned on 2 byte, 4 byte, and 16 byte boundaries (our platform doesn't care about alignment larger than one SIMD vector) . Any allocation gets rounded up to one of those values and put in a properly aligned node. So, sendMsg always copies its data into an aligned node. Since you're writing the free list yourself, you can easily enforce alignment.
We would also use a #pragma or declspec to force that char buf[MAX_SIZE] array to be aligned to at least a word boundary inside the queue_t node struct.
This assumes of course that the input data is aligned, but if for some reason you're passing in a message that expects to be (let's say) 3 bytes off from alignment, you can always detect that with a modulus and return an offset into the free node.
With that underlying design we have interfaces that support both option 1 and 2 above. Again, we precondition that input data is always natively aligned, so our dequeue of course returns an aligned pointer; but if you need oddly aligned data in, again, just offset into the free node and return the offset pointer.
This keeps you dealing in void *s thus avoiding your strict aliasing problems. (In general though I think you may need to relax your strict aliasing requirements when writing your own memory allocators, since by nature they blur types internally.)