[TI] 以C6678为例的多核DSP通信方法总结
1. 写在前面
最近忙于使用多核 DSP 进行开发,型号为 FTM6678。但这款 DSP 和 TI 的 C6678 相似,甚至于很多功能都相同。开发过程中涉及到了很多数据的通信,包括核内通信与核间通信。因此本文拟对这些通信方法进行一个大致的描述。
2. 核内通信
完成核内通信需要使用到 SYS/BIOS 模块。概念参考:TI-RTOS Kernel (SYS/BIOS) User's Guide 的 Chatper4。<span style="color:red;font-weight:bold;">强烈建议去阅读手册,本文只是提供了一个方法的概述。
核内通信用于在单核之间进行通信。在使用 c 语言开发的过程中,核内通信可以帮助完成多个 .c 文件或者任务的数据交互与同步。
下表展示了核内通信的模块及其简短描述:
| Module | Description |
|---|---|
| Semaphore | 用于任务间同步和互斥的信号量。 |
| Event | 线程间通信和同步的事件机制。 |
| Gates | 防止并发访问临界区的锁。 |
| Mailboxes | 任务间消息传递的机制。 |
| Queues | SYS/BIOS 中队列的实现。 |
2.1. Semaphore
又称信号量,与操作系统中的信号灯功能一致,可以进行任务间的同步和互斥。信号量对象是一个非负数,其创建分为静态创建和动态创建两种形式,下述以最简单的二进制信号量为例描述创建过程,更具体的可查看手册解决。
2.1.1. 静态创建
静态创建一般都在 .cfg 文件下创建。以下代码创建了一个名为 hSem 的、初值为 0 的二进制信号量:
var Semaphore = xdc.useModule('ti.sysbios.knl.Semaphore'); // Necessary include
var SemParam = new Semaphore.Params;
SemParam.instance.name = "hSem";
SemParam.mode = Semaphore.Mode_BINARY;
Program.global.hSem = Semaphore.create(0, SemParam); // Initialize to 0
2.1.2. 动态创建
动态创建一般都在 .c 或 .cpp 文件下创建。以下代码创建了同样的 hSem 信号量。
#include <ti/sysbios/knl/Semaphore.h> // Must be included
Semaphore_Handle hSem;
Semaphore_Params semParams;
Semaphore_Params_init(&semParams); // Necessary
semParams.mode = Semaphore_Mode_BINARY;
hSem = Semaphore_create(0, &semParams, NULL);
2.1.3. 等待和释放信号量
Semaphore_post 相当于 Linux 操作系统中的 V 原语,释放一个信号量资源;Semaphore_pend 相当于 Linux 操作系统中的 P 原语,申请一个信号量资源。
Void Semaphore_post(Semaphore_Handle sem); // Signal a semaphore
Bool Semaphore_pend(Semaphore_Handle sem, UInt timeout); // Wait for a semaphore
2.2. Event
Events(事件)提供了在线程中通信和同步的方法,类似于信号量,但是事件允许你指定多个条件(即“事件”),这些条件必须全部满足后,等待的线程才会被唤醒并继续执行。
2.2.1. 静态创建
在 .cfg 下创建:
var Event = xdc.useModule("ti.sysbios.knl.Event");
Program.global.myEvent = Event.create();
2.2.2. 动态创建
在 .c 或 .cpp 文件下创建:
Event_Handle myEvent;
Error_Block eb;
...
Error_init(&eb);
/* Default instance configuration params */
myEvent = Event_create(NULL, &eb);
if (myEvent == NULL) {
System_abort("Event create failed");
}
2.2.3. 等待和释放事件
事件同样使用 pend 和 post 来进行相应操作,不同的是必须指定具体操作的是哪个事件。
UInt Event_pend(Event_Handle event, UInt andMask, UInt orMask, UInt timeout);
Void Event_post(Event_Handle event, UInt eventIds);
讲解一下 andMask 以及 orMask:前者代表“全部必须发生”的事件位掩码,后者代表“任一发生即可”的事件位掩码。
注意:只有任务才能 pend;软硬件中断及任务均可 post。
可以和后续要介绍的 Mailboxes 联动,从而避免 CPU 空转。
2.3. Gates
Gates(门,又称锁)是用来防止并发访问临界区的手段,对应 Linux 操作系统中的锁。如果一段代码正在操作全局变量、共享缓冲区、硬件寄存器等“公共资源”,Gates 可以保证它一次性做完、不被打断。
2.3.1. 基于抢占式的锁实现
三种锁,按关闭的抢占源从“最狠”到“最轻”排列:
GateHwi:
关/开 硬件中断(整个 CPU 屏蔽中断)。
任何线程(Task、Swi、Hwi)都能用,但会把系统中断延迟压到最小,所以临界区必须极短。
GateSwi:
关/开 软件中断(Swi 被禁止抢占)。
只允许 Task 和 Swi 使用,Hwi 里不能用。比 GateHwi 轻,中断仍能响应。
GateTask:
关/开 任务调度(Task 禁止抢占)。
只能在 Task 环境里用,Hwi/Swi 里禁止调用。最轻量级,只防其它 Task 抢 CPU。
2.3.2. 基于信号量的锁实现
GateMutex:
用 二进制信号量 当锁。
可能会阻塞(拿不到锁就挂起)。
所以只能用于 Task,绝对不能在 Swi 或 Hwi 里用(它们不能阻塞)。
功能:保证只有一个 Task 能进临界区。
GateMutexPri:
是带优先级继承的互斥锁。
解决 优先级反转 问题。
优先级继承:低优先级 Task 临时“升级”成高优先级,尽快放锁。
同样只能用于 Task,不能用于 Swi/Hwi。
这里举个例子说明优先级反转:低优先级的 Task 先拿到了锁。此时高优先级任务被调度,同时高优先级任务也需要获得锁资源,但锁资源被低优先级持有,故高优先级需要等待低优先级使用完成。此时又来了一个中优先级的任务,它不需要锁资源,故可以直接抢占 CPU 。此时高优先级任务便需要等待中、低优先级两个任务。
2.3.3. 动态创建
以 GateHwi 为例:
UInt gateKey;
GateHwi_Handle gateHwi;
GateHwi_Params prms;
Error_Block eb;
...
Error_init(&eb);
GateHwi_Params_init(&prms);
gateHwi = GateHwi_create(&prms, &eb);
if (gateHwi == NULL) {
System_abort("Gate create failed");
}
2.3.4. 加锁和开锁
一个简单的加锁示例。
/* Simultaneous operations on a global variable by multiple
* threads could cause problems, so modifications to the global
* variable are protected with a Gate. */
gateKey = GateHwi_enter(gateHwi);
myGlobalVar = 7;
GateHwi_leave(gateHwi, gateKey);
2.4. Mailboxes
又称邮箱,可用于将缓冲区从一个任务传递到同一处理器上的另一个任务。一个邮箱实例可以被多个读取器和写入器使用。Mailboxes 会将缓冲区中的内容复制到固定大小的内部缓冲区中(内部缓冲区是在创建实例时事先定义的)。
写入时数据从本地缓冲区复制到内部缓冲区,读取时数据从内部缓冲区复制到本地缓冲区。所以可以认为 Mailboxes 传递的是数据副本。
Mailboxes 可以对数据传输进行流量控制。
2.4.1. 创建与删除
可以在 .c 或者 .cpp 中调用以下函数,也可以通过 .cfg 文件执行相关操作。
Mailbox_Handle Mailbox_create(SizeT bufsize,
UInt numBufs,
Mailbox_Params *params,
Error_Block *eb)
Void Mailbox_delete(Mailbox_Handle *handle);
2.4.2. 写入和读取
写入使用 Mailbox_post() :
Bool Mailbox_post(Mailbox_Handle handle,
Ptr buf,
UInt timeout);
读取使用 Mailbox_pend() :
Bool Mailbox_pend(Mailbox_Handle handle,
Ptr buf,
UInt timeout);
2.4.3. 补充
通过配置 readerEvent、readerEventId、writerEvent、writerEventId 可以将 Mailboxes 和 Event 联动,使得 Mailboxes 在等待读取/写入的同时,还能处理其它不同的事件。Event_pend() 的多路阻塞能力能避免 CPU 空转。
具体示例详见 TI-RTOS Kernel (SYS/BIOS) User's Guide 的 Section 4.2.1。
2.5. Queues
又称队列。Queues 在 SYS/BIOS 中是用双向链表来实现的,这样的好处是没有最大大小的限制,同时可以在任意地方插入或删除元素。
2.5.1. 动态创建
可以在 .c 或者 .cpp 中调用以下函数。
/* This structure can be added to a Queue because the first field is a Queue_Elem. */
typedef struct Rec {
Queue_Elem elem;
Int data;
} Rec;
Rec r1, r2;
Queue_Handle myQ;
r1.data = 100;
r2.data = 200;
// No parameters or Error block are needed to create a Queue.
myQ = Queue_create(NULL, NULL);
2.5.2. 写入和读取
使用 Queue_enqueue() 来进行写入操作:
// Add r1 and r2 to the back of myQ.
Queue_enqueue(myQ, &(r1.elem));
Queue_enqueue(myQ, &(r2.elem));
使用 Queue_dequeue() 来进行读取操作:
Rec* rp;
// Dequeue the records and print their data
while (!Queue_empty(myQ)) {
// Implicit cast from (Queue_Elem *) to (Rec *)
rp = Queue_dequeue(myQ);
System_printf("rec: %d\n", rp->data);
}
2.5.3. 遍历、插入和移除
遍历队列:
// Queue_next() and Queue_prev() return the next and previous elements in a Queue, respectively.
Queue_Elem *elem;
for (elem = Queue_head(myQ); elem != (Queue_Elem *)myQ; elem = Queue_next(elem)) {
...
}
插入和移除元素:
Queue_enqueue(myQ, &(r1.elem));
/* Insert r2 in front of r1 in the Queue. */
Queue_insert(&(r1.elem), &(r2.elem));
/* Remove r1 from the Queue. Note that Queue_remove() does not
* require a handle to myQ. */
Queue_remove(&(r1.elem));
2.5.4. 补充
Queue 提供了两种原子 API 操作,可以在操作队列的时候禁用中断。
Queue_get() 是 Queue_dequeue() 的原子操作,而 Queue_put() 是 Queue_enqueue() 的原子操作。
3. 核间通信
完成核间通信需要使用到 IPC 模块。概念参考:SYS/BIOS Inter-Processor Communication (IPC) User’s Guide 的 Chatper2。<span style="color:red;font-weight:bold;">强烈建议去阅读手册,本文只是提供了一个方法的概述。
核间通信就是多核 CPU 之间互相传数据、做同步的一套机制。
下表展示了核间通信的模块及其简短描述:
3.1. IPC Module
IPC 模块包含初始化和通信模块。所有使用到 IPC 模块的核都得调用 Ipc_start() 这一个函数。该函数将完成 IPC 模块的初始化,并同步所有活跃的核。
在调用 IPC 模块中的通信模块前需要在 main 函数中调用 Ipc_start()。Ipc_start() 会在 SharedRegion 0 创建资源管理表供其它 IPC 模块使用,所以 SharedRegion 0 必须对所有核可访问。
3.1.1. 配置
相关的配置需要在 .cfg 中进行。
Ipc = xdc.useModule('ti.sdo.ipc.Ipc');
// Configure how the IPC module synchronizes processors
Ipc.procSync = Ipc.ProcSync_ALL;
当设置 procSync 为 Ipc.ProcSync_ALL 时,所有调用 Ipc_start() 的核都会自动连接并完成同步。一般都使用这种,比较省事。
当设置为 Ipc.ProcSync_PAIR 时,需要使用 Ipc_attach() 显式连接所需的核。这种状态下可以自定义同步的顺序,以及节省资源。
当设置为 Ipc.ProcSync_NONE 时,Ipc_start() 在设置其他模块所需的对象之前不会同步任何处理器。这需要谨慎使用。
注意:
Ipc_attach() 需要是双向调用的,且 ID 大的核必须得等 ID 小的核对自身发起 Ipc_attach(),自身才能对其发起 Ipc_attach()。如处理器 2 只能在处理器 1 调用 Ipc_attach(2) 之后才能调用Ipc_attach(1)。
可以配置一个函数,使其在附加到远程处理器或从远程处理器分离时,除了执行默认操作外,还执行自定义操作。函数必须是非阻塞且能执行完成的。这些函数分别在 Ipc_attach() 的末尾和 Ipc_detach() 的开头运行。
var Ipc = xdc.useModule('ti.sdo.ipc.Ipc');
var fxn = new Ipc.UserFxn;
fxn.attach = '&userAttachFxn1'; // after attach, userAttachFxn1 is called.
fxn.detach = '&userDetachFxn1'; // before detach, userDetachFxn1 is called.
Ipc.addUserFxn(fxn, 0x1);
fxn.attach = '&userAttachFxn2';
fxn.detach = '&userDetachFxn2';
Ipc.addUserFxn(fxn, 0x2);
3.2. MultiProc
MultiProc 是 IPC 框架中的处理器 ID 管理模块,用于在多核/多处理器环境中唯一标识和识别各个处理器。该模块为上层通信组件(如 MessageQ、Notify、HeapBufMP、ListMP 等)提供统一的处理器寻址抽象,使应用层无需关心底层硬件核心编号、物理地址映射或启动顺序,即可实现跨核资源访问与消息传递。
为简便,这里只讨论同一个 cluster(处理器集群)下的方法和配置。多 cluster 的配置可以看官方手册。
3.2.1. 配置
分为静态配置和动态配置。
静态配置,适用于处理器角色固定、启动顺序确定的系统,比较常用。以某一个核的.cfg文件为例:
// config.bld 或 .cfg 配置文件
var MultiProc = xdc.useModule('ti.sdo.utils.MultiProc');
// 当前处理器名为 "DSP",Cluster 包含 ["VIDEO", "DSS", "DSP"]
// 自动分配:VIDEO=0, DSS=1, DSP=2
MultiProc.setConfig("DSP", ["VIDEO", "DSS", "DSP"]);
动态配置,运行时确定本地 ID:
// 配置阶段:仅定义 Cluster 成员,不指定本地名称
MultiProc.setConfig(null, ["CORE0", "CORE1", "CORE2"]);
// 启动早期(Ipc_start() 之前):根据硬件/协议设置本地 ID
UInt16 localId = getProcIdFromHardware(); // 用户自定义函数
MultiProc_setLocalId(localId);
3.3. MessageQ
又称消息队列。可以用来结构化传输和接收可变长度的消息。对于一个 MessageQ 对象,当创建时,仅有一个接收端,但可有多个发送端。
3.3.1. 一些关键特征
- 透明迁移:写/读线程可无感迁移至其他处理器,无需修改运行时代码;
- 超时控制:接收消息时可指定超时时间;
- 双向通信:接收端可识别发送端并直接回送消息;
- 零拷贝:支持消息零拷贝传递,减少数据复制开销;
- 任意队列:消息可存放在任意消息队列,灵活路由;
- 全线程友好:发送与接收可在任意线程类型(任务、中断、线程池等)中完成;
- 自定义通知:通知机制(信号量、回调、事件标志等)由应用决定,适应不同场景;
- QoS 池化:可按消息队列分配不同缓冲区池,实现服务质量分级与资源隔离。
3.3.2. 接收与发送
接收端可以调用以下 API:
// To create a messageQ object
MessageQ_create();
// Free message allocation
MessageQ_free();
// Get a message
MessageQ_get();
// Delete the MessageQ object
MessageQ_delete();
发送端可以调用以下 API:
// Open the MessageQ
MessageQ_open();
// For message allocation
MessageQ_alloc();
// Put a message
MessageQ_put();
// Close the MessageQ
MessageQ_close();
3.3.3. 配置与创建
配置在 .cfg 中配置:
var MessageQ = xdc.useModule('ti.sdo.ipc.MessageQ');
/*
Module-wide configuration properties you can set are as follows.
The default values are shown in the following statements.
See the IPC online documentation for details.
*/
// Maximum length of MessageQ names
MessageQ.maxNameLen = 32;
// Max number of MessageQs that can be dynamically created
MessageQ.maxRuntimeEntries = 10;
// Number of heapIds in the system
MessageQ.numHeaps = 0;
// Section name used to place the names table
MessageQ.tableSection = null;
只能在 .c 或 .cpp 中动态创建实例化对象:
// Name must be supplied. An unnamed MessageQ can't be opened.
MessageQ_Handle MessageQ_create(String name, MessageQ_Params *params);
// This function extracts the QueueId from the object.
UInt32 MessageQ_getQueueId(MessageQ_Handle handle);
// We usually create a MessageQ like this:
MessageQ_Handle messageQ = MessageQ_create(CORE0_MESSAGEQNAME, NULL);
// ID is very useful
MessageQ_QueueId QId_Core0 = MessageQ_getQueueId(messageQ);
3.3.4. 打开消息队列与分配空间
发送端需要打开接收端的消息队列才能向接收端发送消息。
// This function expects a name, which must match with the name of the created object.
Int MessageQ_open(String name, MessageQ_QueueId *queueId);
MessageQ_QueueId QId_Core0 = MessageQ_getQueueId(messageQ);;
Int status;
/* Open the remote message queue. Spin until it is ready. */
do {
status = MessageQ_open(CORE0_MESSAGEQNAME, &QId_Core0);
}
while (status < 0);
为消息分配空间,可使得消息在任意消息队列被发送。接收端接收后可进行消息复用或者释放。
MessageQ_Msg MessageQ_alloc(UInt16 heapId, UInt32 size);
塞入消息队列的消息结构体,第一个元素一定得是 MessageQ_MsgHeader 结构,以 MyMsg 结构体为例:
typedef struct MyMsg {
MessageQ_MsgHeader header; // Required, and no modified
SomeEnumType type // Can be any field
... // ...
} MyMsg;
一般来说, 为消息分配的空间会被放在 MessageQ_registerHeap() 函数注册的堆里面。以 SHAREREGION_MSM_ID = 0 的 SharedRegion 共享堆区域为例,使用该函数在其中注册一个 MessageQ 用的堆。
Int MessageQ_registerHeap(Ptr heap, UInt16 heapId);
/* Register this heap with MessageQ */
#define MESGQ_SHAREREGION_MSM_ID (0)
Int status1 = MessageQ_registerHeap(SharedRegion_getHeap(SHAREREGION_MSM_ID),
SHAREREGION_MSM_ID);
// Heaps can be unregistered via MessageQ_unregisterHeap().
应用程序可以使用多个堆来允许应用程序调节其消息使用。
例如,应用程序可以从快速片上内存堆中分配关键消息,从较慢的外部内存堆中分配非关键消息。此外,MessageQ使用的堆可以与其他模块和/或应用程序共享。
此外还有不在堆上分配消息空间的做法,参考手册的 2.3.4.2 小节,这里就不再说明了。
3.3.5. 发送与接收
当消息队列被打开,且消息所需空间已经被分配了,发送端便可用 MessageQ_put() 来往消息队列里送入消息。
Int MessageQ_put(MessageQ_QueueId queueId, MessageQ_Msg msg);
可以使用 MessageQ_setMsgPri() 函数设置消息的优先级,有 3 个优先级。
MessageQ对象在内部维护两个链表:正常和高优先级。正常优先级消息以FIFO方式放置在“正常”链表上。高优先级消息以FIFO方式放置在“高优先级”链表上。紧急消息放置在高链表的开头:
// - MessageQ_NORMALPRI = 0
// - MessageQ_HIGHPRI = 1
// - MessageQ_URGENTPRI = 3
Void MessageQ_setMsgPri(MessageQ_Msg msg,
MessageQ_Priority priority)
接收端可以使用 MessageQ_get() 来获取消息,如果无消息,则会按设定的参数阻塞或跳过:
Int MessageQ_get(MessageQ_Handle handle,
MessageQ_Msg *msg,
UInt timeout)
3.4. ListMP
基于链表列表的模块。ListMP 使用共享内存为多个处理器提供一种共享、传递或存储数据缓冲区、消息或状态信息的方式。
ListMP 使用多处理器门来防止多个处理器同时访问同一链表。所有 ListMP 操作在处理器之间都是原子的。
3.4.1. 动态创建与访问
以下是在 .c 或 .cpp 文件中创建 ListMP 的实例:
ListMP_Params params;
GateMP_Handle gateHandle;
ListMP_Handle handle1;
/* If gateHandle is NULL, the default remote gate will be
automatically chosen by ListMP */
gateHandle = GateMP_getDefaultRemote();
ListMP_Params_init(¶ms);
params.gate = gateHandle;
params.name = "myListMP";
params.regionId = 1; // shared_region ID
handle1 = ListMP_create(¶ms, NULL);
创建 ListMP 实例的处理器必须根据其本地地址空间指定共享内存。此共享内存必须已由应用程序在 SharedRegion 模块中定义。
创建后可使用下列函数访问实例,ListMP_open() 仅在启用全局中断时调用:
while (ListMP_open("myListMP", &handle1, NULL) < 0) {
;
}
注意:
要从链表中添加/删除的 ListMP 元素必须存储在共享内存区域中。
链表必须位于共享列表的所有处理器的最坏情况高速缓存线边界上。
3.4.2. 其它常用 API
注意,要放入链表的结构体,其第一个元素必须是 ListMP_Elem 类型的字段,如:
typedef struct Tester {
ListMP_Elem elem;
Int scratch[30];
Int flag;
} Tester;
常用的 API,包括判空、取 / 放数据等如下所示,此处省略了输入参数:
// 测试 ListMP 是否为空
ListMP_empty()
// 取出并返回 ListMP 头部元素
ListMP_getHead()
// 取出并返回 ListMP 尾部元素
ListMP_getTail()
// 在当前位置插入元素
ListMP_insert()
// 返回 ListMP 中下一个元素(非原子操作)
ListMP_next()
// 返回 ListMP 中前一个元素(非原子操作)
ListMP_prev()
// 将元素压入 ListMP 头部
ListMP_putHead()
// 将元素压入 ListMP 尾部
ListMP_putTail()
// 从 ListMP 中间移除当前元素
ListMP_remove()
3.5. Heap*MP
Heap*MP 就是“多核共享内存堆”——让不同核都能安全地申请 / 释放同一块共享内存。共享内存堆一共有一下三种实现:
HeapBufMP:
固定大小块管理;一个实例只服务单一块尺寸,可多实例搭配不同尺寸。
HeapMultiBufMP:
“多桶”固定大小管理;一个实例支持最多 8 种块尺寸,自动选最小够用桶,桶空即失败。
HeapMemMP:
传统可变长堆;在一段连续共享内存里按需分配、释放任意大小的块。
3.5.1. 创建
以下示例均以 HeapBufMP 为例:
// 初始化参数结构体
HeapBufMP_Params_init(&heapBufMPParams);
// 指定共享内存区域索引(0 表示默认区域)
heapBufMPParams.regionId = 0;
// 为 NameServer 设置堆实例名称
heapBufMPParams.name = "myHeap";
// 每个块的对齐字节数(256 字节对齐)
heapBufMPParams.align = 256;
// 固定大小块的总数量
heapBufMPParams.numBlocks = 40;
// 单个块的字节大小
heapBufMPParams.blockSize = 1024;
// 使用系统默认门锁(NULL 表示默认)
heapBufMPParams.gate = NULL;
// 创建 HeapBufMP 实例
heapHandle = HeapBufMP_create(&heapBufMPParams);
// 将 HeapBufMP 注册到 MessageQ,供跨核消息分配使用
MessageQ_registerHeap(HeapBufMP_Handle_upCast(heapHandle), HEAPID);
3.5.2. 其它常用 API
以下示例均以 HeapBufMP 为例:
// 打开一个 HeapBufMP 实例
/* Open heap created by other processor. Loop until open. */
do {
status = HeapBufMP_open("myHeap", &heapHandle);
}
while (status < 0);
// 关闭一个实例
Int HeapBufMP_close(HeapBufMP_Handle *handlePtr);
// 删去一个实例
Int HeapBufMP_delete(HeapBufMP_Handle *handlePtr);
// 申请空间
Void* HeapBufMP_alloc(HeapBufMP_Handle handle, SizeT size, SizeT align);
// 释放空间
Void HeapBufMP_free(HeapBufMP_Handle handle, Ptr block, SizeT size);
// 查询 heap 状态
Void HeapBufMP_getExtendedStats(HeapBufMP_Handle handle, HeapBufMP_ExtendedStats *stats);
Void HeapBufMP_getStats(HeapBufMP_Handle handle, Ptr stats);
3.6. GateMP
GateMP 实例可以用于强制执行本地和远程上下文保护。也就是说,进入 GateMP 可以防止在同一处理器上运行的另一个线程的抢占,同时防止远程处理器进入同一闸门。GateMP 通常用于保护对共享资源的读/写,例如共享内存。
3.6.1. 动态创建
只能动态创建 GateMP.
GateMP_Params gparams;
GateMP_Handle gateHandle;
...
GateMP_Params_init(&gparams);
// localProtect 参数用来配置门对本地处理器的保护方式
gparams.localProtect = GateMP_LocalProtect_THREAD;
// remoteProtect 参数用来配置门对远程处理器的保护方式
gparams.remoteProtect = GateMP_RemoteProtect_SYSTEM;
gparams.name = "myGate";
// regionId 指示使用哪一个 shared_region
gparams.regionId = 1;
gateHandle = GateMP_create(&gparams, NULL);
3.6.2. 其它常用 API
以下展示了常用的 API 操作,此处省略输入参数:
// 打开一个实例
status = GateMP_open("myGate", &gateHandle);
if (status < 0) {
System_printf("GateMP_open failed\n");
}
// 关闭一个实例
GateMP_close();
// 删去一个实例
GateMP_delete();
// 获得锁
GateMP_enter();
// 释放锁
GateMP_leave();
// 查询锁状态
GateMP_query();
3.6.3. 补充
其它核间通信模块也可使用 GateMP 来保护自己的资源。
3.7. Notify
Notify 用于接受通知并产生对应响应。
接受通知的核可注册相应回调函数,并通过为回调函数注册事件来响应对应的通知。
3.7.1. 注册回调函数
回调函数的注册必须按照以下结构:
/*
procId:核的逻辑id
lineId:中断线编号,通常为0
eventId:事件id,用来区分事件
arg:可用来传参
payload:4字节小数据
*/
Void cbFxn(UInt16 procId, UInt16 lineId, UInt32 eventId, UArg arg, UInt32 payload);
3.7.2. 将回调函数绑定到事件上
一般使用以下 API 完成绑定:
// 将一个或多个回调函数绑定到事件上
// 若需要多事件绑定,只需重复调用
Int Notify_registerEvent(UInt16 procId,
UInt16 lineId,
UInt32 eventId,
Notify_FnNotifyCbck fnNotifyCbck,
UArg cbckArg
);
// 将一个回调函数绑定到事件上
Int Notify_registerEventSingle(UInt16 procId,
UInt16 lineId,
UInt32 eventId,
Notify_FnNotifyCbck fnNotifyCbck,
UArg cbckArg
);
// 示例:
Int status;
armProcId = MultiProc_getId("ARM");
Ipc_start();
/* Register cbFxn with Notify. It will be called when ARM
* sends event number EVENTID to line #0 on this processor.
* The argument 0x1010 is passed to the callback function. */
status = Notify_registerEvent(armProcId, 0, EVENTID,
(Notify_FnNotifyCbck)cbFxn, 0x1010);
if (status < 0) {
System_abort("Notify_registerEvent failed\n");
}
3.7.3. 远端核发送
远端核可以发送相应的通知(事件)来触发上述定义的回调函数。
使用下述 API:
/*
waitClear参数为TRUE表示:如果已发送过相同事件ID的先前事件,则Notify驱动程序会等待确认先前事件已接收。
*/
Int Notify_sendEvent(UInt16 procId,
UInt16 lineId,
UInt32 eventId,
UInt32 payload,
Bool waitClear
);
3.7.4. 补充
特定事件可通过调用 Notify_disableEvent() 和 Notify_enableEvent() 函数进行禁用或启用。
3.8. SharedRegion
SharedRegion 模块会创建共享内存区域查找表。查找表包含核对系统中每个共享区域的视图。如果某个核无法查看某个共享内存区域,则该共享内存区域对该核无效。每个核都有自己的查找表,存放在本地的内存中。
3.8.1. 本地查找表
一般在 .cfg 中配置 SharedRegion 的查找表。
| 字段 | 类型 | 含义 |
|---|---|---|
base |
Ptr |
该区域在本核视角下的基地址(物理或映射后虚拟地址),不同的核可能不一样。 |
len |
SizeT |
区域总长度,所有核必须填同一值。 |
ownerProcId |
UInt16 |
负责管理该区域的 MultiProc ID;owner 会创建 HeapMemMP,其余核只 open。 |
isValid |
Bool |
本核能否访问;TRUE=可见,FALSE=不可见。 |
cacheEnable |
Bool |
本核对该区域是否使能 cache。 |
cacheLineSize |
UInt16 |
区域对应的 cache line 大小,全系统必须相同。 |
createHeap |
Bool |
是否在该区域自动创建 HeapMemMP 堆。 |
name |
String |
区域名字,仅调试/日志使用。 |
所有字段均存于本地私有内存。
此外,属性 SharedRegion.numEntries 表示系统最多能支持多少个核来访问共享区域。只能在 cfg 脚本或 .cfg 文件里改,例如:SharedRegion.numEntries = 4。所有配置 SharedRegion 的核对于该属性的赋值都需保持一致。
注意事项:
- SharedRegion.numEntries属性在所有处理器上必须相同。
- SharedRegion 指针的大小为32位宽。
- SharedRegion 查找表必须至少包含1个条目,才能进行地址转换。
- 从单个处理器的角度来看,共享内存区域不得相互重叠。
- IPC_start() 使用索引为 0 的 SharedRegion 创建资源管理表,供其他IPC模块内部使用。因此,SharedRegion 0 必须可供所有处理器访问。应用程序也可以使用 SharedRegion 0 ,但必须注意内存限制。
以下展示了在 .cfg 中配置 SharedRegion 的示例:
var SharedRegion = xdc.useModule('ti.sdo.ipc.SharedRegion');
SharedRegion.cacheLineSize = 32;
SharedRegion.numEntries = 4;
SharedRegion.translate = true;
var SHAREDMEM = 0x0C000000;
var SHAREDMEMSIZE = 0x00200000;
SharedRegion.setEntryMeta(0,
{
base: SHAREDMEM,
len: SHAREDMEMSIZE,
ownerProcId: 0,
isValid: true,
cacheEnable: true,
cacheLineSize: 128,
createHeap: true,
name: "internal_shared_mem"
}
);
// 如果 createHeap 字段设置为 true,则会在 SharedRegion 中创建 HeapMemMP 实例。
动态配置查找表操作不常用,这里就不展开叙述了。
3.8.2. 分配 Shared Region 中的内存空间
由于 SharedRegion 0 对所有核可见,故可以在上面申请内存空间。如下所示:
buf = Memory_alloc(SharedRegion_getHeap(0), sizeof(Tester) * COUNT, 128, NULL);
3.8.3. 获取关于 Shared Region 的信息
以下示例展示了如何获取相关信息:
/* 1. 先把地址转成 32 位“通用指针” */
SharedRegion_SRPtr srptr;
UInt16 id;
id = SharedRegion_getId(addr); /* 查这块地址属于哪个共享区 */
srptr = SharedRegion_getSRPtr(addr, id); /* 拿到 32 位通用指针 */
addr = SharedRegion_getPtr(srptr); /* 把通用指针再换回本地地址 */
/* 2. 其余常用 API */
id = SharedRegion_getIdByName("MyRegion"); /* 用名字查id */
IHeap_Handle heap = SharedRegion_getHeap(id); /* 拿到这块区的堆句柄 */
Ptr buf = Memory_alloc(heap, 1024, 0, NULL); /* 就能在共享区里申请内存 */
Memory_free(heap, buf, 1024);
Bool cacheOn = SharedRegion_isCacheEnabled(id); /* 查本区是否开缓存 */
UInt16 clSize = SharedRegion_getCacheLineSize(id); /* 拿缓存行大小,方便 flush */

浙公网安备 33010602011771号