DIY TCP/IP 网络设备模块5

上一篇:DIY TCP/IP 网络设备模块4
5.4 网络设备模块的发送队列
网络设备层发送队列用于异步处理DIY TCP/IP上层模块发送的数据帧。为上层模块提供网络设备层的发送接口。上层模块的数据帧进入网络设备模块的发送队列后,唤醒发送线程,从发送队列中取出数据帧,通过PF_PACKET类型的socket,将数据帧逐个发送到Linux Kernel网络设备的驱动中。与网络设备模块接收队列的实现类似,首先修改网络设备结构体,添加发送队列相关的数据结构。
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;
  queue_t rxpkt_q;
  /* tx */
  int tx_sock;
  pthread_t tx_thread;
  pthread_cond_t txq_cond;
  pthread_mutex_t txq_mutex;
  queue_t txpkt_q;
  /* dev info */
  int ifindex;
 } net_device_t;

Line 9: tx_sock文件描述符为PF_PACKET类型的socket。
Line 10-13: tx_thread是发送线程,txq_cond是发送线程的睡眠等待条件,txq_mutex是保护睡眠等待条件和发送队列的互斥量,txpkt_q是发送队列。
Line 15: ifindex, 网络接口的序号,指定发送数据帧时使用的linux kernel中的网络设备。
5.4.1 发送线程
dev_tx_init与dev_rx_init类似,用于初始化与发送线程相关的数据结构。在网络设备的初始化函数netdev_init中被调用。也有与销毁接收线程类似的dev_tx_deinit函数,在销毁网络设备时被调用。

 static int dev_tx_init(net_device_t *ndev)
 {
         int ret = 0;
 
         if (ndev == NULL)
                 return -1;
         log_printf(DEBUG, "Network device TX init\n");
         /* init tx queue wait condition */
         if (pthread_cond_init(&ndev->rxq_cond, NULL)) {
	         log_printf(DEBUG, "Failed to init txq condition, %s (%d)\n",
	             strerror(errno), errno);
	         ret = -errno;
	         goto out;     
	     }
         /* init tx queue mutex */
         if (pthread_mutex_init(&ndev->rxq_mutex, NULL)) {
	         log_printf(DEBUG, "Failed to init txq mutex, %s (%d)\n",
	             strerror(errno), errno);
	         ret = -errno;
	         goto out;
	     }
         /* init tx thread */
         terminate_loop = 0;
         if(pthread_create(&ndev->tx_thread, NULL, dev_tx_routine, (void *)ndev)) {
                 log_printf(DEBUG, "Failed to create rx thread: %s (%d)\n",
                                 strerror(errno), errno);
                 ret = -errno;
         }
         /* init tx packet queue */
         queue_init(&ndev->txpkt_q);
         /* create tx socket */
        ndev->tx_sock = socket(AF_PACKET, SOCK_RAW, HSTON(ETH_P_ALL));
        if (ndev->tx_sock < 0) {
             log_printf(ERROR, "Failed to create tx socket\n");
             ret = -1;
             goto out;
        }
  out:
         return ret;
  }

Line 5-14: pthread_cond_init初始化发送线程的睡眠等待条件。
Line 15-21: pthread_mutex_init初始化保护睡眠等待条件和发送队列的互斥量互斥量。
Line 23-30: terminate_loop同样也是发送线程函数体dev_tx_routine的结束条件,初始值为0,该变量在netdev_stop_loop中被设置为1。pthread_create创建发送线程,指定发送线程函数体的入参为指向网络设备的指针netdev。dev_tx_routine从发送队列中取出上层模块发送的数据帧,通过PF_PACKET的socket将数据帧发送给Linux Kernel网络设备的驱动。queue_init初始化发送队列。
Line 31-39: 创建发送套接字tx_sock,域为AF_PACKET,类型是SOCK_RAW,协议为ETH_P_ALL。通过tx_socket发送的数据帧需包含802.3的头部,上层模块发送的数据帧,根据目标IP地址查找DIY TCP/IP ARP模块的地址映射表,为上层中添加802.3头部。Linux kernel的网络协议栈不对通过SOCK_RAW类型的socket发送的数据做任何改动,直接将数据发送到Linux kernel中与网络接口对应的设备的驱动。
创建和销毁函数成对出现是本人实现代码的习惯,目的是避免内存泄漏。再来介绍发送线程的销毁函数dev_tx_deinit。

 static void dev_tx_deinit(net_device_t *ndev)
 {
         if (ndev == NULL)
                 return;
         log_printf(DEBUG, "Network device TX deinit\n");
         pthread_cond_signal(&ndev->txq_cond);
         /* join tx thread */
         if (pthread_join(ndev->tx_thread, NULL))
	         log_printf(DEBUG, "tx thread join failed, %s (%d)\n",
	         strerror(errno), errno);
         /* flush dev tx queue */
	     dev_flush_txpktq(ndev);
         /* destroy tx queue wait condition */
         if (pthread_cond_destroy(&ndev->txq_cond))
	         log_printf(DEBUG, "txq condition destroy failed, %s (%d)\n",
	         strerror(errno), errno);
         /* destroy tx queue mutext */
         if (pthread_mutex_destroy(&ndev->txq_mutex))
	         log_printf(DEBUG, "txq mutex destroy failed, %s (%d)\n",
	         strerror(errno), errno);
	     /* close tx socket */
	     if (ndev->tx_sock)
	         close(ndev->tx_sock);
 }

