嵌入式操作系统VxWorks中网络协议存储池原理及实现

来源:互联网 发布:zfs linux 编辑:程序博客网 时间:2024/05/17 04:21

嵌入式操作系统VxWorks中网络协议存储池原理及实现

周卫东 蔺妍 刘利强

(哈尔滨工程大学自动化学院,黑龙江 哈尔滨,150001)

 

摘 要  本文讨论了网络协议存储池的基本原理和在嵌入式操作系统中的实现方法。为在嵌入式系统中实现TCP/IP协议栈,提供了一种有效、简洁、可靠的缓冲区管理。

关键词 VxWorks; mBlk; clBlk; 网络协议存储池

 

VxWorks操作系统是美国WindRiver公司于1983年设计开发的一种嵌入式实时操作系统(RTOS)。它以良好的持续发展能力、高性能的内核以及卓越的实时性被广泛的应用在通信、军事、航空、航天等高精尖技术及实时性要求极高的领域中。VxWorks操作系统有着优越的网络性能,而缓冲区的数据拷贝是影响网络性能的主要因素。

众所周知,缓冲区在网络协议栈中有两个作用:第一,提供载体,使分组或报文可以在各协议层中流动;第二,为各级缓冲区提供空间。缓冲区的设立使得TCP/IP协议栈支持异步I/O操作,异步操作对于协议栈的I/O性能是非常重要的。在网络输出的过程中每一层需要在数据的首部或者尾部添加数据头和数据尾来对数据进行封装使得接收端对应的层能够进行正确的接收,在输入的过程中每层都需要将本层的数据头和数据尾去掉而最终还原成发送端发送的数据。上述的封装/去封装和拷贝操作使得网络协议对内核的存储器管理能力提出了很多要求。这些要求包括能方便地操作可变长缓存,能在缓存头部和尾部添加数据(如低层封装来自高层的数据),能从缓存中移去数据(如当数据包向上经过协议栈时要去掉首部),并能尽量减少这些操作所作的数据复制。

1 使用netBufLib管理存储池的基本原理

网络协议存储池使用mBlk结构、clBlk结构、簇缓冲区和netBufLib提供的函数进行组织和管理。mBlk和clBlk结构为簇缓冲区(cluster)中数据的缓冲共享和缓冲链接提供必要的信息。netBufLib例程使用mBlk和clBlk来管理cluster和引用cluster中的数据,这些结构体中的信息用于管理cluster中的数据并且允许他们通过引用的形式来实现数据共享,从而达到数据“零拷贝”(zerocopy)的目的。

1.1 结构体mBlk和clBlk及其数据结构

mBlk是访问存储在内存池中数据的最基本对象,由于mBlk仅仅只是通过clBlk来引用数据,这使得网络层在交换数据时就可以避免数据复制。只需把一个mBlk连到相应mBlk链上就可以存储和交换任意多的数据。一个mBlk结构体包括两个成员变量mNext和mNextPkt,由它们来组成纵横两个链表:mNext来组成横的链表,这个链表中的所有结点构成一个包(packet);mNextPkt来组成纵的链表,这个链表中的每个结点就是一个包 (packet),所有的结点链在一起构成一个包队列,如图1所示。

图1包含两个数据包的mBlk链

   结构体mBlk和clBlk的数据结构如下所示:

structmBlk

{

M_BLK_HDR  mBlkHdr;      /*header */

 M_PKT_HDR  mBlkPktHdr;  /* pkthdr */

 CL_BLK*       pClBlk;          /*pointer to cluster blk */

}M_BLK;

 

structclBlk

{

CL_BLK_LIST       clNode;      /* union of next clBlk */

 UINT             clSize;        /* cluster size*/

 Int              clRefCnt; /* countof thecluster */

 struct netPool*   pNetPool;  /* pointer to the netPool */

}CL_BLK;

 

/*header at beginning of each mBlk */

structmHdr

