DIY TCP/IP 网络设备模块2

上一篇:DIY TCP/IP 网络设备模块1
5.3 网络设备模块的接收队列
DIY TCP/IP网络设备模块的接收队列异步处理pcap_callback返回的数据帧,在接收线程中解析数据帧,根据以太网头部类型,将数据帧交给DIY TCP/IP的上层模块处理。接收队列的实现包括:接收线程;定义网络设备模块数据帧的数据结构;链表和队列的实现;接收线程异步处理链路层数据帧。
5.3.1 接收线程
接收线程与网络设备相关,所以在网络设备结构体net_device_t中加入接收线程相关的成员,更新网络设备结构体如下:

  #ifndef _DEVICE_H_
  #define _DEVICE_H_
...
 
  typedef struct _net_device {
   pcap_t *pcap_dev;
   /* rx */
   pthread_t rx_thread;
   pthread_cond_t rxq_cond;
   pthread_mutex_t rxq_mutex;
  } net_device_t;
...
#endif

device.h中省略的部分与5.1节一致。
Line 8-10: rx_thread接收线程,rxq_cond接收线程的睡眠等待条件,rxq_mutex保护睡眠等待条件和接收队列的互斥量。
网络设备结构体中加入接收线程相关的数据结构后,对应在网络设备的初始化函数netdev_init中加入对接收线程的初始化操作。device.c中netdev_init函数的新增代码如下:

 net_device_t * netdev_init(char *ifname)
 {
...
     /* rx init */
     if (dev_rx_init(ndev))
         goto out;
 
     return ndev;
 out:
     netdev_deinit(ndev);
     return NULL;
 }
 
 void netdev_deinit(net_device_t *ndev)
 {
     if (ndev == NULL)
         return;
     printf("Network device deinit\n");
     if (ndev->pcap_dev)
         pcap_close(ndev->pcap_dev);
     dev_rx_deinit(ndev);
     free(ndev);
     gndev = NULL;
 }

Line 1-3: 省略的代码与5.1节一致,在netdev_init函数设置帧过滤条件pcap_setfilter之后,调用dev_rx_init初始化接收线程。
Line 14-23: 网络设备的初始化函数中加入了接收线程的初始化操作,相应的要在网络设备的销毁函数netdev_deinit中加入销毁接收线程的操作。pcap_close关闭pcap设备,与dev_rx_init对应,dev_rx_deinit销毁接收线程,最后释放网络设备占用的内存空间,销毁网络设备。
dev_rx_init和det_rx_deinit是device.c中新增的静态函数,均通过网络设备结构体net_device_t访问接收线程相关的数据结构。
dev_rx_init函数

  static int dev_rx_init(net_device_t *ndev)
  {
          int ret = 0;
  
          if (ndev == NULL)
                  return -1;
          printf("Network device RX init\n");
          /* init rx queue wait condition */
          if (pthread_cond_init(&ndev->rxq_cond, NULL)) {
              printf("Failed to init rxq condition, %s (%d)\n",
              strerror(errno), errno);
              ret = -errno;
              goto out;
          
          }
          /* init rx queue mutex */
          if (pthread_mutex_init(&ndev->rxq_mutex, NULL)) {
              printf("Failed to init rxq mutex, %s (%d)\n",
                  strerror(errno), errno);
              ret = -errno;
              goto out;
          }
          /* init rx thread */
          terminate_loop = 0;
          if(pthread_create(&ndev->rx_thread, NULL, dev_rx_routine, (void *)ndev)) {
                  printf("Failed to create rx thread: %s (%d)\n",
                                  strerror(errno), errno);
                  ret = -errno;
          }
  out:
          return ret;
  }

Line 3-15: pthread_cond_init初始化接收线程的睡眠等待条件。可以通过man page查看pthread库函数的使用说明。如果遇到错误提示:”No menu entry for pthread_xxx”,则需要先执行”sudo apt-get install manpages-posix-dev”安装posix-dev man page。
Line 16-22: pthread_mutex_init初始化互斥量rxq_mutex,防止DIY TCP/IP主线程和接收线程并发访问接收队列和睡眠等待条件。
Line 23-31: terminate_loop是定义在device.c中的静态整型变量,初始值为0。该变量在netdev_stop_loop中被设置为1,用于结束接收线程循环体的执行。
pthread_create创建接收线程,dev_rx_routine是接收线程的函数体,网络设备结构体的指针ndev是dev_rx_routine的入参。该函数负责从接收队列中取出pcap_loop返回的链路层数据帧,根据以太网头部类型,将数据帧交给DIY TCP/IP的上层模块处理。由于接收队列尚未实现,本节在pcap_loop接收到链路层数据帧后,唤醒接收线程,接收线程给出打印信息即可,后续章节继续扩展实现dev_rx_routine。
dev_rx_deinit函数

   static void dev_rx_deinit(net_device_t *ndev)
   {
         if (ndev == NULL)
            return;
         printf("Network device RX deinit\n");
         pthread_cond_signal(&ndev->rxq_cond);
         /* join rx thread */
         if (pthread_join(ndev->rx_thread, NULL))
         	printf("rx thread join failed, %s (%d)\n",
         	strerror(errno), errno);
         /* destroy rx queue wait condition */
         if (pthread_cond_destroy(&ndev->rxq_cond))
         	printf("rxq condition destroy failed, %s (%d)\n",
         	strerror(errno), errno);
         /* destroy rx queue mutext */
         if (pthread_mutex_destroy(&ndev->rxq_mutex))
         	printf("rxq mutex destroy failed, %s (%d)\n",
         	strerror(errno), errno);
    }

