DIY TCP/IP 网络设备模块3

上一篇:DIY TCP/IP 网络设备模块2
5.3.2 链表和队列的实现
介绍网络设备模块的接收队列之前,先来实现DIY TCP/IP的第一个utility,也是各个模块都会用到的链表和队列。不像c++或java,提供丰富的数据类型如:链表,队列,字典等,C语言需自己动手实现链表和队列。这种每个模块都会使用的代码放在DIY TCP/IP的utility.c 文件中,相应的头文件是utility.h。本节的链表是参考Linux kernel中经典的list head的实现,并在此基础上封装实现队列。链表,队列数据结构的定义在utility.h头文件中。链表,队列的基本操作函数,由于函数体较短,都定义为内联函数实现在utility.h头文件中。
utility.h

  #ifndef _UTILS_H_
  #define _UTILS_H_
  #include <stddef.h>
  typedef struct _list_head {
   struct _list_head *prev;
   struct _list_head *next;
  } list_head_t;
 

Line 3: 引用stddef.h头文件,是为了使用offsetof宏,offsetof用于实现container_of宏,container_of的实现在介绍队列时引入。
Line 4-7: list_head_t数据类型是双向循环链表的表头。数据结构本身包含两个指针prev和next,分别用于指向双向循环链表中的前向节点和后续节点,数据结构本身也可以做为双向循环链表的节点,被其他数据结构封装。
链表表头初始化

 static inline void list_init(list_head_t *list_head)
 {
  list_head->prev = list_head->next = list_head;
 }

链表节点
链表表头插入操作

 static inline void list_add_head(list_head_t *head, list_head_t *node)
 {
  node->next = head->next;
  node->prev = head;
 
  head->next->prev = node;
  head->next = node;
 }

假设N2为待插入节点,H为链表头,N1为H的下一个链表节点。list_add_head将N2插入H后面。
链表表头插入操作0
链表表头插入操作1
Line 3-4: 分别对应上图中的1和2
链表表头插入操作2
Line 6: 对应上图中的3
链表表头插入操作3
Line 7: 对应上图中的4
链表表头插入操作4
list_head_add函数中的4步操作执行之后,就完成了在双向循环链表表头节点后面插入节点的操作。该函数同样适用于只有一个表头节点的情况。
链表表尾插入操作

 static inline void list_add_tail(list_head_t *head, list_head_t *node)
 {
  node->next = head;
  node->prev = head->prev;
 
  head->prev->next = node;
  head->prev = node;
 }

链表表尾插入操作1
Line 3-4:对应上图中的1和2
链表表尾插入操作2
Line 6-7: 对应上图中的3和4
链表表尾插入操作3
list_add_tail函数中的4步操作执行之后,就完成在双向循环链表表尾插入节点的操作,该函数同样也适用于只有一个表头节点的情况。
链表插入操作

 static inline void list_add(list_head_t *prev, list_head_t *node)
 {
  node->next = prev->next;
  node->prev = prev;
 
  prev->next->prev = node;
  prev->next = node;
 }

list_add在双向循环链表的prev节点后插入节点,该函数与list_add_head类似,不再画图细述。
链表删除操作

 static inline void list_del(list_head_t *node)
 {
  node->prev->next = node->next;
  node->next->prev = node->prev;
 }

list_del将节点node从双向循环链表中删除。例如将N2从链表中删除:
链表节点删除操作
Line 3-4: 对应上图中的1和2,节点N2的next和prev指针并没有重置,但在双向循环链表中已经不能通过任何节点的前向节点或后向节点找到N2了。N2节点占用的内存由调用者释放。
链表判空操作

 static inline int list_empty(list_head_t *list_head)
 {
  return (list_head->next == list_head && list_head->prev == list_head);
 }

list_empty判断双向循环链表是否为空,如果表头节点的next和prev都指向自身,链表为空,list_empty操作通常在删除节点之前被调用。
获取节点操作

 static list_head_t *list_get_node(list_head_t *head)
 {
  if (list_empty(head))
      return NULL;
  else
      return head->next;
 }

list_get_node函数在链表不空时,返回双向循环链表表头节点的下个节点。至此链表的基本操作函数都已实现,接下来在链表的数据结构和基本函数操作的基础上实现队列。引入队列的数据结构之前,先实现containter_of宏。

  #define container_of(ptr, type, member) ({               \
       const typeof( ((type *)0)->member )* __tptr = ptr;  \
       (type *)((char *)ptr - offsetof(type, member));     \
       })

container_of宏如字面意思,由数据结构的某个成员的地址,获取数据结构的首地址。ptr是数据结构中某个成员的地址,type是该数据结构的数据类型,member是ptr对应的成员在数据结构中的名称。
container_of宏与NTOHS的实现方式一致,都是将C语言中的大括号复合语句包裹在小括号表达中实现,返回大括号复合语句的最后一条表达式的值。
Line 5: 将0地址强制类型转换成type类型的指针,从而获取到数据成员member,再通过typeof获得member成员的数据类型,通过member成员的数据类型,定义临时变量__tptr为指向member数据类型的指针,将其赋值为传入container_of的ptr的值。
Line 6: offsetof(type, member)即数据结构type中member成员的偏移量,由ptr减去member成员的偏移量即得到数据结构type的首地址,再强制转换为type类型的指针。
将链表数据结构封装在队列数据结构中,由container_of宏通过链表节点的地址就可以获取封装该链表节点的队列节点的地址。
队列

 typedef struct _queue {
  list_head_t head;
 } queue_t;

队列数据结构queue_t封装链表节点list_head_t。

 static inline void queue_init(queue_t *q)
 {
  list_init(&q->head);
 }
 
 static inline int queue_empty(queue_t *q)
 {
  return list_empty(&q->head);
 }

queue_init,队列初始化操作,封装链表的表头初始化操作,queue_empty队列判空操作封装链表的判空操作。

 static inline void enqueue(queue_t *qhead, queue_t *qnode)
 {
  list_add_tail(&qhead->head, &qnode->head);
 }
 
 static inline queue_t *dequeue(queue_t *qhead)
 {
  list_head_t *node = NULL;
  node = qhead->head.next;
  list_del(node);
  return container_of(node, queue_t, head);
 }

enqueue入队操作,封装list_add_tail即在双向循环链表的表尾插入节点,dequeue出队操作,首先获取表头节点的下一个节点的指针,然后从链表中删除该节点,再通过container_of宏返回封装该节点的队列节点的指针,即完成出队操作。
通过链表数据结构,和链表基本操作函数,配合container_of宏,可以快速的实现队列的FIFO操作函数。队列数据结构和FIFO操作函数实现后,就可以在此基础上实现DIY TCP/IP网络设备模块的接收队列了。
下一篇:DIY TCP/IP 网络设备模块4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值