{

 struct mBlk*  mNext;    /* next buffer in chain */

 struct mBlk*  mNextPkt;  /* next chain in queue/record */

 char*         mData;    /* location ofdata */

 int           mLen;     /* amount ofdata in this mBlk */

 UCHAR         mType;    /* type of datain this mBlk */

 UCHAR         mFlags;       /* flags; seebelow */

M_BLK_HDR;

 

/*record/packet header in first mBlk of chain; valid if M_PKTHDR set */

structpktHdr

{

structifnet* rcvif; /* rcv interface */

 int           len;  /* total packet length */

}M_PKT_HDR;

 

typedefstructclDesc

{

 int    clSize;       /*cluster type*/

 int    clNum;    /*number of clusters*/

 char*  memArea;   /*pre allocatedmemory area*/

 int    memSize;   /*pre allocatedmemory size*/

}CL_DESC;

 

typedefstructm_cl_config

{

 int    mBlkNum;  /*number of mBlks*/

 int    clBlkNum;  /*numberof clBlks*/

 char*  memArea;   /*pre allocatedmemory area*/

 int    memSize;   /*pre allocatedmemory size*/

}M_CL_CONFIG;

1.2 网络协议存储池的初始化

VxWorks在网络初始化时给网络协议分配存储池并调用netPoolInit(NET_POOL_ID,M_CL_CONFIG*,CL_DESC*)函数对其初始化,由于一个网络协议通常需要不同大小的簇,因此它的存储池也必须包含很多簇池(每一个簇池对应一个大小的簇)。如图2所示。另外,每个簇的大小必须为2的方幂,最大可为64KB(65536),存储池的常用簇的大小为64,128,256,512,1024比特,簇的大小是否有效取决于CL_DESC表中的相关内容,CL_DESC表是由netPoolInit()函数调用设定的。

图2  网络协议存储池初始化后的结构

1.3 存储池的链接及释放

存储池在初始化后,由netPool结构组织几个下一级子池:一个mBlk池、一个clBlk池和一个cluster池。mBlk池就是由很多mBlk组成的一条mBlk链;clBlk池就是由很多clBlk组成的一条clBlk链。cluster池由很多的更下一级cluster子池构成,每一个cluster子池就是一个cluster链。每一个cluster链中的所有cluster的大小相同,不同链中的cluster大小不同。但要实现不同进程访问同一簇而不需要作数据的拷贝,还需要把mBlk结构,clBlk结构和簇结构链接在一起。创建这三级结构一般要遵循这样五步:

a.调用系统函数netClusterGet()预定一块簇缓冲区;

b.调用系统函数netClBlkGet()预定一个clBlk结构;

c.调用系统函数netMblkGet()预定一个mBlk结构;

d.调用系统函数netClBlkJoin()把簇添加到clBlk结构中

e.调用系统函数netMblkClJoin()把clBlk结构添加到mBlk结构中

这样,就构成了最后的缓冲区。

在缓冲区中的数据使用完毕后要及时的释放内存,这一过程只需要调用系统函数netMblkClChainFree()释放存有数据的mBlk链表。例如当数据向上层传送时,在本层中可以释放已经不再使用的mBlk链表,由于在clBlk中记录着指向本模块的mBlk的个数,虽然释放了mBlk链表,但是这并不表示将cluster中的数据释放掉了,上层复制的链表仍然控制着这些数据,直到clBlk中的mBlk计数为0时才真正的将数据占用的簇释放掉,将数据占用的内存空间释放、归还给系统将来使用。

2 网络协议存储池与数据的封装处理

VxWorks操作系统之所以采用mBlk—clBlk—cluster这样的网络数据存储结构,目的就是减少数据拷贝的次数,提高网络数据的传输速率。

图3存储带有1460个字节数据的mBlk

在网络输出的过程中当从上层向下层传递数据时,下层协议需要对数据进行封装使得接收端对应的层能够进行正确的接收。下面通过实例分析网络数据的封装过程。例如要在如图3所示的mBlk链中添加IP和UDP的首部。

在mBlk链表中封装数据的方法是分配另外一个mBlk,把它放在链首,并将分组首部复制到这个mBlk。IP首部和UDP首部被放置在新mBlk的最 后,这个新mBlk就成了整个链表的首部。如果需要,它允许任何其它低层协议(例如添加以太网首部)在IP首部前添加自己的首部,不需要再复制IP和UDP首部。在第一个mBlk中的mBlkHdr.mData指针指向首部的起始位置,mBlkHdr.mLen的值是28。在分组首部和IP首部之间有72字节的未用空间留给以后的首部,通过适当地修改mBlkHdr.mData指针和mBlkHdr.mLen添加在IP首部的前面。注意,分组首部已经移到新mBlk中了,分组首部必须放在mBlk链表的第一个mBlk中。在移动分组首部的同时,在第一个mBlk设置mFlags为M_PKTHDR,设置mType为MT_HEADER。在第二个mBlk中分组首部占用的空间现在未用, 设置mFlags为M_EXT,设置mType为MT_DATA。最后,改变在此分组首部中的长度成员mBlkPktHdr.len,成员mBlkPktHdr.len的值是这个分组的mBlk链表中所有数据的总长度:即所有通过mBlkHdr.mNext指针链接的mbuf的mBlkHdr.mLen值的和。本例中由于增加了28个字节变成了1488。如图4所示。

图4添加完IP和UDP首部的mBlk

这样,当报文在协议栈中流动时,不会拷贝报文链,而只需把指向mBlk的指针通过参数传递。当报文需要进人缓冲区时,也是通过链表的指针操作将报文插入或添加到队列中。

3 结论

网络协议存储池的职责有两个:为协议栈提供合适的缓冲区,如果太大会浪费系统资源,太小会影响协议栈的吞吐量;提供合适的数据结构装载网络报文,既可以使协议栈方便地处理报文,又可以减少缓冲区拷贝的次数。减少拷贝次数不仅降低了CPU的负荷,还可以降低存储器的消耗。本文剖析了嵌入式操作系统VxWorks中网络协议存储池的原理,实现了数据能够动态增删、但在逻辑上又呈现连续性的数据结构。能够满足在各协议层之间传递数据而不需要进行内存拷贝。

 

参考文献

[1]翟东海,李力.mbuf的实现原理剖析及其在网络编程中的应用[J].计算机工程与应,2004(8):104-106.

[2][美]DouglasE.Comer著.张娟等译.用TCP/IP进行网际互联第二卷:设计、实现与内核(第三版)[M].北京:电子工业出版社,2001.05.

[3][美]GaryR.Wright W.RichardStevens著.陆雪莹,蒋慧等译.TCP/IP详解卷2:实现[M].北京:机械工业出版社.2000.07:10—50.

[4]Wind River System Inc.VxWorksNetwork

收稿日期:8月25日   修改日期:9月2日

 

VxWorks中三元组mBlk的原理


原文地址:http://blog.csdn.net/u012658346/article/details/46515349

数据包在网络协议栈中从上向下流动时,需要在数据的首部和尾部为其增加相应的包头和包尾。例如在TCP/IP协议栈中,数据从应用层向下传输的过程中,需要为其封装TCP头部、IP头部等,使得接收端能根据各层的报头来正确的接收数据。这就要求存储数据的缓冲区是可变长的,而同时如果数据是通过拷贝在协议层之间传输时,大量的拷贝会极大的影响性能。

因此在VxWorks中,网络协议栈采用了MBlk-ClBlk-Cluster三元组的结构来存储数据。

1)在为数据包封装头部时,申请一个新的mBlk来存放待封装的报头,并将其链接到mBlk链的头部,这样就不需要拷贝了。