Line 1-18: pthread_cond_signal唤醒睡眠等待在rxq_cond条件上的接收线程,pthread_join等待接收线程结束。pthread_cond_destroy销毁睡眠等待条件rxq_cond,pthread_mutex_destroy销毁互斥量rxq_mutex。
dev_rx_routine函数
dev_rx_routine是接收线程的函数体,dev_rx_init函数中创建接收线程时,指定dev_rx_routine的入参是网络设备的指针。

  static void *dev_rx_routine(void *args)
  {
      net_device_t *ndev = NULL;
      if (args == NULL)
          goto exit;
      ndev = (net_device_t *)args;
      while(!terminate_loop) {
          pthread_mutex_lock(&ndev->rxq_mutex);
          pthread_cond_wait(&ndev->rxq_cond, &ndev->rxq_mutex);
          pthread_mutex_unlock(&ndev->rxq_mutex);
          if (terminate_loop)
              break;
          printf("packet received\n");
      }
  exit:
      printf("Dev rx routine exited\n");
      pthread_exit(0);
  }

Line 6: 将入参args转换为网络设备结的指针,通过ndev获取与接收线程相关的数据成员。
Line 7-14: 先获得保护睡眠等待条件的互斥量,防止多线程并发访问。pthread_cond_wait检查睡眠等待条件,如果pthread_cont_wait判断需要睡眠等待,则释放rxq_mutex互斥量,避免接收线程在睡眠等待时持有互斥量,然后让出CPU,使线程睡眠。如果pthread_cond_wait判断可以继续执行,则pthread_cond_wait返回,先释放互斥量rxq_mutex,再检查循环结束条件terminate_loop是否为1。线程睡眠等待时terminate_loop有可能被置1,循环体的最后,打印输出”packet received”表示接收线程被成功唤醒,接收队列中有可用的数据帧。
Line 16-17: 线程循环体结束时调用pthread_exit使线程退出,释放线程资源,使pthread_join的调用者检查到线程退出。
rxq_cond初始化后,调用pthread_cond_wait的接收线程阻塞,等待rxq_cond条件满足。rxq_cond的满足条件是从链路层接收到数据帧,因此在pcap_loop的回调函数pcap_callback中,调用pthread_cond_signal来唤醒接收线程。pcap_callback改动如下:

  static void pcap_callback(unsigned char *arg,
         const struct pcap_pkthdr *pkthdr, const unsigned char *packet)
  {
      net_device_t *ndev = NULL;
      ethhdr_t *ethpkt = NULL;
      if (packet == NULL || arg == NULL)
          return;
      ndev = (net_device_t *)arg;
      ethpkt = (ethhdr_t *)packet;
          printf("%ld.%06ld: capture length: %u, pkt length: %u, ethernet type: %04x, "MACSTR " --> " MACSTR"\n",
      pkthdr->ts.tv_sec, pkthdr->ts.tv_usec, pkthdr->caplen, pkthdr->len,
      NTOHS(ethpkt->type), MAC2STR(ethpkt->src), MAC2STR(ethpkt->dst));
      pthread_cond_signal(&ndev->rxq_cond);
  }

Line 13: 调用pthread_cond_signal唤醒接收线程。pcap_callback通过网络设备的指针获取rxq_cond,因此在netdev_start_loop调用pcap_loop时,设置pcap_callback的入参为网络设备的指针,netdev_start_loop改动如下:

 void netdev_start_loop(net_device_t *ndev)
 {
     if (ndev == NULL || ndev->pcap_dev == NULL)
         return;
     pcap_loop(ndev->pcap_dev, -1, pcap_callback, (void *)ndev);
     printf("pcap_loop ended\n");
 }

上文多次提到在netdev_stop_loop中设置terminate_loop条件,用于结束接收线程的循环体,netdev_stop_loop改动如下:

 void netdev_stop_loop(net_device_t *ndev)
 {
     if (ndev == NULL || ndev->pcap_dev == NULL)
         return;
     pcap_breakloop(ndev->pcap_dev);
     terminate_loop = 1;
 }
 

网络设备结构体中加入接收线程的数据结构后,所有device.c的改动都已全部给出,编译运行DIY TCP/IP,结果如下:

gannicus@ubuntu:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch2/1$ make clean
rm -rf *.o
rm -rf tcp_ip_stack
gannicus@ubuntu:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch2/1$ make
gcc -c device.c
gcc -c init.c
gcc -o tcp_ip_stack device.o init.o -lpcap -lpthread
sudo gannicus@ubuntu:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch2/1$ sudo ./tcp_ip_stack 
[sudo] password for gannicus: 
Network device init
filter: ether proto 0x0800 or ether proto 0x0806
Network device RX init
1531403200.164847: capture length: 131, pkt length: 131, ethernet type: 0800, dc:33:0d:1c:50:fc --> ff:ff:ff:ff:ff:ff
packet received
1531403201.802873: capture length: 159, pkt length: 159, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> ff:ff:ff:ff:ff:ff
packet received
1531403202.519325: capture length: 131, pkt length: 131, ethernet type: 0800, dc:33:0d:2a:95:33 --> ff:ff:ff:ff:ff:ff
packet received
1531403205.284280: capture length: 131, pkt length: 131, ethernet type: 0800, dc:33:0d:1c:50:fc --> ff:ff:ff:ff:ff:ff
packet received
^Cpcap_loop ended
Network device deinit
Network device RX deinit
Dev rx routine exited

从运行结果可以看出,pcap_callback每接收到数据帧后都会唤醒接收线程,在接收线程中打印出packet_received。
本节实现了接收线程的框架,在此基础上实现接收队列,其实就是在pcap_callback函数中将接收到的链路层数据帧加入接收队列,然后唤醒接收线程。接收线程从接收队列中取出数据帧,解析,然后交给DIY TCP/IP上层模块处理。
下一篇:DIY TCP/IP 网络设备模块3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值