上一篇:DIY TCP/IP Payload Buffer模块0
6.2 pdbuf数据结构
本节新增pdbuf.h头文件,定义pdbuf数据结构和pdbuf模块的接口函数。pdbuf模块所有操作函数的实现,以及DIY TCP/IP的ARP,IP,ICMP,TCP,UDP等模块的组帧操作,都是基于本节定义的pdbuf_t数据结构。
#ifndef _PDBUF_H_
#define _PDBUF_H_
#include <assert.h>
#include <string.h>
typedef struct _pdbuf {
unsigned char *payload;
unsigned char *end;
unsigned char buf[0];
} pdbuf_t;
pdbuf_t *pdbuf_alloc(unsigned int sz, int ignore_mtu);
void pdbuf_free(pdbuf_t *pdbuf);
void pdbuf_init();
void pdbuf_deinit();
Line 6-10: struct _pdbuf结构体包含3个成员,buf[0]是GNU C中的可变长数组,不占内存空间,sizeof(struct _pdbuf)在32位系统上占8个字节。为struct _pdbuf分配内存空间时除了存放payload和end成员,多分配的内存空间跟在end指针的后面,用于存放有效数据。如果将跟在end后面的内存空间看成长度为n的unsigned char数组,buf就是数组首地址&buf[0]。payload指针指向buf数组中的某个字节&buf[i],pdbuf流经DIY TCP/IP上层各个模块,添加协议头部后,指向当前模块添加的的协议头部,end指向&buf[n]。用图示表示pdbuf数据结构:
Line 12-15: 4个函数声明,分别是pdbuf_alloc用于动态分配pdbuf,pdbuf_free释放分配的pdbuf 。pdbuf_init初始化pdbuf模块,pdbuf_deinit销毁pdbuf模块,对应的函数实现在pdbuf.c文件中。本章最后一节介绍pdbuf模块对buffer操作的函数时,再向pdbuf.h头文件中添加,push,pop,insert,reweind等函数声明。
介绍pdbuf.h头文件中声明的4个函数实现之前,先来看pdbuf.c中定义的一个重要的数据结构reserved_hdr_t,该数据结构确保为pdbuf分配内存时预留足够的空间给各层协议头部使用。
typedef struct _reserved_hdr {
union {
arphdr_t arphdr;
iphdr_t iphdr;
} l3;
union {
tcphdr_t tcphdr;
udphdr_t udphdr;
icmphdr_t icmphdr;
} l4;
} __attribute__ ((packed)) reserved_hdr_t;
#define RESERVED_HEADER_SZ (sizeof(reserved_hdr_t))
static unsigned int allocated_buf = 0;
static unsigned int freed_buf = 0;
Line 1-11: reserved_hdr_t数据结构有两个成员,l3和l4,均为union数据类型,l3包括arphdr和iphdr,分别是layer3的ARP数据帧和IP头部,6.1节已经介绍过,ARP和IP数据跟在以太网头的后面,同属于layer3层。l4包括tcphdr,udphdr和icmphdr,分别是TCP,UDP和ICMP的协议头部,这三种协议均基于IP协议实现。DIY TCP/IP上层模块构建需要发送的数据帧时,为数据帧添加layer3层的协议头部,只可能是ARP或IP协议的其中一个,添加layer4层的协议头部也只能是TCP,UDP,ICMP其中的一个。union数据类型可以保证sizeof(l3)占用的内存空间可以存放layer 3层的所有协议中,最大的协议头部数据。同样sizeof(l4)占用的内存空间,也可以存放layer4层最大的协议头部数据。所以为pdbuf分配存放数据的内存空间时,额外再分配sizeof(reserved_hdr_t)字节的空间,就可以保证预留的协议头部内存空间可以存放l3和l4层最大的协议头部数据。
Line 13: 方便使用预留协议头部内存空间的大小,定义RESERVED_HEADER_SZ宏。
Line 15-16: 两个无符号整型值,保存pdbuf模块的统计信息。allocated_buf表示pdbuf模块已经分配的pdbuf的数量,freed_buf表示pdbuf模块已经释放的pdbuf的数量。这两个值不相等,则说明有内存重复释放,或者是内存泄漏。
接下来看pdbuf.c文件中pdbuf_alloc的实现
pdbuf_t *pdbuf_alloc(unsigned int sz, int ignore_mtu)
{
pdbuf_t *pdbuf = NULL;
unsigned int alloc_sz = 0;
if (!ignore_mtu) {
/* size: pdbuf_t descriptor + reserved header size + ethernet header size + required size */
if ((sz + RESERVED_HEADER_SZ) > MTU_SIZE || sz == 0) {
sz = MTU_SIZE - RESERVED_HEADER_SZ;
}
}
alloc_sz = sz + RESERVED_HEADER_SZ + sizeof(ethhdr_t) + sizeof(pdbuf_t);
log_printf(VERBOSE, "required size: %u, alloc size: %u, reserved: %u, descriptor: %u\n",
sz, alloc_sz, (RESERVED_HEADER_SZ + sizeof(ethhdr_t)), sizeof(pdbuf_t));
pdbuf = malloc(alloc_sz);
if (pdbuf == NULL) {
log_printf(DEBUG, "Failed to alloc pdbuf, %s (%d)\n", strerror(errno), errno);
goto out;
}
memset(pdbuf, 0, alloc_sz);
/* end offset: 1st invalid byte */
pdbuf->end = (unsigned char *)pdbuf + alloc_sz;
/* payload offset: end */
pdbuf->payload = pdbuf->end;
allocated_buf += 1;
out:
return pdbuf;
}
pdbuf_alloc是对malloc函数的封装。入参sz是请求的内存空间大小,ignore_mtu表示分配内存空间时是否忽略MTU的限制。MTU宏的定义在common.h头文件中,代表以太网最大协议传输单元,值为1500。该值表示跟在以太网头部后面的有效载荷最多为1500字节,不包括以太网头部的14字节。DIY TCP/IP的IP模块重组IP分片时,会申请超过MTU大小的内存空间,这种情况下调用pdbuf_alloc时指定ignore_mtu为1,则分配内存空间时忽略MTU的限制。pdbuf_alloc将分配的内存空间封装在pdbuf数据结构中,成功时返回pdbuf的指针,失败返回NULL。
Line 1-11: ignore_mtu为0的情况下,判断请求的内存空间加上预留协议头部空间的大小是否超过MTU。如果超过MTU,或者sz为0的情况下,将sz设置为MTU – RESERVED_HEADER_SZ。RESERVED_HEADER_SZ是层3加层4协议头部的最大值。如果设置了ignore_mtu,则不对sz做任何处理。
Line 12-20: 计算需要申请的内存空间大小,分配的内存的空间需要存放待发送数据的有效载荷,预留的协议头部数据,以太网头部数据和pdbuf_t数据结构本身。所以向malloc申请的内存大小为代码中计算得到的alloc_sz。malloc出错时直接返回NULL,成功时初始化分配的内存空间的全部字节为0。
Line 22-29 初始化pdbuf结构体中的payload指针和end指针,payload和end均指向分配的内存空间的末尾字节的下一个字节地址,即第一个无效字节的地址。累加已经分配的pdbuf计数allocated_buf,最后返回pdbuf指针。
介绍完pdbuf_alloc,再来看pdbuf.c文件中剩下的3个函数实现,分别是pdbuf_init,pdbuf_deinit和pdbuf_free。
void pdbuf_free(pdbuf_t *pdbuf)
{
if (pdbuf == NULL)
return;
free(pdbuf);
freed_buf += 1;
}
void pdbuf_init()
{
allocated_buf = freed_buf = 0;
}
void pdbuf_deinit()
{
log_printf(DEBUG, "\n#Internal Buffer Management#\n");
log_printf(DEBUG, "Alloc: %u, Free: %u\n",
allocated_buf, freed_buf);
if (allocated_buf != freed_buf)
log_printf(ERROR, "Internal Buffer Memory Leak!\n");
allocated_buf = freed_buf = 0;
}
Line 2-8: 通过malloc分配pdbuf_t占用的内存空间时,紧跟在pdbuf_t结构体后面的是有效载荷的内存空间,所以释放内存空间时,只需要释放pdbuf指向的内存即可,这也是使用可变长数组的初衷,简化释放代码的实现。调用C库函数free释放内存空间,最后累加已经释放的pdbuf的计数freed_buf。
Line 10-13: pdbuf模块的初始化函数pdbuf_init,目前只需要将alloced_buf和freed_buf初始化为0,后续章节可以根据需要扩展该函数。
Line 15-23: pdbuf_deinit的调用在在init.c中,netdev_start_loop返回时,即DIY TCP/IP被销毁,需要调用各个模块的deinit函数释放内存空间。已经添加了网络设备模块的netdev_deinit用于释放网络设备模块的内存空间。pdbuf模块加入后,还需调用pdbuf_deinit打印pdbuf的分配和释放情况,若两个值不相等,则说明DIY TCP/IP被销毁时有内存泄露,或内存的重复释放,为debug提供线索。
6.3 pdbuf模块的函数接口
本节介绍pdbuf模块的基本操作函数包括:pdbuf_push,pdbuf_pop,pdbuf_insert,pdbuf_empty,pdbuf_rewind。这些函数均做为内联函数实现pdbuf.h头文件中。
static inline void pdbuf_push(pdbuf_t *pdbuf, unsigned int sz)
{
assert((pdbuf->payload - sz) >= pdbuf->buf);
pdbuf->payload -= sz;
}
static inline unsigned int pdbuf_pop(pdbuf_t *pdbuf, void *dst, unsigned int sz)
{
if (pdbuf->payload + sz > pdbuf->end)
sz = pdbuf->end - pdbuf->payload;
assert((pdbuf->payload + sz) <= pdbuf->end);
memcpy(dst, pdbuf->payload, sz);
pdbuf->payload += sz;
return sz;
}
static inline unsigned int pdbuf_empty(pdbuf_t *pdbuf)
{
return pdbuf->payload == pdbuf->end;
}
static inline void pdbuf_insert(pdbuf_t *pdbuf, void *src, unsigned int sz)
{
assert((pdbuf->payload + sz) < pdbuf->end);
memcpy(pdbuf->payload, src, sz);
pdbuf->payload += sz;
}
static inline void pdbuf_rewind(pdbuf_t *pdbuf, unsigned int sz)
{
assert((pdbuf->buf + sz) < pdbuf->payload);
pdbuf->payload = pdbuf->buf + sz;
}
#endif
Line 1-5: pdbuf_push函数,入参为pdbuf指针和sz。pdbuf_alloc在分配pdbuf时,将pdbuf->payload指向分配内存空间末尾第一个无效的字节地址处,如果将字节数组pdbuf->buf看做栈,则pdbuf->payload指向地址最大的栈底,栈底字节是无效的内存空间。pdbuf内存空间开始的8个字节是pdbuf_t结构体,剩下的部分是buf字节数组。调用者向pdbuf->buf中写入数据时,需调用pdbuf_push将pdbuf->payload指针向地址减小的方向移动sz个字节。然后调用者再向pdbuf->payload指向的内存写入sz个字节的内容。整个过程类似成向pdbuf中”push”数据。line19 assert确保移动pdbuf->payload指针时不超过pdbuf->buf无符号字节数组的第一个字节的地址。
Line 7-15: pdbuf_pop函数将pdbuf->buf中的数据拷贝到目的地址dst指向的内存空间。入参为pdbuf指针,目的地址dst和sz。pdbuf_push将pdbuf->payload向地址减小的方向移动,pdbuf_pop是反向操作,将pdbuf->payload向地址增大的方向移动。拷贝数据之前先判断需要拷贝的字节数,如果pdbuf->payload + sz大于pdbuf->end,则将sz调整为pdbuf->buf中剩余的所有字节总数。然后拷贝sz个字节到dst中,再将pdbuf->payload向地址增大的方向移动sz个字节。
Line 17-20: pdbuf_empty函数判断pdbuf->buf中是否存有数据。pdbuf刚被pdbuf_alloc分配出来时,pdbuf->payload和pdbuf->end是相等的,通过pdbuf_push向pdbuf存入数据,pdbuf_pop向pdbuf提取数据,都为更新pdbuf->payload的值。如果payload和end相等,代表pdbuf->buf中没有存放任何有效数据。
Line 22-27: pdbuf_insert函数,向pdbuf->buf中插入数据。将入参src指向的内存空间的sz个字节拷贝到pdbuf->payload指向的内存空间,更新pdbuf->payload向地址增大的方向移动,该函数多数情况与pdbuf_reweind函数配合使用。
Line 29-33: pdbuf_reweind函数,重新调整pdbuf->payload指针的值为pdbuf->buf + sz。
DIY TCP/IP各模块构建需要发送的数据帧时,均通过pdbuf模块分配内存,通过pdbuf模块的接口函数构建各模块的协议数据,添加协议头部。本节最后修改Makefile和init.c文件,将pdbuf模块加入编译,在DIY TCP/IP的初始化时调用pdbuf_init,销毁时调用pdbuf_deinit。
修改init.c
int main(int argc, char *argv[])
{
int ret = 0;
net_device_t *ndev = NULL;
signal(SIGINT, signal_handler);
ndev = netdev_init(DEFAULT_IFNAME);
if (ndev == NULL)
return -1;
pdbuf_init();
netdev_start_loop(ndev);
out:
netdev_deinit(ndev);
pdbuf_deinit();
return ret;
}
修改Makefile
将pdbuf.o目标文件链接到可执行程序tcp_ip_stack,添加Makefile目标pdbuf.o,对应的依赖为pdbuf.c和pdbuf.h,处理命令是编译pdbuf.o目标文件。
tcp_ip_stack:device.o init.o debug.o pdbuf.o
gcc -o tcp_ip_stack device.o init.o debug.o pdbuf.o -lpcap -lpthread
device.o:device.c device.h init.h common.h
gcc -c device.c
init.o:init.c init.h
gcc -c init.c
debug.o:debug.c debug.h
gcc -c debug.c
pdbuf.o:pdbuf.c pdbuf.h
gcc -c pdbuf.c
clean:
rm -rf *.o
rm -rf tcp_ip_stack
6.4 小结
本章介绍了DIY TCP/IP的pdbuf模块,定义了各层协议头部的数据结构。详细介绍了pdbuf模块的各个函数接口实现,pdbuf动态分配内存时如何预留各层协议头部的内存空间。与第5章的网络设备层发送队列一样,pdbuf模块的代码暂时也得不到验证。发送队列和pdbuf模块的功能将在DIY TCP/IP的ARP模块实现时得到验证。
当前目录结构:
下一篇:DIY TCP/IP ARP模块0