2)将真正待传输的上层数据存放在Cluster中,MBlk通过操作Cluster来引用数据。这样数据在协议层之间传输时,只需要修改MBlk的指针即可,不用真正的拷贝数据。

使用三元组的结构来存储网络协议栈中的数据,可以极大的提升系统性能。

1、存储池netPool

存储池netPool与内存池类似,在初始化的时候,就先根据配置构造好一定数量的mBlk、ClBlk和Cluster。然后在需要用到时,从存储池中取出相应的结构,并将其组合在一起即可。

存储池初始化后的示意图如下:


由上图可知,一个存储池又可分为一个mBlk池、一个ClBlk池和多个Cluster池,其中不同的Cluster池中存放着大小不同的Cluster,Cluster的大小默认可以为64、128、256、512、1024、2048这几种。不同Cluster池中Cluster的个数也不相同。

在使用时,根据需要选择大小最合适的Cluster。

netPool的结构体如下所示:

struct netPool              /* NET_POOL */      {      M_BLK_ID    pmBlkHead;      /* head of mBlks */      CL_BLK_ID   pClBlkHead;     /* head of cluster Blocks */      int     mBlkCnt;        /* number of mblks */      int     mBlkFree;       /* number of free mblks */      int     clMask;         /* cluster availability mask */      int     clLg2Max;       /* cluster log2 maximum size */      int     clSizeMax;      /* maximum cluster size */      int     clLg2Min;       /* cluster log2 minimum size */      int     clSizeMin;      /* minimum cluster size */      CL_POOL *   clTbl [CL_TBL_SIZE];    /* pool table */      M_STAT *    pPoolStat;      /* pool statistics */      POOL_FUNC * pFuncTbl;       /* ptr to function ptr table */      };  


