1、简介
IPC: Inter-Processor Communication 处理器间通信,指提供多处理器环境中的处理器之间的通信、相同处理器不同线程间的通信。包括数据传递、数据流和链表。
IPC可用于以下通信:
- 在同一处理器上的其他线程
- 在其他处理器上运行的SYS/BIOS线程
- GPP处理器上运行的SysLink的线程(例如Linux)
IPC传输分为两种:
- QMSS(队列管理器):发送任务和内核之间的数据
- SRIO IPC:发送任务、内核和芯片之间的数据
2、模块
MultiProc
许多IPC模块需要在多处理器环境中识别处理器。MultiProc在一个模块中集中管理处理器id。因为这配置几乎是普遍要求,大多数IPC应用程序需要提供这个模块的配置。MultiProc可以在cfg文件中静态配置,也可以在程序中动态配置,一般都是用静态配置。 var MultiProc = xdc.useModule(‘ti.sdo.utils.MultiProc’); 调用模块,相当于是获取一个接口,然后用接口进行配置
静态设置(cfg设置)
/*********在cfg文件中配置************************/
MultiProc.numProcessors = 8; //设置系统的核数为8,这里的值就是nameList数组的总长度
MultiProc.setConfig(null, ["CORE0", "CORE1",
"CORE2", "CORE3", "CORE4", "CORE5", "CORE6", "CORE7"]); //设置各个核的名字,其ID是根据其名字在setConfig第二个参数数组中的位置+BaseID来确定的,BaseID默认是0,若位置为0,则ID就是0,其在nameList数组的位置就是0(nameList数组和setConfig中的数组不是同一个)
/**************在一个系统中有两个DSP,总共16个,这个16个核之间要进行通信,可以进行如下设置,使得各个核的ID不一样************************/
/*For first C6678 device:*/
var MultiProc = xdc.useModule('ti.sdo.utils.MultiProc');
MultiProc.baseIdOfCluster = 0; //设置baseid
MultiProc.numProcessors = 16;//nameList总共有16个元素
MultiProc.setConfig(null, ["CORE0", "CORE1", "CORE2", "CORE3",
"CORE4", "CORE5", "CORE6", "CORE7"]);
/***DSP0中的核name占据了nameList的0~7位*****/
/*For second C6678 device:*/
var MultiProc = xdc.useModule('ti.sdo.utils.MultiProc');
MultiProc.baseIdOfCluster = 8;//设置baseid
MultiProc.numProcessors = 16;
MultiProc.setConfig(null, ["CORE0", "CORE1", "CORE2", "CORE3",
"CORE4", "CORE5", "CORE6", "CORE7"]);
/***DSP1中的核name占据了nameList的8~15位*****/
动态设置
MultiProc_getNumProcessors();//获取MultiProc.numProcessors的值
MultiProc_self()//获取当前核的id
MultiProc_getName(id)//根据id获取核的name
MultiProc_getId("name")//根据name获取ID
IPC
IPC的启动很简单,只需要在函数中调用Ipc_start()就行。该函数只能被调用一次,除非返回值是Ipc_E_NOTREADY。它表示ShareRegion 0 无效或者还没有建立,所以Ipc_start可能需要再次调用,一旦它成功启动,后面调用的返回值是Ipc_S_ALREADYSETUP。
IPC的配置是在cfg文件中完成的:
var Ipc = xdc.useModule('ti.sdo.ipc.Ipc');
Ipc.procSync = Ipc.ProcSync_ALL;
Ipc.ProcSync_ALL :调用ipc_start还将在内部调用ipc_attach同步所有核,应用程序不应该再调用Ipc_attach。如果在一个设备上的所有的IPC核都是同时启动的话,可以选择这个同步方式。
Ipc.ProcSync_PAIR ipc_start不会调用ipc_attach,所有此时需要在代码中由应用程序调用ipc_attach;
Ipc.ProcSync_NONE Ipc_start将与ProcSync_PAIR工作完全一样,但是ipc_attach不会同步远程的核;
核的同步要按照ID号从小到大的顺序进行,比如当前核必须先同步核0,才能同步核1。两核之间的相互同步必须先满足ID号小的先同步ID号大的,比如核2需要等到核1调用了Ipc_attach(2)之后才能调用Ipc_attach(1) 。
Notify
/*注册事件,和回调函数进行绑定
* @param[in] procId 远端的核号
* @param[in] lineId Line id (大部分系统为0)
* @param[in] eventId 事件ID 最大31
* @param[in] fnNotifyCbck 回调函数
* @param[in] cbckArg 回调函数的参数 */
Int Notify_registerEvent(UInt16 procId,
UInt16 lineId,
UInt32 eventId,
Notify_FnNotifyCbck fnNotifyCbck,
UArg cbckArg);
/*发送事件
* @param[in] procId 目的的核号
* @param[in] lineId Line id
* @param[in] eventId 事件ID
* @param[in] payload 和事件一起发送的数据.是一个int型的数据
* @param[in] waitClear 如果为TRUE,则当前发送的事件会等待上一个相同ID的事件被目标核处理掉,如果为FALSE,则被挂起的事件中与最近发送的事件的ID相同的事件会被覆盖掉。如果notify设备是通过FIFO传递事件的,那么当FIFO满的时候,如果为TRUE则会一直等待到FIFO有空间,如果为FALSE则会返回Notify_E_FAIL*/
Int Notify_sendEvent(UInt16 procId,
UInt16 lineId,
UInt32 eventId,
UInt32 payload,
Bool waitClear);
/****回调函数的形式******************************************************/
Void myFxn2(UInt16 procId, UInt16 lineId, UInt32 eventNo, UArg arg,
UInt32 payload)
MessageQ
MessageQ同Notify模块一样,也是用于多核之间的通信的,不过不同的是,Notify模块更加侧重于通知,其只能传递一个参数,而MessageQ却可以传递变长度的消息,更侧重于传递消息,另外不同线程间的消息是独立的,例如对于每个MessageQ来说,存在一个读者却可能有多个写者。
MessageQ模块的主要特点:
1. 实现了处理期间变长消息的传递,所需要传递的消息一般超过32bit;
2. 其消息的传递都是通过操作消息队列来实现的;
3. 每个消息队列可以有多个写者,但只能有一个读者,而每个任务(task)可以对多个消息队列进行读写;
4. 一个宿主在准备接收消息时,必须先创建消息队列,而在发送消息前,需要打开预定的接收消息队列;
HeapBufMP:固定大小的内存管理器,其分配的所有缓冲区都是一样的,当然也可以通过不同HeapBufMP实例来管理不同的大小的缓冲区。
HeapMultiBufMP:每个HeapMultiBufMP支持8个不同大小的缓冲区。当一个分配需求被发送,HeapMultiBufMP的实例从不同大小的待分配缓冲区中,选择一个能满足要求的最小缓冲区。如果待分配缓冲区为空,那么这个分配就失败了。
HeapMemMP:这是个能分配变长大小的内存管理器。另外HeapMemMP管理共享内存区(Shared memory)的一个缓冲区。
在cfg文件中开启MessageQ
/****需要HeapBufMP模块来给消息分配空间***************************************/
var MessageQ = xdc.useModule('ti.sdo.ipc.MessageQ');
var HeapBufMP = xdc.useModule('ti.sdo.ipc.heaps.HeapBufMP');
创建MessageQ对象
/***MessageQ不是共享内存,只能有一个读者,所以要每个核都去创建自己的MessageQ***/
MessageQ_Handle MessageQ_create(String name, const MessageQ_Params *params);
消息创建好之后,需要给消息分配堆空间。消息其实就是一个结构体。但是其结构体的第一个元素必须是 MessageQ_MsgHeader类型的。
创建一个堆对象
/*因为测试例程只需要发送一个消息就可以了,所以HeapBufMP就只分配一个消息块*/
HeapBufMP_Params_init(&heapBufParams);
heapBufParams.regionId = 0;
heapBufParams.name = HEAP_NAME;
heapBufParams.numBlocks = 1; //分配的块数
heapBufParams.blockSize = sizeof(Msg);//块的大小,Msg是创建的消息结构体
heapHandle = HeapBufMP_create(&heapBufParams);
创建好对象后,需要给堆对象注册一个id,后面给消息分配内存的时候,就直接调用ID
/* @param[in] heap 要注册id的堆对象*/
/* @param[in] heapId 得到的堆ID*/
Int MessageQ_registerHeap(Ptr heap, UInt16 heapId);
根据堆ID给堆分配空间
//返回一个msg,这个msg就是用来传递消息的。
MessageQ_Msg MessageQ_alloc(UInt16 heapId, UInt32 size);
分配完空间得到msg后就可以发送了
/*通过接收核创建消息时注册的名称来打开消息队列,并返回消息队列的ID
根据消息队列ID,将消息发送给消息队列*/
Int MessageQ_open(String name, MessageQ_QueueId *queueId); //打开队列
Int MessageQ_put(MessageQ_QueueId queueId, MessageQ_Msg msg);//发送消息queueId就是打开消息的时候得到的,msg就是堆内存分配的时候返回的。
消息接收
Int MessageQ_get(MessageQ_Handle handle, MessageQ_Msg *msg, UInt timeout);
ShareRegion
SharedRegion模块是一个共享区域,特别是对于多处理器环境下,SharedRegion模块就是用于让一个内存区域能被不同处理器共享并操作。这个模块会给每个处理器上创建一个共享内存区域查找表,这个查找表保证各个处理器能查看到系统内的所有共享区域。查找表中共享内存区域在所有查找表中的区域ID是一致的,在运行时,查找表可以通过共享区域ID及共享区域名称来快速查找共享区域
通过cfg配置共享区域
var SharedRegion = xdc.useModule('ti.sdo.ipc.SharedRegion');
SharedRegion.numEntries = 4; //共享区域的最大个数
SharedRegion.translate = true; //是否需要进行地址转换
/* Shared Memory base address and length */
var SHAREDMEM = 0x88000000;
var SHAREDMEMSIZE = 0x00300000;
SharedRegion.setEntryMeta(0,//共享区域的ID号
{ base: SHAREDMEM, //共享区域的基地址
len: SHAREDMEMSIZE, //共享区域大小
ownerProcId: 0, //共享区域所有者的核ID
isValid: true, //对于当前核,该区域是否有效
name: "SR0", //区域的名称
});
1)base:区域的基地址,不同处理器其基地址可以是不同的。
2)len:区域的长度,同一个共享区域在所有处理器的查找表中的长度应该是相同的。
3)ownerProcID:管理该区域的处理器ID,如果存在区域所有者,这个区域所有者(owner)就是创造HeapMemMp实例的,而其他核打开这个实例。
4)isValid:表明该区域在当前核上是否可用,判断当前核能否使用此共享区域的。
5)cacheLineSize:这个值在所有核的查找表中都应该是相同的
6)createHeap:表明是否需要给当前区域创建一个堆。
7)name:区域的名称。
共享内存的使用
heaphandle = (IHeap_Handle)SharedRegion_getHeap(regionId); // 通过区域ID获得共享区域的堆句柄
buf = Memory_alloc(heap, size, align, NULL); // 通过堆句柄分配区域内存
地址转换
共享内存的地址在各个核上会被映射到不同的地址空间,因此我们需要进行地址转换。
/*先将本地分配的共享内存得到一个本地地址转换,通过SharedRegion_getSRPtr()将本地地址转换成共享内存地址,然后通过MessageQ将共享内存地址发送到其他核。其他核通过SharedRegion_getPtr()来得到共享内存对应的本地地址,然后就可以通过本地地址来访问共享内存的内容了*/
SharedRegion_getSRPtr() //根据给定的本地指针及区域ID来获得当前共享区域指针
SharedRegion_getPtr() //根据共享区域指针来获得本地指针
能否使用此共享区域的。
5)cacheLineSize:这个值在所有核的查找表中都应该是相同的
6)createHeap:表明是否需要给当前区域创建一个堆。
7)name:区域的名称。
共享内存的使用
```c
heaphandle = (IHeap_Handle)SharedRegion_getHeap(regionId); // 通过区域ID获得共享区域的堆句柄
buf = Memory_alloc(heap, size, align, NULL); // 通过堆句柄分配区域内存
地址转换
共享内存的地址在各个核上会被映射到不同的地址空间,因此我们需要进行地址转换。
/*先将本地分配的共享内存得到一个本地地址转换,通过SharedRegion_getSRPtr()将本地地址转换成共享内存地址,然后通过MessageQ将共享内存地址发送到其他核。其他核通过SharedRegion_getPtr()来得到共享内存对应的本地地址,然后就可以通过本地地址来访问共享内存的内容了*/
SharedRegion_getSRPtr() //根据给定的本地指针及区域ID来获得当前共享区域指针
SharedRegion_getPtr() //根据共享区域指针来获得本地指针