最近在看TCP/IP的BSD实现。首先是它的存储管理,主要是通过mbuf这个结构体来管理缓存。看了一部分,觉得设计的很好,把大块的数据拆成小块存储,这样能很方便的写回收池。之前在写流媒体服务器时,一直苦恼对应帧数据大内存管理,分配的内存之后,该怎么回收,最后是模仿nginx的内存池,再每块固定大小的内存块前面加上了引用计数,再把这块内存分块分配,最后引用计数为0时再回收到池。
看了TCP/IP的缓存管理,觉得把帧数据打散存在固定大小的mbuf里,最后统一回收这块固定大小的结构块就可以了,也是一种方法。
下面来看看mbuf的实现。
一:结构体
mbuf的头部信息
struct m_hdr {
struct mbuf *mh_next; /* 指向链中下一个mbuf的指针 */
struct mbuf *mh_nextpkt; /* 指向下一个链的指针 */
int mh_len; /* mbuf中数据的长度(不包括头部) */
char *mh_data; /* 指向数据区的指针 */
short mh_type; /* mbuf的数据类型,如MT_DATA*/
short mh_flags; /* mbuf标识,具体定义见下 */
};
mbuf标识
#define M_EXT 0x0001 /*使用簇作为外部换成 */
#define M_PKTHDR 0x0002 /* 指示mbuf包含的是一个分组首部 */
#define M_EOR 0x0004 /* 分组结束,一般哟哟与OSI协议,TCP从来不用这个标志,因为TCP是流协议,没有边界的*/
struct pkthdr {
int len; /* 整个mbuf链表包含数据的总长度,在链表的第一个mbuf中维护一个带有总长度的分组首部的原因是,当需要总长度时可以避免查看所有mbuf中的mh_len来求和*/
struct ifnet *rcvif; /* 指向接收分组的接收接口结构的指针*/
};
这里使用共用体来描述,依赖于m_type的类型来决定共用体的内容。这个可以学习。
struct mbuf {
struct m_hdr m_hdr;
union {
struct {
struct pkthdr MH_pkthdr; /* M_PKTHDR set */
union {
struct m_ext MH_ext; /* M_EXT set */
char MH_databuf[MHLEN];
} MH_dat;
} MH;
char M_databuf[MLEN]; /* !M_PKTHDR, !M_EXT */
} M_dat;
};
并且最后定义了几个宏来简化结构体的操作,这个也可以学习下。
比如:
#define m_next m_hdr.mh_next
#defiene m_dat M_dat.M_databuf
mbuf的数据结构如下:
依靠m_next来组成一个分组。
依靠m_nextpkt组成多个分组。
二:添加IP和UDP首部
在mbuf中添加协议首部也很方便,只需要再分配一个mbuf,并把它添加到链首。这里是将IP与UDP首部28个字节放在mbuf的数据区的底部,这样底层协议可以
很方便的在上面继续增加协议头。
三:输入是如何防止异步导致的数据结构被破坏
数据输入是异步的,网卡驱动程序接收到一个中断,内核就会调用设备驱动来处理这个分组,这样mbuf由于在两个协议层之间是数据共享的,所以中断触发的时候就有可能破坏数据结构。
Net/3的代码是通过调整中断优先级来保护共享数据的。网络协议处理是软中断,级别低于网络设备的输入输出中断,当网络设备驱动程序完成之后,会把接收到的分组放置到IP队列中,然后出发协议处理软中断。
比如正道IP协议层来处理输入分组时,它会去检查链表中是否有数据:
struct mbuf *m;
int s;
s = splimp(s);
IF_DEQUEUE(&ipring,m)
splx(s);
if(m == 0)
return;
splimp是把cpu的优先级升高到跟网路设备驱动程序级别,防止任何网络设备驱动程序中断发生。
splx是恢复之前的优先级。
三:dtom宏
#define dtom(x) (struct mbuf*)((int)x & ~(MSIZE -1)) MSIZE是mbuf的大小,32位下为128位。
这个宏实现了将一个存放在mbuf中任意位置的数据转换成mbuf,当时一直没看懂,这个宏怎么能实现把结构体成员转成结构体指针,如果说是offset这个宏,还能明白。这个宏主要就是将地址的地位清零来获取起始位置,
后来觉得是因为mbuf总是128字节对齐的,这样清零之后总是能获得起始位置。
TCP/IP卷2 - 读书笔记(1) 存储管理,布布扣,bubuko.com