每个mBlk、ClBlk、Cluster子池中的元素都通过一个next指针形成链表,而netPool则需要保存链表头部信息即可。

上述结构体中,pmBlkHead指向mBlk池的头部,pClBlkHead指向ClBlk池的头部,clTbl[CL_TBL_SIZE]则保存了不同大小的Cluster形成的各个Cluster池的信息。

这样就可以通过netPool结构来管理mBlk-ClBlk-Cluster三元组结构了。

2、mBlk

mBlk的结构体如下:

typedef struct mBlk      {      M_BLK_HDR   mBlkHdr;        /* mBlk相关信息 */      M_PKT_HDR   mBlkPktHdr;     /* 与接收端口相关的信息 */      CL_BLK *    pClBlk;         /* 指向ClBlk */      } M_BLK;  


其中mBlkHdr的结构如下:

typedef struct mHdr      {      struct mBlk *   mNext;      /* next buffer in chain ,形成横向链表*/      struct mBlk *   mNextPkt;   /* next chain in queue/record ,形成纵向链表*/      char *      mData;      /* location of data ,指向数据区*/      int         mLen;       /* amount of data in this mBlk */      UCHAR       mType;      /* type of data in this mBlk */      UCHAR       mFlags;     /* flags; see below */      USHORT      reserved;       } M_BLK_HDR;  


mBlk结构体通过mBlkHdr结构体中的mNext和mNextPkt两个指针,形成纵向和横向两个链表,如下所示:

每一条横向的mBlk链为一个完整的数据包,纵向mBlk链则将多个数据包链接在一起,形成一个数据包链表。

3、ClBlk

ClBlk的结构体如下:

typedef struct clBlk      {      CL_BLK_LIST     clNode;     /* union of next clBlk, buffer ptr */      int     clSize;     /* cluster size */      int         clRefCnt;   /* reference count of the cluster */      FUNCPTR     pClFreeRtn; /* pointer to cluster free routine */      int         clFreeArg1; /* free routine arg 1 */      int         clFreeArg2; /* free routine arg 2 */      int         clFreeArg3; /* free routine arg 3 */      struct netPool *    pNetPool;   /* pointer to the netPool */      } CL_BLK; 


由上可知,clNode是一个联合体,当clBlk空闲时,指向下一个clBlk,即在ClBlk池中形成链表。而当clBlk被使用时,则指向具体的数据区,一般为一个Cluster。

通过clRefCnt完成引用计数,当计数为0时,则将其归还到存储池中。
4、Cluster

当Cluster空闲时,其首部四个字节指向下一个Cluster,从而以链表形式形成一个Cluster池。而当Cluster被使用时,则为具体的数据区。

通过以上的分析可知,一个基本的三元组结构示意图如下:


多个mBlk则通过mBlk.mBlkHdr中的mNext和mNextPkt指针形成横向和纵向的链表,来构造实际的数据包。


VxWorks中mBlk三元组的实现源码分析

 原文地址:http://blog.csdn.net/u012658346/article/details/46519959

在上文中,对mBlk三元组的原理进行了简单的介绍。下面将简单介绍三元组的实现,对其部分代码进行分析。

mBlk是用C语言实现的,因此其中包含了大量复杂的指针操作和函数指针。

1、函数表poolFunc

poolFunc是一个结构体,其中定义了多个函数指针,用来操作存储池,包括初始化netPool,申请和归还mBlk、ClBlk和Cluster等。

系统定义的默认函数表如下:

[cpp] view plaincopy
  1. LOCAL POOL_FUNC dfltFuncTbl =       /* default pool function table */  
  2.     {  
  3.     _poolInit,  
  4.     _mBlkFree,  
  5.     _clBlkFree,  
  6.     _clFree,  
  7.     _mBlkClFree,  
  8.     _mBlkGet,  
  9.     _clBlkGet,  
  10.     _clusterGet,  
  11.     _mClGet,  
  12.     _clPoolIdGet,  //由于存在多个Cluster子池,因此申请Cluster时,必须先得到其对应的Cluster池  
  13.     };  

2、存储池netPool的初始化

通过函数表中的函数指针 STATUS (*pInitRtn) (NET_POOL_ID pNetPool, M_CL_CONFIG * pMclBlkConfig, CL_DESC * pClDescTbl, int clDescTblNumEnt,  BOOL fromKheap);来完成存储池的初始化。

