Davinci的异构多核间通信基础组件SysLink
绪论:Davinci中的多核系统一般由GPP+DSP构成,也就是所谓的异构多核(同构是指内部核的结构是相同的,而异构是指内部的核结构是不同的),为了为异构多核处理器间提供高效的异构多核协作,需要建立异构多核间的通信机制。在TI提供的异构多核间通信组件SysLink中,核间通信机制为用户提供了多种实现方法。下面的内容将介绍SysLink架构、特性和相关的API。
关键词
SysLink工具包为异构多核之间的通信提供基础开发接口,使得多核系统之间更方便的交换信息。在多核异构系统中,每个核心运行的操作系统和所处地位各不相同。所运行的OS可以是HLOS,如Linux、WinCE等,也可以是RTOS如SYS/BIOS或者QNX。异构多核系统中主处理器(Host Processor)肩负着控制从处理器(Slave Processor)的责任。
广义上的SysLink包含了运行在HLOS上的SysLink和运行在RTOS上的IPC。其基本架构如下图所示:
SysLink工具包主要针对于嵌入式操作系统中的应用,主要由以下组件构成:
系统管理(System Manager)
处理器管理(Processor Manager——PM)
核间通信(Inter-Processor Communication——IPC)
其他模块(Utility Modules)
1、系统管理
系统管理模块(IPC module)为方便多核管理提供了简单快捷的方法,此外也为系统资源(e.g.系统内存)的管理提供了接口。
IPC 模块提供的功能包括:
SysLink系统初始化(syslink_setup()、syslink_destroy())并为其他SysLink组件分配内存,包括IPC模块和PM模块(MemoryOS_setup()、Ipc_setup(&config))
系统配置:任何系统级别的配置信息是由系统管理;
2、处理器管理
ProcMgr模块为从处理器提供了以下services:具体例子可以参见DVRRDK_xx.xx.xx.xx\dvr_rdk\mcfw\src_linux\utils\fw_load中firmware load的例子。
boot-loading从处理器
读写从处理器的内存区
从处理器电源管理
因此该模块为以上services提供了以下接口:
Loader:处理器的Loader接口有多种实现实现方式,被写入的文件形式可能是如COFF、ELF、动态loader(不太清楚这是啥)或者自定义类型的文件等等;
Power Manager:考虑到处理器管理模块的通用性并且希望电源管理模块可以自定义,在SysLink中电源管理是可嵌入处理器管理的独立模块;
Processor Manager:为处理器提供了加载、MMU管理(A8支持)、读写从处理器内存等接口。
Loader流程图:
Processor Manager系统框架图如下
在SysLink系统中,为了方便管理,每个处理器都会被编码(即Processor ID);如图中所示,在该系统中使用了硬件抽象层来屏蔽底层硬件差异,这样做的好处就是为不同的底层硬件提供了通用的软件接口。
注:
SysLink中从处理器的Loader文件理论上支持多种格式,在SysLink Release版本中主要支持COFF和ELF。在TI的编译系统中,可以以可执行文件的后缀名来区别COFF文件和ELF文件,后缀名中带有‘e’的是ELF(如:xxx.xe64P),不带‘e’的是COFF文件(如:xxx.x64P)。
当前的ELF Loader只支持顺序加载,即只有当一个从处理器加载并启动后才能去加载下一个从处理器,不支持并行加载。
3、处理器间通信协议(Inter-Processor Communication Protocols)
SysLink下定义了以下几种通信机制:
- Notify
- MessageQ
- ListMp
- GateMp
- HeapBufMp
- HeapMemMp
- FrameQ(通常用于raw 视频数据)
- RingIO(通常用于音频数据)
复制代码
这些通信机制的接口都有一下几个共同点:
所有IPC通信机制的接口都由系统规范化的命名;
在HLOS端,所有IPC 接口都有<Module>_setup() and <Module>_destroy() API用于初始化或者销毁相应的IPC Module;部分初始化还需要提供配置接口,<Module>_config();
所有的实例化都需要使用<Module>_create()来创建,使用<Module>_delete()来删除;
在更深层次使用IPC时需要用API <Module>_open()来获取handle,在结束使用IPC时需要用API <Module>_close()来回收handle;
IPC的配置多数都是在SYS/BIOS下完成配置的,对于支持XDC配置的则可以使用静态配置方法;
每个IPC模块都支持Trace信息用于调试,而且支持不同的trace等级;
部分IPCs提供了专门的APIs来用于提取分析信息;
3.1、Notify
Notify组件将硬件中断抽象成多组逻辑事件,是一种简单快捷的发送低于32bit信息的通信方式。
Notify组件提供了以下接口:
初始化并配置Notify组件;Notify_attach();
注册/注销事件;Notify_registerEvent()/Notify_unregisterEvent()/Notify_registerEventSingle()/Notify_unregisterEventSingle()
发送带参数的事件给某处理器;Notify_sendEvent()
通过回调函数接收事件;Notify_FnNotifyCbck()
使能/禁用事件;Notify_diableEvent()/Notify_enableEvent()
其他逻辑接口;Notify_eventAvailable()/Notify_intLineRegistered()/Notify_numIntLines()/Notify_restore()
注:
同一个中断号可以注册多个事件,同一个事件可以有多个回调函数或者多个宿主(可以是处理器、线程或者任务),事件被触发后所有宿主都会被唤醒;
一个事件可以接收多个宿主发送来的通知(notification),事件所携带的参数最大支持32bit;
事件是有优先级的,EventId越小优先级越高,事件0的优先级最高,随着EventId增大优先级依次递减;当多个事件被触发,优先级最高的会最先响应;
Notify模块使用硬件中断,因此不能被频繁调度。
Notify组件常用于传递附带消息少于32bit的场景,如信令传递、buffer指针传递等。在信令传递时使用高优先级的事件,如事件0;而在传递buffer指针是可以使用低优先级的事件,如事件30等。
在Notify_sentEvent() API中带有参数waitClear,该参数为可选参数,如果waitClear为TRUE,这就意味着多宿主事件无法及时响应,必须等待前一宿主事件结束后才能响应下一宿主;如果waitClear为FALSE,最好不要为事件附带参数,否则多宿主事件可能会由于消息被覆盖而出现丢消息的现象。该API最好不要在中断服务程序(ISR)中调用(特别是waitClear = TRUE时),否则会导致中断调度出现异常(表现之一:高优先级的中断响应会延迟);此外该API不能再使用GateMP模块锁保护的程序段中调用,否则可能会导致操作系统死锁。
由于其他模块使用了Notify机制,因此在SysLink中预留了部分事件号,这部分事件号用户需要慎重选用(如果你没有使用其他组建的话,可以考虑占用这部分事件号),在注册事件前可以使用Notify_eventAvailable()来检查该事件是否可用,即该中断号上的该事件号是否被注册。
3.2、MessageQ
MessageQ,顾名思义,基于队列的消息传递,可不是MaggieQ噢,哈哈。
MessageQ有以下特点:
实现了处理期间变长消息的传递;
消息的传递都是通过操作消息队列来实现的;
每个消息队列可以有多个写者,但只能有一个读者;每个任务(task)可以对多个消息队列进行读写;
一个宿主在准备接收消息时,必须先创建消息队列,而在发送消息前,需要打开预定的接收消息队列;
MessageQ组件常用在满足以下条件的场景中:
在消息传递中有多个写者,但仅有一个读者;
所需要传递的消息超过32bit,且长度可变;读写者的缓冲区大小相同;
处理期间需要频繁传递消息,在这种情况下,消息被依次放入队列,能保证不会丢消息;
消息队列为空时,调用MessageQ_get()获取消息时会被阻塞,直到消息队列被写入消息;
支持处理器间移动消息队列,在这种情况下,调用MessageQ_open()来定位队列位置,而消息传递部分代码不需要改动;
MessageQ组件提供了以下几个API:
消息队列初始化:MessageQ_Params_init()
消息队列创建/销毁:MessageQ_create()/MessageQ_delete(),create创建消息队列,并分配相应存储空间
消息队列打开/关闭:MessageQ_open()/MessageQ_close(),open时会返回远程处理器上的QueID的地址。
为消息队列分配堆内存:MessageQ_alloc()/MessageQ_free()
为消息队列注册/注销堆内存:MessageQ_registerHeap()/MessageQ_unregisterHeap()
向消息队列中放入/获取消息:MessageQ_put()/MessageQ_get()
其他逻辑API:
获取消息队列ID:MessageQ_getQueueId()
获取消息队列中消息数:MessageQ_count()
在消息队列中嵌入消息:MessageQ_setReplyQueue()
为消息队列解阻塞:MessageQ_unblock()
为调试消息队列加入Trace:MessageQ_setMsgTrace()
3.3、ListMP
ListMP实现了多宿主双向循环链表,即该双向循环链表为多个处理器共同拥有,可以由多个处理器共同维护,共同使用。
ListMP的实现区别于一般的双向循环链表,因此它不仅具有双向循环链表的特性外,还增添了其他的特性,比如以下几点:
实现了简单的多宿主协议,支持多个读写者(multi-reader、multi-writee);
使用Gate作为内部保护机制,防止多个宿主处理器同时访问该链表;
ListMP的实现并未加入通知机制,如果需要的话,可以在外部封装是引入Notify机制来实现;使用ListMP机制来管理的buffers都需要从共享内存区分配,包括从堆内存分配的buffers以及动态分配的内存。
ListMP组件常用于满足一下条件的场景中:
需要被多个宿主访问并且需要频繁传递消息或者数据;
可用于无规则的消息传递,基于链表实现,因此读者可以遍历所有对象,并选出需要的对象进行处理;如果硬件支持快速队列,则无法完成队列遍历操作(WHY);
可以自定义消息优先级,同样是基于链表实现,读者可以随意的选择在链表头部还是链表的尾部来插入消息或者实现链表对象的位置调整,进而实现消息的优先级选择;如果硬件支持快速队列,则无法完成队列遍历操作(WHY);
无内置通知机制,可以灵活的外部通知机制来实现。譬如根据实际情况,选用Notify来实现,亦或是使用选用MessageQ则可以使用最少的中断资源实现性能优良的通知机制,缺点是需要额外的代码实现通知机制。
ListMP组件提供了以下API接口:
ListMP参数初始化:ListMP_Params_init()
ListMP创建/销毁:ListMP_create()/ListMP_delete()
ListMP打开/关闭:ListMP_open()/ListMP_close()
ListMP相关链表操作:
判断链表空:ListMP_empty()
获取保护锁:ListMP_getGate()
获取链表头/表尾:ListMP_getHead()/ListMP_getTail()
链表插入操作:ListMP_insert()
获取链表上游元素/下游元素:ListMP_next()/ListMP_prev()
插入元素到链表头/尾:ListMP_putHead()/ListMP_putTail()
删除元素:ListMP_remove()
3.4、GateMP
GateMP是针对于多处理器共享资源的一种保护机制,就如其名字一样,把共享资源比作房子,那么GateMP就是这个房子的门。GateMP组件实现了开关门的机制,用于保护共享资源一次只被一个处理器读写。根据SOC硬件资源配置的不同,GateMP的实现有所不同。对于硬件支持Hardware Spinlock的可以基于H/W spinlock来实现GateHwSpinlock;而对于没有该硬件资源的系统中,则使用软件方法(Peterson算法)来实现GatePeterson。
GateMP组件框架如下:
GateMP组件对用户提供了以下API接口:
GateMP初始化:GateMP_Params_init();
GateMP创建/删除:GateMP_create()/GateMP_delete();
GateMP打开/关闭:GateMP_open()/GateMP_close();
进入/离开GateMP保护:GateMP_enter()/GateMP_leave();
获取当前GateMP的保护类型:GateMP_getLocalProtect()/GateMP_getRemoteProtect();
注:如果某个处理器在想使用被某个GateMP保护的共享资源,那么该处理器会被阻塞,直到该资源被释放(即GateMP_leave())。
|