3.4.4 数据预留和对齐指针
数据预留和对齐主要由skb_reserve()、skb_put()、skb_push()以及skb_pull()这几个函数来完成。htm
1.skb_reserve()blog
skb_reserve()在数据缓存区头部预留必定的空间,一般被用来在数据缓存区中插入协议首部或者在某个边界上对齐。它并无把数据移出或移入数据缓存区,而只是简单地更新了数据缓存区的两个指针-分别指向负载起始和结尾的data和tail指针,图3-15 展现了调用skb_reserve()先后这两个指针的变化。接口
请注意:skb_reserve()只能用于空的SKB,一般会在分配SKB以后就调用该函数,此时data和tail指针还一同指向数据区的起始位置,如图3-15a所示。例如,某个以太网设备驱动的接收函数,在分配SKB以后,向数据缓存区填充数据以前,会有这样的一条语句skb_reserve(skb, 2),这是由于以太网头长度为14B,再加上2B就正好16字节边界对齐,因此大多数以太网设备都会在数据包以前保留2B。ci
当SKB在协议栈中向下传递时,每一层协议都把skb->data指针向上移动,而后复制本层首部,同时更新skb->len。这些操做都使用图3-15 中所示的函数完成。get
![]() |
图3-15 在接收过程当中使用skb_reserve() a) 空的SKB b) 头部预留2个字节 c) 复制以太网帧到SKB |
2.skb_push()源码
skb_push()在数据缓存区的前头加入一块数据,与skb_reserve()相似,也并无真正向数据缓存区中添加数据,而只是移动数据缓存区的头指针data和尾指针tail。数据由其余函数复制到数据缓存区中。
函数执行步骤以下:
1)当TCP发送数据时,会根据一些条件,如TCP最大分段长度MSS、是否支持聚合分散I/O等,分配一个SKB。
2)TCP需在数据缓存区的头部预留足够的空间,用来填充各层首部。MAX_TCP_HEADER是各层首部长度的总和,它考虑了最坏的状况:因为TCP层不知道将要用哪一个接口发送包,它为每一层预留了最大的首部长度,甚至还考虑了出现多个IP首部的可能性,由于在内核编译支持IP over IP的状况下,会遇到多个IP首部。
3)把TCP负载复制到数据缓存区。须要注意的是,图3-16 只是一个例子,TCP负载可能会被组织成其余形式,例如分片,在后续章节中将会看到一个分片的数据缓存区是什么样的。
![]() |
图3-16 TCP层向链路层传递时数据的填充过程 a) 空的SKB b) 在SKB的头部预留足够的空间 c) 复制TCP数据 d) 添加TCP首部 e) 添加IP首部 f) 添加以太网帧首部 |
4)TCP层添加TCP首部。
5)SKB传递到IP层,IP层为数据包添加IP首部。
6)SKB传递到链路层,链路层为数据包添加链路层首部。
3.skb_put()
skb_put()修改指向数据区末尾的指针tail,使之往下移len字节,即便数据区向下扩大len字节,并更新数据区长度len。调用skb_put()先后,SKB结构变化如图3-17所示。
![]() |
图3-17 skb_put()示意 a) 调用前 b) 调用后 |
4.skb_pull()
skb_pull()经过将data指针往下移动,在数据区首部忽略len字节长度的数据,一般用于接收到数据包后在各层间由下往上传递时,上层忽略下层的首部。调用skb_pull()先后,SKB结构变化如图3-18所示。
![]() |
图3-18 skb_pull()示意a) 调用前 b) 调用后 |