默认情况下,函数指针指向_poolInit()函数。

[cpp] view plaincopy
  1. LOCAL STATUS _poolInit  
  2.     (  
  3.     NET_POOL_ID     pNetPool,   /* pointer to a net pool */  
  4.     M_CL_CONFIG *   pMclBlkConfig,  /* pointer to mBlk,clBlk config */  
  5.     CL_DESC *       pClDescTbl, /* pointer to cluster desc table */  
  6.     int         clDescTblNumEnt, /* number of cluster desc entries */  
  7.     BOOL                fromKheap       /* 1:KHEAP_ALLOC  0:malloc/calloc */  
  8.     )  
_poolInit()通过传入的配置参数来完成初始化。

pMclBlkConfig结构体中存储着mBlk和ClBlk的配置信息:

[cpp] view plaincopy
  1. typedef struct  
  2.     {  
  3.     int     mBlkNum;        /* number of mBlks */  
  4.     int     clBlkNum;       /* number of cluster Blocks */  
  5.     char *  memArea;        /* pre allocated memory area */  
  6.     int     memSize;        /* pre allocated memory size */  
  7.     } M_CL_CONFIG;  
其中保存着mBlk、ClBlk的个数,并为mBlk和ClBlk预分配了一定内存。

pClDescTbl则保存着各个Cluster池的配置信息:

[cpp] view plaincopy
  1. typedef struct clDesc  
  2.     {  
  3.     int     clSize;         /* cluster type */  
  4.     int     clNum;          /* number of clusters */  
  5.     char *  memArea;        /* pre allocated memory area */  
  6.     int     memSize;        /* pre allocated memory size */  
  7.     } CL_DESC;   
pClDescTbl中的每一个元素都保存着对应的Cluster池的信息,包括Cluster的大小、个数,以及为当前Cluster池中所有Cluster预先分配的内存空间。
在_poolInit()函数中,则根据相应的配置信息,来将存储池中各个子池中元素链接成链表,并将相关信息存储在结构体netPool中,然后即可通过netPool来管理所有元素。

3、示例

一个简单的使用示例如下:

[cpp] view plaincopy
  1. M_CL_CONFIG mClBlkConfig;  
  2. CL_DESC clDescTbl[1];  //数组大小为1,说明只分配一个Cluster池  
  3. int  clDescTblNumEnt = 1;  
  4. NetPoolPtr = (NET_POOL_ID)malloc(sizeof(NET_POOL));  
  5.   
  6. mClBlkConfig.mBlkNum = 2000;//2000个mBlk  
  7. mClBlkConfig.clBlkNum= 1000; //1000个ClBlk  
  8. mClBlkConfig.memSize = (mClBlkConfig.mBlkNum * (M_BLK_SZ + sizeof(void *))) +  
  9.                       (mClBlkConfig.clBlkNum * CL_BLK_SZ);  //mBlk和ClBlk将占用的空间大小  
  10. mClBlkConfig.memArea = (char*)malloc(mClBlkConfig.memSize); //为mBlk和ClBlk预先分配内存  
  11. ueMCLBlkMemArea=mClBlkConfig.memArea;  
  12. memset(mClBlkConfig.memArea,0,mClBlkConfig.memSize);  
  13.   
  14. clDescTbl[0].clSize = 2048;//每个Cluster的大小  
  15. clDescTbl[0].clNum = 1000;  
  16. clDescTbl[0].memSize = clDescTbl[0].clNum*(clDescTbl[0].clSize+sizeof(void *));  
  17. clDescTbl[0].memArea = (char*)malloc(clDescTbl[0].memSize);//为所有的Cluster预先分配内存  
  18. ueClMemArea=clDescTbl[0].memArea;  
  19. memset(clDescTbl[0].memArea,0,clDescTbl[0].memSize);  
  20.   
  21. if(netPoolInit   //调用函数,完成存储池的初始化  
  22. (  
  23.     NetPoolPtr,        /* pointer to a net pool */  
  24.     &mClBlkConfig,   /* pointer to a mBlk configuration */  
  25.     clDescTbl,      /* pointer to cluster desc table */  
  26.     clDescTblNumEnt, /* number of cluster desc entries */  
  27.     NULL          /* pointer to pool function table */  
  28.    ) == /*VXOK*/0)  
  29. {  
  30.     printf("netpool initial OK!\n");  
  31. }  

