上一篇: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