Line 1-12: pthread_cond_signal唤醒睡眠等待在txq_cond条件上的发送线程,pthread_join等待发送线程结束。dev_flush_txpktq清空发送队列,释放数据帧占用的内存。
Line 13-23: pthread_cond_destroy销毁睡眠等待条件txq_cond,pthread_mutex_destroy销毁互斥量txq_mutex。最后关闭发送套接字tx_sock。
再来修改网络设备初始化函数netdev_init和销毁函数netdev_deinit,分别添加对dev_tx_init和dev_tx_deinit的调用。

 net_device_t * netdev_init(char *ifname)
 {
…
     /* rx init */
     if (dev_rx_init(ndev))
         goto out;	
     /* tx init */
     if (dev_tx_init(ndev))
         goto out;
     return ndev;
 out:
     netdev_deinit(ndev);
     return NULL;
 }

Line 2-3: 省略的代码与5.1节netdev_init一致,dev_tx_init的调用放在接收逻辑的初始化函数dev_rx_init之后。

 void netdev_deinit(net_device_t *ndev)
 {
     if (ndev == NULL)
         return;
     log_printf(DEBUG, "Network device deinit\n");
     if (ndev->pcap_dev)
         pcap_close(ndev->pcap_dev);
     dev_rx_deinit(ndev);
     dev_tx_deinit(ndev);
     free(ndev);
     gndev = NULL;
 }

本节介绍与发送线程相关的初始化和销毁函数,其中涉及到清空发送队列的函数dev_flush_txpktq和发送线程的函数体dev_tx_routine。这两个函数的实现在下节引入。
5.4.2 发送队列
5.4.1节已经引入了发送线程的函数体dev_tx_routine,和发送队列的清空函数dev_flush_txpktq。本节除了介绍这两个函数的具体实现之外,还会引入新的函数netdev_tx_pkt,该函数是网络设备模块暴露给其他模块使用的接口,用于将上层模块的待发送数据帧加入网络设备模块的发送队列。
dev_tx_routine

 static void *dev_tx_routine(void *args)
 {
     net_device_t *ndev = NULL;
     queue_t *qnode = NULL;
     dev_txpkt_t *txpkt = NULL;
 
     if (args == NULL)
         goto exit;
     ndev = (net_device_t *)args;
     while(!terminate_loop) {
         pthread_mutex_lock(&ndev->txq_mutex);
         pthread_cond_wait(&ndev->txq_cond, &ndev->txq_mutex);
         pthread_mutex_unlock(&ndev->txq_mutex);
         if (terminate_loop)
             break;
 again:
         pthread_mutex_lock(&ndev->txq_mutex);
         if (!queue_empty(&ndev->txpkt_q)) {
             qnode = dequeue(&ndev->txpkt_q);
             pthread_mutex_unlock(&ndev->txq_mutex);
             txpkt = container_of(qnode, dev_txpkt_t, node);
             dev_process_txpkt(txpkt);
             goto again;
         }
         pthread_mutex_unlock(&ndev->txq_mutex);
     }
 exit:
     log_printf(DEBUG, "Dev tx routine exited\n");
     pthread_exit(0);
 }