从上面的代码可以发现,为每个mBlk和ClBlk及Cluster结构分配内存时,都是在待分配内存的基础上加了一个sizeof(void *),通过这四个字节来指向具体的存储池。在mBlk和ClBlk中,这4个字节是指向其所在的netPool的,而在Cluster中,这4个字节是指向该Cluster所在的Cluster池的。

这样在释放资源时,就可以通过这4个字节找到其所在的存储池。

4、netPoolInit()的具体实现

用户首先配置好相应参数,然后调用netPoolInit()来完成存储池的初始化。

[cpp] view plaincopy
  1. STATUS netPoolInit  
  2.     (  
  3.     NET_POOL_ID     pNetPool,    /* pointer to a net pool */  
  4.     M_CL_CONFIG *   pMclBlkConfig,   /* pointer to a mBlk configuration */  
  5.     CL_DESC *       pClDescTbl,  /* pointer to cluster desc table */  
  6.     int         clDescTblNumEnt, /* number of cluster desc entries */  
  7.     POOL_FUNC *     pFuncTbl     /* pointer to pool function table */  
  8.     )  
  9.     {  
  10.     if (pNetPool == NULL)  
  11.         return (VXERROR);  
  12.   
  13.     if (pFuncTbl != NULL)  
  14.         pNetPool->pFuncTbl = pFuncTbl;  
  15.     else  
  16.         pNetPool->pFuncTbl = _pNetPoolFuncTbl;   /* default func table */  
  17.   
  18.     return (poolInit (pNetPool, pMclBlkConfig, pClDescTbl,  
  19.         clDescTblNumEnt, FROM_HOMEPDHEAP)); /* allocate from the homePD's heap */  
  20.     }  
由上可知,在netPoolInit()中,首先指定netPool的函数表,然后调用poolInit()。

poolInit()为一个宏定义,其实现如下:

[cpp] view plaincopy
  1. #define poolInit(poolId,pMclBlkConfig,pClDescTbl,tblNumEnt,fromKheap)   \  
  2.             (*(((NET_POOL_ID)(poolId))->pFuncTbl->pInitRtn))  \  
  3.                  ((poolId), (pMclBlkConfig), (pClDescTbl), (tblNumEnt), \  
  4.           (fromKheap))  
即在poolInit()中,通过netPool指定的函数表中的函数指针调用真正的初始化函数,系统默认的初始化函数为_poolInit()。

在_poolInit()中,则是按照mBlk、ClBlk、Cluster的顺序,依次初始化各个子池。


三个子池的初始化实现基本类似,下面以mBlk的初始化为例,进行简要介绍。

mBlk的初始化主要是调用下面两个函数来完成的:

[cpp] view plaincopy
  1. if (_memPoolInit (pMclBlkConfig->mBlkNum, M_BLK_SZ, sizeof(void *),pMclBlkConfig->memArea) != VXOK)  
  2.             {  
  3.             goto poolInitError;  
  4.             }  
  5. pNetPool->pmBlkHead = _mBlkCarve ( pNetPool,pMclBlkConfig->mBlkNum,pMclBlkConfig->memArea);  
其中在_memPoolInit()中,只是判断是否已经成功分配内存和内存是否对齐,若成功则继续,否则返回错误。

然后调用_mBlkCarve()将为mBlk分配的内存划分成mBlkNum份,并链接起来,形成一条mBlk链,然后返回链表头部,交给netPool管理。

在之前的示例中可以看到,为mBlk分配的内存总大小为:

[cpp] view plaincopy
  1. mClBlkConfig.mBlkNum * (M_BLK_SZ + sizeof(void *))  
即每个mBlk的大小为(M_BLK_SZ+4),其中M_BLK_SZ=sizeof(mBlk),4字节用来指向存储池netPool,示意图如下:


_mBlkCarve()的实现如下:

[cpp] view plaincopy
  1. LOCAL M_BLK_ID _mBlkCarve  
  2.     (NET_POOL_ID        pNetPool,   /* pointer to net pool */  
  3.     int         num,        /* number of units to allocate */  
  4.     char *      pool        /* pre allocated memory area */  
  5.     )  
  6.     {  
  7.     M_BLK_ID        pMblk = NULL;  
  8.     int         ix;  
  9.     int         size;       /* size of each unit */  
  10.     M_BLK_ID *      ppMblk;  
  11.   
  12.     size = M_BLK_SZ + sizeof (void *);  /* 初始化时每次应移动的大小*/  
  13.     ppMblk = &pMblk;//pMblk用于保存mBlk形成的链表的首个元素   
  14.     for (ix = 0; ix < num; ++ix)  
  15.     {  
  16.     *ppMblk          = (M_BLK_ID) (pool + sizeof (void *));//mBlk空闲时,每个mBlk的起始4字节用于指向下一个mBlk,用于形成链表  
  17.         *((NET_POOL_ID *)(pool)) = pNetPool;//头部4字节用于指向netPool  
  18.     (*ppMblk)->mBlkHdr.mType = MT_FREE;//此时mBlk为空闲  
  19.         ppMblk           = &((*ppMblk)->mBlkHdr.mNext);  
  20.     pool             += size;  
  21.     }  
  22.     return (pMblk);  
  23.     }  

第一次循环时,ppMblk指向pMblk,

*ppMblk = (M_BLK_ID) (pool + sizeof (void *));执行后,将memArea偏移4字节后的地址赋给*ppMblk,即将其赋给pMblk,这样pMblk就指向了mBlk链的头部。

然后执行 ppMblk  = &((*ppMblk)->mBlkHdr.mNext); ppMblk指向mBlk的next指针,然后pool向下移动size大小,即sizeof(MBLK)+4,然后执行*ppMblk  = (M_BLK_ID) (pool + sizeof (void *)); 则将下一个mBlk的起始地址赋给了*ppMblk ,即上一个mBlk的next指针,这样就形成了一个mBlk链


左侧为mBlk的简单示意图,通过mBlkHdr中的成员变量next指针指向下一个mBlk,形成一条mBlk链。

右侧为对预先分配好的内存块memArea进行初始化后的结果,每个子块的起始4字节指向netPool,每个mBlk通过next指针形成链表,然后返回链表头部pMblk赋给 pNetPool->pmBlkHead,将mBlk链的管理权交给存储池netPool。

ClBlk和Cluster的初始化过程和结果与mBlk类似。
5、资源的申请和释放

用户通过调用netTupleGet()来申请一个完整的三元组,其中先分别申请mBlk、ClBlk和Cluster,然后再将它们串联在一起。

5.1、mBlk的申请与释放

netPool保存着mBlk、ClBlk和Cluster链表的头部,mBlk、ClBlk、Cluster的申请释放都十分类似,以mBlk的申请、释放为例进行说明。用户调用netMblkGet()来申请一个mBlk,在该函数内部,通过netPool函数表中的函数指针来调用_mBlkGet()真正的申请资源。_mBlkGet()的实现如下:

[cpp] view plaincopy
  1. LOCAL M_BLK_ID _mBlkGet(  
  2.     NET_POOL_ID     pNetPool,   /* pointer to the net pool */  
  3.     int         canWait,    /* M_WAIT/M_DONTWAIT */  
  4.     UCHAR       type        /* mBlk type */  
  5.     )  
  6.     {  
  7.     M_BLK_ID        pMblk = NULL;   /* pointer to mbuf */  
  8.     int         level;      /* level of interrupt */  
  9.   
  10.     reTry:  
  11.     level = intLock();          /* lock interrupts very briefly */  
  12.     if ((pMblk = pNetPool->pmBlkHead) != NULL)  //取出链表头部元素  
  13.         {  
  14.         pNetPool->pmBlkHead = pMblk->mBlkHdr.mNext; //更新链表头部  
  15.         pNetPool->pPoolStat->mTypes [MT_FREE]--;  //更新统计信息  
  16.         pNetPool->pPoolStat->mTypes [type]++;  
  17.         intUnlock (level);          /* unlock interrupts */  
  18.   
  19.         pMblk->mBlkHdr.mType = type;  
  20.         pMblk->mBlkHdr.mNext     = NULL;  
  21.         pMblk->mBlkHdr.mNextPkt = NULL;  
  22.         pMblk->mBlkHdr.mFlags    = 0;  
  23.         }  
  24.     return (pMblk);//返回获取的mBlk  
  25.     }  
这样就从netPool中的mBlk子池中取出了一个mBlk。

mBlk的释放则是调用netMblkFree()将其归还到netPool中,其最终实现通过_mBlkFree()完成:

