skb是linux kernel中收发数据包用到的控制结构体,有些字段指向分配的内存用于存放数据包,
向协议栈传送时,通过移动指针来获取到以太头,网络头,传输头等信息。
struct sk_buff {
__u16 transport_header; //传输头相对于skb->head的偏移
__u16 network_header;//网络头相对于skb->head的偏移
__u16 mac_header;//以太网头相对于skb->head的偏移
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head, *data;
}
head和end分别指向存放数据内存区域的头和尾,一旦分配就固定不变。
data和tail分别是真正数据的起始位结束。
head和data之间的区域成为headroom,data和tail之间的区域存放真正的数据,tail和end之间的区域成为tailroom。skb刚分配时,head,data和tail在同一位置,end在末尾,所以刚开始时,headroom大小为0,tailroom大小为size,后续对数据包的操作,通过移动data和tail完成,head和end固定不变。
head|data|tail ----size-----end
len 和 data_len
len代表整个数据区域的长度!
这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据
(skb_shared_info) 组成!后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这
里仅仅是控制信息,数据是通过后面的data指针指向其他内存块的!那个内存块中是线性数据和
非线性数据!那么len就是length(线性数据) + length(非线性数据)!!!
data_len: 指的是length(非线性数据)!!!那么可以知道:length(线性数据) = skb->len - skb->data_len
b. 分配skb函数
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int flags, int node)
{
cache = (flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
//从 cache里取出一个skb结构体。为了提高分配skb效率,会在初始化时,分配一个skb
//放在cache中。
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
//分配数据区域和skb_shared_info,它俩是在一块连续内存中
size = SKB_DATA_ALIGN(size);
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
/* kmalloc(size) might give us more room than requested.
* Put skb_shared_info exactly at the end of allocated zone,
* to allow max possible filling before reallocation.
*/
分配完内存后,将size减去skb_shared_info 的大小,此时size只表示存放数据的大小
size = SKB_WITH_OVERHEAD(ksize(data));
#define SKB_WITH_OVERHEAD(X) \
((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
//将skb tail前面的成员全部清零。tail后面的不用清零,因为随后就会赋值
memset(skb, 0, offsetof(struct sk_buff, tail));
/* Account for allocated memory : skb + skb->head */
skb->truesize = SKB_TRUESIZE(size);
skb->pfmemalloc = pfmemalloc;
//引用计数设置为1
atomic_set(&skb->users, 1);
//在后续报文处理过程中,head和end分别表示数据内存的起始和结尾,是固定不变的
//通过偏移data和tail来指向不同的数据位置
//初始化时head和data指针都指向data
skb->head = data;
skb->data = data;
//在64位下,tail和end都是整数,表示相对于head的偏移
//初始时tailf为0,即指向head
//end为tail+size,即指向内存中存放数据的末尾,skb_shared_info的起始
skb_reset_tail_pointer(skb);
skb->tail = skb->data - skb->head
skb->end = skb->tail + size;
skb->mac_header = (typeof(skb->mac_header))~0U;
skb->transport_header = (typeof(skb->transport_header))~0U;
/* make sure we initialize shinfo sequentially */
//返回 skb->head + skb->end,即为skb_shared_info的首地址
shinfo = skb_shinfo(skb);
#define skb_shinfo(SKB) ((struct skb_shared_info *)
(skb_end_pointer(SKB)))skb->head + skb->end
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
//设置shinfo引用计数为1
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
}
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
skb->data -= len;
skb->len += len;
在skb头部删除 len 字节
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
skb->len -= len;
skb->data += len;
在skb尾部添加 len 字节
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
skb->tail += len;
skb->len += len;
预留headroom
static inline void skb_reserve(struct sk_buff *skb, int len)
skb->data += len;
skb->tail += len;
eth_type_trans
//reset mac头位置,mac_header 是相对于skb->skb的偏移
skb_reset_mac_header(skb);
skb->mac_header = skb->data - skb->head;
//将data指针向后偏移14字节,指向下一个协议头,即三层头或者vlan头
//skb->len表示数据包的总长度,偏移14字节后,len也要减去14
#define ETH_HLEN 14 /* Total octets in header. */
skb_pull_inline(skb, ETH_HLEN);
skb->len -= len;
skb->data += len;
//通过 skb->mac_header 仍然可以获取mac头
eth = eth_hdr(skb);
(struct ethhdr *)skb_mac_header(skb);
skb->head + skb->mac_header;
//根据数据包的目的mac决定此数据包的pkt_type
//如果目的mac中从左往右第二个字节为1为组播,并且如果mac为 全1,则为广播,否则为组播
//如果目的mac中从左往右第二个字节不为1为单播,并且和接收设备的mac不同则设置为PACKET_OTHERHOST
//如果和接收设备的mac相同,则不用设置,默认为0,即 PACKET_HOST,表示 to us的数据包
if (unlikely(is_multicast_ether_addr(eth->h_dest))) {
if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast))
skb->pkt_type = PACKET_BROADCAST;
else
skb->pkt_type = PACKET_MULTICAST;
}
else if (unlikely(!ether_addr_equal_64bits(eth->h_dest,dev->dev_addr)))
skb->pkt_type = PACKET_OTHERHOST;
//如果以太网协议大于ETH_P_802_3_MIN,则返回以太网协议即可
if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN))
return eth->h_proto;
所以eth_type_trans执行后,
skb->mac_header = skb->data - skb->head;
skb->data 指向网络头
__netif_receive_skb_core
//设置网络头偏移量
skb_reset_network_header(skb);
skb->network_header = skb->data - skb->head;
//重置mac头长度
skb_reset_mac_len(skb);
skb->mac_len = skb->network_header - skb->mac_header;
执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->data 仍然指向网络头
3.3. ip_rcv 数据包进入网络层的处理
ip_rcv
当数据包进入协议栈往上层递交的过程中,比如在IP层,它需要对数据包的IP头部进行分析,比如头部合法性等,这时候就需要确保IP头部在线性缓冲区中,这样才能对它进行分析,如果在非线性缓冲区中,而非线性缓冲区是unmapped的page,因此就需要从这些unmapped page当中把数据复制到线性缓冲区中。
这个艰难的工作就是__pskb_pull_tail完成的。我将对它的代码进行单独的分析。
当然最好情况是skb->data指向的线性缓冲区中的数据至少是大于len的,这样就可以直接返回了成功了。
len一定是不能大于整个skb的数据总长的。这个就不必说明吧...
线性缓冲区中数据不足,不幸还是发生了...调用__pskb_pull_tail。
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
/* skb_headlen定义为skb->len - skb->data_len。即skb->head指向
的线性缓冲区里当前
* 有效数据的长度。*/
if (likely(len <= skb_headlen(skb)))
return 1;
if (unlikely(len > skb->len))
return 0;
return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
iph = ip_hdr(skb);
(struct iphdr *)skb_network_header(skb);
skb->head + skb->network_header;
skb->transport_header = skb->network_header + iph->ihl*4;
执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 仍然指向网络头
3.4. 经过查找路由,发现是本地的数据包
ip_local_deliver_finish
//获取网络头长度
static inline u32 skb_network_header_len(const struct sk_buff *skb)
return skb->transport_header - skb->network_header;
//偏移data到传输头
__skb_pull(skb, skb_network_header_len(skb));
skb->len -= len;
skb->data += len;
int protocol = ip_hdr(skb)->protocol;
ipprot = rcu_dereference(inet_protos[protocol]);
ipprot->handler(skb);
执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向传输层
3.5. 数据到达传输层,以icmp为例
icmp_rcv
pskb_pull(skb, sizeof(*icmph))
skb->len -= len;
skb->data += len;
icmph = icmp_hdr(skb);
(struct icmphdr *)skb_transport_header(skb);
skb->head + skb->transport_header;
执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向应用层
也可参考:skb结构和相关操作函数 - 简书 (jianshu.com)