分类: LINUX
2007-11-27 13:49:28
滕俐军
UTStarcom( 中国)有限公司深圳研发中心Common software
摘要 Abstract
本文分析了当前协议层间消息的传递机制,总结了两种高效的传递技术。
Abstract
This paper gives an analysis on the message
transfer mechanisms used in our protocol layers and summarizes two types of
high efficient way.
关键词: 消息传递,内存管理
Key words: Message Transfer, Memory Management
在公司的产品中,常用的协议层间交互方式有以下几种:
1. 直接调用机制:协议层间直接调用对方的函数
2. 回调机制:协议层通过直接调用机制向公共模块注册回调函数,公共模块通过回调函数与协议层交互。
3. 消息机制:协议层间通过系统级的消息传递机制交互。
前两种方式又称为紧耦合模式,协议层间耦合度较高;第三种方式称为松耦合模式,模块间耦合度很低。松耦合模式有利于模块管理、协议层的分布式处理等。本文将详细分析当前协议层常用的消息机制。
传统上,我们都是利用操作系统提供的进程间的消息机制,比如vxWorks提供了msgQSend、windows提供了WriteFile、Unix提供了msgsnd等。我们的SSI也提供了通用的函数比如WACOS_msgQSend等,它封装了各种操作系统的调用。
基于该机制,有两种使用方式:
本方式将发送的消息保存在某内存块中,通过系统函数发送消息内容。
1. 优点
该机制是最常见的进程间通讯方法,适用于各种操作系统,使用广泛,开发人员非常熟悉。
2. 缺点
(1)不灵活
消息结构必须是固定的,任何参数都不能动态增加。这样才能预计消息所需要的内存大小,将消息保存在连续内存中以便拷贝。
(2)效率低
消息最少需要拷贝一次(发送任务的缓冲->接收任务的缓冲),最多两次(发送任务的缓冲->消息队列缓冲->接收任务的缓冲)。
3. 适用范围
适合消息长度不太大的情况,否则会影响效率。
本方式将发送的消息存在某申请的内存块中,通过系统函数发送该消息的指针。
本方式提高了消息传递的效率,但:
n 增加了任务间耦合度:要求发送任务必须申请内存,接收任务必须能够释放该申请的内存。
n 容易带来内存泄漏等问题。
n 仍有不灵活的缺点
目前这种模式使用较少。
我们将协议层收/发的消息中的数据归纳为两种:
n 参数结构数据:用于指示编码或其它信息
n 编码的协议数据:基于参数结构,按照协议标准产生的编码数据。
SSI提供了新的独立的消息机制,可以实现协议层间的协议编码数据的高效传递。同时利用基于带有内存管理的应用参数结构,我们可以实现对参数结构数据的高效传递。
SSI提供了新的独立的消息机制,可以实现协议编码数据的高效传递。
1. 消息传递结构Buffer
SSI为协议层提供了一套独特的消息传递结构--Buffer。
(1)消息缓冲结构MBUF
MBUF用于管理发送的数据,由三部分组成:
一是消息控制块SsMblk,其中b_next和b_prev用于把MBUF链入协议层的消息队列;b_cont用于链接所有的DBUF;b_rptr指向存放消息信息的结构SsMsgInfo;b_dadap用于访问SsDblk结构。
二是数据控制块SsDblk,消息缓冲所带的数据就是信息结构SsMsgInfo,其中db_base指向SsMsgInfo的起始位置;db_type为SS_M_PROTO,表明为消息缓冲;db_ref是引用计数,用于零拷贝复制消息
三是消息信息块SsMsgInfo,其中endptr指向最后一个DBUF;next初始化为b_cont指向的DBUF,方便DBUF的顺序读取,用于构造传输消息的矢量数组等;len记录当前DBUF个数;region和pool指定内存的分配区;pst保存用于发送消息的信息,包含源和目的信息、消息类型等。
图表 1 消息缓冲结构
该结构有以下特点:
n 带有内存申请和释放的信息
n 指向一个由若干个DBUF构成的数据链,内存可以动态增加
n 支持零拷贝
(2)数据缓冲结构DBUF
DBUF用于保存发送的数据,由三部分组成:
一是消息控制块SsMblk,其中b_next和b_prev用于把DBUF链入消息的数据链表;b_cont用于链接下一个DBUF;b_rptr指向数据区读起始位置;b_wptr指向数据区写起始位置;b_dadap用于访问SsDblk结构。
二是数据控制块SsDblk,其中db_base指向数据区的起始位置;db_lim指向数据区的结束位置;db_ref是引用计数,用于零拷贝复制数据;db_type为SS_D_PROTO,表明为数据缓冲。
三是数据区,其中pad 区为数据填充区;payload区为数据区;blank区为未用区
图表 2 数据缓冲结构
2. 使用方式
(1)消息缓冲MBUF的申请
在发送消息前,通过SgetMsg获得MBUF。
(2)数据缓冲DBUF的申请
协议层通过相应的协议编码/参数打包函数,将协议数据/层间参数存入MBUF,此时会申请DBUF,数据存入DBUF的payload区。数据区大小目前为(256-(sizeof(SsMblk)+sizeof(SsDblk)))=218字节。当前DBUF用完可以动态增加。
(3)消息发送
协议层通过SSI的系统函数SPstTsk()将Buffer直接放入其它协议层的消息队列。
(4)消息接收
协议层从自己的消息队列中取出某个消息Buffer,通过协议解码/参数解包函数得到协议数据/层间参数,然后通过通用函数SPutMsg释放消息所占内存。
下图显示协议层上下层间的消息处理情况:
图表 3 消息缓冲传递处理
(5)消息释放
通过函数SPutMsg可以释放消息缓冲,该函数会释放该消息缓冲管理的所有数据缓冲。
3. 优点
(1)灵活
通过本机制可以收发任意结构的消息,对结构是否固定无要求。协议层只需要设计好编码/解码/打包/解包函数,
(2)协议编码数据的传递效率高
n 本层的协议数据编码后可以一直向下传递,无需任何拷贝。
n 支持零拷贝:当消息为多个进程引用时,只需增加参考计数,无需真正拷贝。
4. 缺点
(1)不适合层间参数比较复杂情况:此时打包和解包消耗很大,会降低效率
主要是应用层与协议层间,应用层的参数结构往往比较复杂。比如在ISUP协议层,由于应用层的消息参数结构非常大(几千字节),开发人员不得不为应用层创建了一个参数子集(小于300字节),以减少内存申请、清零、打包、解包等带来的消耗。
5. 适用范围
(1)适合协议层内部的协议数据传递:
从目前的使用来看,本机制广泛应用于协议层。层间参数一般不多,所以打包/解包带来的消耗并不大。
(2)适合适度大小的参数结构数据的传递:新模块可以参考是否使用本方法,比如我们将旧PSH模块中的字节序列编码方法(直接通过字节位置定位参数)替换为本方法,消息结构更加清晰,易于维护和扩展。
通过在应用参数结构中增加内存链表控制点,应用参数结构可以以指针方式传递。
1. 内存管理控制点
在应用参数结构中,需要加入一个内存链表控制点(CmMemListCp),放在第一个参数位置。比如MEGACO的消息参数结构:
typedef struct mgMgcoMsg
{
CmMemListCp memCp;
TknPres pres;
……
} MgMgcoMsg;
CmMemListCp中记录了内存块的链表(首、末内存块指针)、内存块个数(count)、当前的内存控制块(CmMemCb))等。内存块通过双向链表(CmMemList)链接起来,CmMemList是在内存快申请时额外增加的,位于块起始位置,包含next和prev两个链表指针。
CmMemCb用于管理当前内存块,记录了当前的内存块使用情况,包括内存块最大字节数(maxSize)、内存分配区(sMem,用于内存申请和释放)、已分配内存字节数(memAllocated)、内存初始指针(initPtr)、内存当前运行指针(runPtr)等。
图表 4 内存管理控制结构
2. 使用方式
(1)初始内存的申请过程
首先要通过cmAllocEvnt函数来为应用参数结构创建一块初始内存。
内存块大小为(maxBlkSize+sizeof(CmMemList)),maxBlkSize一般大于参数结构的初始大小,而且要能保证动态申请时对连续内存的需求。一种优化的大小是对于大部分常见的参数结构消息,所有的动态内存申请都能从第一块内存块中得到,无需再申请新的内存块,这样可以提高效率和保证系统稳定。
目前SSI的内存管理有高效的桶(Bucket)模式,将内存分成N组bucket链表,每组内bucket大小一样,各组不一样,从72字节到2048字节不等,保证相应大小的连续内存。Bucket模式的内存申请非常快,它是从满足所需大小的bucket组的闲链中取出第一个bucket。应用应该基于bucket来定义maxBlkSize,以获得高的效率。
CmMemListCp的链表链入当前内存块,CmMemCb按照当前内存块进行初始化。
(2)动态内存的申请过程
在进行应用参数赋值时,需要通过cmGetMem来动态得到所需内存。
首先检查当前内存块的剩余内存是否可用。通常应该是可用的,如果不可用,则需要增加新的内存块。通过sMem所指定内存区,申请新的内存块,CmMemListCp的链表链入当前内存块,CmMemCb按照当前内存块进行初始化。然后按下面的可用条件得到所需动态内存。
如果可用,则runPtr当前值即为申请的内存地址,然后runPtr移动所需字节数,memAllocated增加相同的字节数。可见,这种动态内存申请非常快。
(3)参数结构消息发送
当应用参数结构赋值完成后,应用层只需要通过操作系统或SSI提供的消息机制,打包发送本参数结构的指针。
(4)参数结构消息接收
协议层通过操作系统或SSI提供的消息机制,接收到应用层的参数结构消息,通过解包函数得到应用参数结构的指针,可以直接得到应用参数信息。
(5)消息内存的释放过程
通过cmFreeMem可以释放CmMemListCp管理的所有内存块。在bucket模式下,这种释放是将当前的bucket块指针放入相应bucket组的闲链,非常快的。
(6)举例
下面以一个MEGACO消息参数结构为例,说明参数结构初始申请、参数动态内存申请和参数结构释放的使用情况:
MgMgcoMsg *message;
cmAllocEvnt(memSize, maxBlkSize, &sMem, (Ptr *)&message);
cmGetMem(&(message->memCp), len, (Ptr *)&(message->lcl.mid.val);
cmFreeMem(&(message->memCp));
3. 优点
(1)非常灵活
通过本机制可以发送任意结构的参数结构,只要求该参数结构的内存完全通过对应内存管理控制点申请。
(2)效率高
只需要发送参数结构的指针,不需要对参数内容进行拷贝。
动态内存的申请只是指针的移动,其效率远高于正常的内存申请。
(3)不会增加任务的耦合度和导致内存泄漏等问题
基于本方法的内存管理机制,消息接收任务能通过统一机制释放消息的内存
4. 缺点
(1)需要修改应用参数结构
一是要增加内存控制点CmMemListCp
二是为了充分利用本机制的优势,现有应用参数结构可能需要修改为可动态扩充的版本,对应用层影响大。比如ISUP的应用参数结构。
(2)不适合简单的参数结构
本方法的内存管理会带来一定开销,这使得在简单数据结构上效果并不最好。
5. 适用范围
本机制实现了对参数结构消息的高效传递,适用于应用参数结构复杂,需要动态申请内存等情况。
本机制还适用于多线程分布处理的情况。比如在多CPU的solaris系统,每个CPU绑定一个消息处理线程,主线程将消息负荷分担到各个子线程。本机制保证了消息内存的正确释放。
目前,本机制应用于MEGACO/MGCP/SIP等协议与应用层的接口。
本文分析和总结了两种用于协议层间消息传递的高效技术,它们不仅适用于协议层,也可以用于其它模块间的消息传递。希望这些技术的使用能使我们公司的产品更快、更强。