DIY TCP/IP Payload Buffer模块1

上一篇: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数据结构:
DIY TCP/IP 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 End of Ch6
下一篇:DIY TCP/IP ARP模块0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值