Line 9: 将入参args转换为网络设备结构体的指针。
Line 10-15: 发送线程的函数体是一个while循环,结束条件terminate_loop在创建发送线程时被初始化为0。只有在netdev_stop_loop被调用时,treminate_loop才会被置为1。while循环内先获得保护睡眠等待条件的互斥量,防止多线程并发访问。pthread_cond_wait检查睡眠等待条件,如果需要睡眠,pthread_cond_wait会释放txq_mutex互斥量,避免发送线程睡眠时仍然持有互斥量,然后让出CPU,使发送线程睡眠。如果发送线程被唤醒,pthread_cond_wait返回,pthred_cond_wait返回之前会再次获取互斥量txq_mutex,所以pthread_cond_wait返回后需要首先释放互斥量txq_mutex,然后判断退出条件terminate_loop是否为1,因为发送线程睡眠等待时terminate_loop有可能被置1。
Line 16-25: 发送线程循环体继续执行,获取保护发送队列的互斥量txq_mutex,判断发送队列不空时,取出队列中的节点,利用container_of宏获取封装queue_t的数据结构dev_txpkt_t的指针,交由dev_process_txpkt处理。跳转到again标号处继续处理发送队列中的数据帧,直到发送队列为空。
Line 26-30: 发送线程退出之前,pthread_exit设置返回值,pthread_join的调用者可以获取到该返回值。
dev_process_txpkt

 static void dev_process_txpkt(dev_txpkt_t *txpkt)
 {
     if (txpkt == NULL)
         return;
     log_printf(VERBOSE, "process tx pkt\n");
     if (txpkt->pdbuf)
         free(txpkt->pdbuf);
     free(txpkt);
 }

DIY TCP/IP尚未实现任何上层模块,dev_process_txpkt仅仅是打印信息,表示发送线程被成功唤醒,并且取到了发送队列中的数据帧。不对发送数据做任何处理,先释放dev_txpkt_t中payload指向的内存,再释放dev_txpkt_t占用的内存。介绍DIY TCP/IP的ARP模块的实现时,再扩展dev_process_txpkt函数,通过PF_PACKET类型的socket发送数据帧。
dev_flush_txpktq

 static void dev_flush_txpktq(net_device_t *ndev)
 {
     int flush_count = 0;
     queue_t *qnode = NULL;
     dev_txpkt_t *txpkt = NULL;
 
     if (ndev == NULL)
         return;
     while(!queue_empty(&ndev->txpkt_q)) {
         qnode = dequeue(&ndev->txpkt_q);
         txpkt = container_of(qnode, dev_txpkt_t, node);
         free(txpkt->pdbuf);
         free(txpkt);
         flush_count += 1;
     }
     log_printf(DEBUG, "Dev txq flushed %d packets\n", flush_count);
 }

该函数在netdev_deinit中被调用,具体是在发送线程退出之后被调用。所以在清空发送队列时,不需要获取保护发送队列的互斥量。遍历发送队列中的节点,通过containter_of得到封装队列节点的dev_txpkt_t的指针。此时发送线程已经退出,直接释放payload指针指向的内存,和dev_txpkt_t数据结构占用的内存即可。
netdev_tx_pkt

 void netdev_tx_pkt(void *pdbuf)
 {
     dev_txpkt_t *txpkt = NULL;
     if (gndev == NULL || pdbuf == NULL) {
         log_printf(ERROR, "Invalid net device tx parameter\n");
         return;
     }
     txpkt = malloc(sizeof(dev_txpkt_t));
     if (txpkt == NULL) {
         log_printf(ERROR, "No memory for tx packet, %s (%d)\n",
                 strerror(errno), errno);
         return;
     }
     txpkt->pdbuf = pdbuf;
     /* enqueue tx pkt */
     pthread_mutex_lock(&gndev->txq_mutex);
     enqueue(&gndev->txpkt_q, &txpkt->node);
     pthread_mutex_unlock(&gndev->txq_mutex);
     /* wake up tx thread */
     pthread_cond_signal(&gndev->txq_cond);
 }

netdev_tx_pkt是网络设备模块暴露给DIY TCP/IP其他模块的接口,将数据帧加入网络设备模块的发送队列,唤醒发送线程异步处理待发送的数据帧。
Line 8-14: 为dev_txpkt_t数据结构分配内存,将上层模块的数据帧封装在dev_txpkt_t结构中,txpkt->pdbuf指向上层模块发送的数据帧。
Line 15-20: 获取txq_mutex互斥量,将dev_txpkt_t加入发送队列,释放互斥量txq_mutex,最后调用pthread_cond_signal唤醒发送线程。
DIY TCP/IP的上层模块尚未实现,本节发送队列的验证留在ARP模块的实现章节,当ARP模块收到ARP请求,并判断需要回复ARP Reply数据帧时,netdev_tx_pkt接口将被第一次调用,本节实现的代码也会得到验证。
下一篇:DIY TCP/IP 网络设备模块6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值