[cpp] view plaincopy
  1. LOCAL void _mBlkFree(  
  2.     NET_POOL_ID     pNetPool,   /* pointer to the net pool */  
  3.     M_BLK_ID        pMblk       /* mBlk to free */  
  4.     )  
  5.     {  
  6.     FAST int        ms;  
  7.     pMblk->mBlkHdr.mNextPkt  = NULL;  
  8.   
  9.     ms = intLock ();  
  10.     pNetPool->pPoolStat->mTypes [pMblk->mBlkHdr.mType]--;  
  11.     pNetPool->pPoolStat->mTypes [MT_FREE]++;  
  12.     pMblk->mBlkHdr.mType     = MT_FREE;  
  13.     pMblk->mBlkHdr.mNext     = pNetPool->pmBlkHead;  //将归还的元素插入到mBlk链的头部  
  14.     pNetPool->pmBlkHead  = pMblk; /* 更新netPool中保存的链表头部 */  
  15.   
  16.     intUnlock (ms);  
  17.     }  
ClBlk和Cluster的申请、释放与mBlk类似。

5.2、三元组的申请

实现如下:

[cpp] view plaincopy
  1. M_BLK_ID netTupleGet( )  
  2.     {  
  3.     M_BLK_ID        pMblk = NULL;       /* pointer to mBlk */  
  4.     pMblk = mBlkGet (pNetPool, canWait, type);  //首先申请一个mBlk  
  5.        /*mBlk申请成功后,调用mClGet()来申请一个ClBlk和Cluster,然后将三者绑定在一起*/  
  6.     if (pMblk && (mClGet (pNetPool, pMblk, bufSize, canWait, bestFit)!= VXOK))      
  7.     return (pMblk);   
  8.     }  
在mClGet()中,则是通过函数表中的_mClGet()函数来具体实现的

[cpp] view plaincopy
  1. LOCAL STATUS _mClGet( )  
  2.     {  
  3.     int         log2Size;   /* size of cluster to the base 2 */  
  4.     CL_POOL_ID      pClPool;    /* pointer to the cluster pool */  
  5.     CL_BUF_ID       pClBuf = NULL;  /* pointer to the cluster buffer */  
  6.     CL_BLK_ID       pClBlk = NULL;  /* pointer to the cluster blk */  
  7.     FAST int        ms;         /* integer for level */  
  8.   
  9.     if ((pMblk == NULL) || ((pClBlk = _clBlkGet (pNetPool, canWait)) == NULL))//申请一个ClBlk  
  10.     goto mClGetError;  
  11.   
  12.     /* 省略。。。。   通过一系列计算得到log2Size,然后找到待申请Cluster大小对应的Cluster子池。pClHead即为该Cluster子池的链表头部 */                                                                                                                  
  13.     if ((pClBuf = pClPool->pClHead)) //然后从Cluster子池中取出一个Cluster  
  14.     {  
  15.     if ((pClPool->pClHead = pClBuf->pClNext) == NULL)  
  16.             {  
  17.         pNetPool->clMask &= ~(CL_LOG2_TO_CL_SIZE(pClPool->clLg2));  
  18.             }  
  19.     pClPool->clNumFree--;  
  20.     pClPool->clUsage++;  
  21.         intUnlock (ms);  
  22.     }  
  23.     else   //若Cluster申请失败,则返回错误  
  24.     {  
  25.     pNetPool->pPoolStat->mDrops++;    /* no. times failed to find space */  
  26.         intUnlock (ms);  
  27.     errnoSet (S_netBufLib_NO_POOL_MEMORY);  
  28.     assert(0);  
  29.     goto mClGetError;  
  30.     }  
  31.     /*然后将mBlk、ClBlk、Cluster三者绑定到一起*/  
  32.     pMblk->mBlkHdr.mData  = (caddr_t) pClBuf;  
  33.     pMblk->mBlkHdr.mFlags |= M_EXT;  
  34.     pClBlk->clNode.pClBuf = (caddr_t) pClBuf;  
  35.     pClBlk->clSize     = pClPool->clSize;  
  36.     pClBlk->pClFreeRtn     = NULL;  
  37.     pClBlk->clRefCnt   = 1;  
  38.     pMblk->pClBlk      = pClBlk;  
  39.   
  40.     return (VXOK);      /* return VXOK */  
  41.   
  42.     mClGetError:  
  43.     return (VXERROR);       /* return VXERROR */  
  44.     }  
最终形成的三元组包链如下所示:

0 0