IPC核间通信
IPC(Inter-Processor Communication)提供与处理器硬件无关的API,可用于多核处理器核间通信、同一处理器进程间通信和设备间通信。对于异构多核处理器,可通过IPC组件进行核间通信。IPC通信中常用的MessageQ——大小可变的消息传递模块。这是一种功能强大但易于使用 MessageQ 的消息传递, 调用MessageQ 模块后,如下图标红部分:
在MessageQ 模块工作场景图中,灰色的部分并不是不要的,只是在MessageQ中,灰色部分会在应用程序 main ()函数中的 Ipc _ start () API 调用负责配置,我们在只需要关注MessageQ 模块工作场景图的标红部分即可。所有使用 IPC 模块的应用程序都必须调用 IPC _ start () API,该 API 执行以下操作:
1、初始化 IPC 使用的许多对象和模块同步多个处理器,以便它们可以以任何顺序引导。
2、IPC _ start ()使用索引为0(零)的共享区域创建资源管理表,供其他 IPC 模块在内部使用。
所以在调用 Ipc _ attach ()之前,必须在处理器上调用 Ipc _ start ()。
MessageQ 模块支持可变长度消息的结构化发送和接收。它是独立于操作系统的,可以使用任何线程模型。**对于你创建的每个 MessageQ,只有一个读取器,可能有多个写入器。**消息通过消息队列发送和接收。读取器是从消息队列中获取(读取)消息的线程。写入器是一个将消息放入(写入)消息队列的线程。每个消息队列有一个读取器,可以有多个写入器。一个线程可以读取或写入多个消息队列。
单个读线程调用 MessageQ _ create ()、 MessageQ _ get ()、 MessageQ _ free ()和 MessageQ _ delete ()。
Writer 线程调用 MessageQ _ open ()、 MessageQ _ alloc ()、 MessageQ _ put ()和 MessageQ _ close ()。
上述的MessageQ读取和写入流程可以用流程图表示:
从概念上讲,读线程拥有一个消息队列。读线程创建一个消息队列。然后,Writer 线程打开创建的消息队列以访问它们。
创建 MessageQ 对象MessageQ _ create ()
读线程创建一个消息队列。要动态创建一个 MessageQ 对象,其语法如下:
MessageQ_Handle MessageQ_create(String name,MessageQ_Params *params);
创建队列时,需要指定名称字符串,因为MessageQ _ open ()函数需要此名称。虽然这个名称不是必需的(也就是说,它可以是 NULL) ,但是一个未命名的队列是不能被打开的。如果调用成功,则返回 messageq_handle。如果调用失败,则返回 NULL。
打开消息队列MessageQ _ open ()
Writer 线程打开创建的消息队列来访问它们。为了获得已创建的消息队列的句柄,编写线程必须调用 MessageQ _ open () ,其语法如下。
Int MessageQ_open(String name, MessageQ_QueueId *queueId);
这个函数需要一个名字——String name,必须与创建对象的名字匹配。如果在任何处理器上没有找到匹配的名称, MessageQ_open()将返回MessageQ_E_NOTFOUND,如果 MessageQ open 成功,队列 ID 被填充 MessageQ_S_SUCCESS。
分配消息MessageQ _ alloc ()
MessageQ 通过 MessageQ _ alloc ()和 MessageQ _ free ()函数管理消息分配;
MessageQ 使用 Heaps 进行消息分配;
MessageQ_Msg MessageQ_alloc(UInt16 heapId, UInt32 size);
MessageQ _ alloc ()中的分配大小必须包括消息头的大小,也就是32字节。一旦消息被分配,它可以被发送到任何消息队列。一旦读取器接收到消息,它可以释放消息或者重新使用消息。如果应用程序没有使用 MessageQ _ alloc ()分配的消息,则无法通过 MessageQ _ free ()函数释放消息,即使消息由不同的处理器接收。另外,传输可能会在内部调用 MessageQ _ free ()并且遇到错误。
发送消息MessageQ _ put ()
一旦打开消息队列并分配了消息,就可以通过 MessageQ _ put ()函数将消息发送到 MessageQ。函数语法如下:
Int MessageQ_put(MessageQ_QueueId queueId, MessageQ_Msg msg);
可以通过 MessageQ _ getreplyqueue ()函数“发现”消息队列 ID,
/* 使用嵌入的答复目的地 */
replyMessageQ = MessageQ _ getreplyqueue (msg);
if(replyMessageQ == MessageQ _ invalidmessageq)
{
System _ abort (“ Invalid reply queue n”) ;
}
/* 发送回应 */
Status = MessageQ _ put (replyQueue,msg) ;
if(Status < 0)
{
System _ abort (“ MessageQ _ put 不成功 n”) ;
}
如果 MessageQ _ put ()成功,则返回 MessageQ _ s _ success。
在发送消息之前,可以使用 MessageQ _ setmsgid ()函数为接收线程可以检查的消息分配一个数值。
/* Increment… 远程端将检查这个 */
msgId++;
MessageQ _ setmsgid (msg,msgId);
还可以使用 MessageQ _ setmsgpri ()函数来设置消息的优先级。
接收信息MessageQ _ get ()
为了接收消息,读线程调用MessageQ _ get () API接口,接口函数如下所示:
Int MessageQ_get(MessageQ_Handle handle,
MessageQ_Msg *msg, UInt timeout)
Status = MessageQ _ get (MessageQ,& msg,MessageQ _ forever); if (status < 0)
{
System _ abort (“不应该发生; 永远超时n”) ;
}
如果没有消息出现,也没有错误发生,这个函数会在等待消息到达的超时期间阻塞。如果超时过期,则返回 MessageQ _ e _ fail。如果发生错误,msg 参数将不会改变。收到消息后,可以使用以下 api 从消息头获取有关消息的信息:
1、MessageQ _ getmsgid ()获取 MessageQ _ setmsgid ()设置的 ID 值。例如:
/* 获取 id 并将其递增以发送回 */
msgId = MessageQ _ getmsgid (msg) ;
MessageQ _ setmsgid (msg,msgId) ;
2、Messageq_getmsgpri ()获得 messageq_setmsgpri ()设置的优先级。
3、MessageQ _ getmsgsize ()获取以字节为单位的消息大小。
4、Messageq_getreplyqueue ()获取 messageq_setreplyqueue ()提供的队列 ID。
删除 MessageQ 对象MessageQ _ delete ()
MessageQ _ delete ()释放存储在本地内存中的 MessageQ 对象。如果任何消息仍然在内部链接列表中,它们将被释放。句柄的内容被函数取空,以防止删除后使用。
Void MessageQ _ delete (MessageQ _ handle * handle) ;
一旦消息队列被删除,就不能向它发送任何消息。 MessageQ _ close ()是推荐的,但不是必需的。
消息优先级
MessageQ 支持以下三个消息优先级:
1、MessageQ _ normalpri = 0;
2、MessageQ _ highpri = 1;
3、MessageQ _ urgentpri = 3;
可以在发送消息之前使用 MessageQ _ setmsgpri 函数设置消息的优先级:
Void MessageQ_setMsgPri(MessageQ_Msg msg,
MessageQ_Priority priority);
在内部,MessageQ 对象维护两个链表: 普通链表和高优先级链表。一个普通的优先级消息以 FIFO 的方式放置在“普通”链表中。一个高优先级的消息以 FIFO 的方式被放置到“高优先级”链表中。紧急消息被放置在高链表的开头。
由于在阅读一条信息之前可能会发送多条紧急信息,因此不能保证紧急信息的顺序。
当收到消息时,读者首先查看高优先级链接列表。如果一条消息出现在这个列表中,它就会被返回。如果没有,则检查正常的优先级链接列表。如果一条消息出现在那里,它将被返回;否则同步器的等待函数就会被调用。
应答队列
对于某些应用程序,在队列上执行 messageq_open ()是不现实的。例如,服务器可能不想打开所有客户端的队列来发送响应。为了支持这个用例,消息发送者可以使用 MessageQ _ setreplyqueue ()函数在消息中嵌入一个回复 queueId。
Void MessageQ_setReplyQueue(MessageQ_Handle handle,
MessageQ_Msg msg)
这个 API 将消息队列的 queueId 存储到 MsgHeader 中的字段中。
MessageQ _ getreplyqueue ()函数执行相反的操作。例如:
MessageQ_QueueId replyQueue;
MessageQ_Msg msg;
...
/* Use the embedded reply destination */
replyMessageQ = MessageQ_getReplyQueue(msg);
if (replyMessageQ == MessageQ_INVALIDMESSAGEQ)
{
System_abort("Invalid reply queue\n");
}
这个函数返回的 MessageQ _ queueid 值可以在 MessageQ _ put ()调用中使用。
共享内存分配
AM5728核心板的Linux 内核预留0xa0000000~0xac000000(192MByte)内存作为 CMEM 共享内存。
本次实验使用 0xa0000000~0xa0200000(2MByte)作为共享内存进行测试,此 2MByte 内存划分为 4 个 512KByte(0x80000,524288)池空间。设备树修改如下:
更换设备树后重新启动开发板,查询内存分配及空间大小:
在上述共享内存分区中,id 0池空间用于存放原始的时域数据,id 1池空间用于存放处理后的频域数据,剩余两个池空间先分配出来,不使用。DSP 端(DSP1)对共享内存 dataln 的数据进行 FFT 幅值运算,并把结果存入共享内存 dataOut,ARM 端(Cortex-A15_0)读取共享内存 dataOut 的数据并写入到 FFTData文件。
DSP 端创建一个 message 来传递信息,ARM 端使用 CMEM 创建4个空间用于数据的存储,“msg->dataIn”和“msg->dataOut”分别指向这其中两个空间的首地址。线性调频信号(LFM)模拟数据存入共享内存 dataIn,并发送消息至 DSP 端。DSP 端接收到消息后,读取共享内存 dataIn的数据进行 FFT 幅值运算,并把结果存入共享内存dataOut,同时发送消息至 ARM 端。ARM 端接收到消息后,从共享内存 dataOut 中读取数据并写入 FFTData 文件。最后发送ShutDown 消息到 DSP 端,DSP 端接收到该消息后,将信息回传至 ARM 端,关闭自己并重新初始化。