走进Linux内核网络 报文是什么—sk_buff

前言

今天来聊下sk_buff。如果把内核网络协议栈比作一个人,那么sk_buff就是流淌在他体内血管里红血球,它运输养分(数据)走遍全身(协议栈每一层)。一个sk_buff就是一个报文。

报文的表示方式

在协议栈层次模型中,我们提到过,报文在协议栈各层之间穿梭,发送方向不断加上Header,接收方向不断去掉Header。自然而然,我们要设计数据结构去表示在各个层上形态各异的报文

我们可以这么设计IP报文:

struct ip_pdu{
   struct ip_hdr ip_hdr;
   char* payload;
}

TCP报文呢, 就像下面这样

struct tcp_pdu{
   struct tcp_hdr tcp_hdr;
   char* payload;
}

然后层与层之间进行报文数据结构的拷贝和转换。

等等!网络协议那么多,难道要每种协议设计一个新的数据结构?!另外,像这样每个报文都要拷贝多次的话效率也太低了吧?!
所以,linux采用的报文层间传递的方式就是一个结构,传递指针,这个结构就是sk_buff, 也就是说, 无论是哪个层次的报文, 在内核中始终都以sk_buff表示(sk_buffsocket buffer的简称)

下面是sk_buff结构的构成(精简后)

<skbuff.h>
typedef unsigned char *sk_buffer_data_t;
struct sk_buff{
     struct sock *sk;
     struct net_device *dev;
     char cb[40];    

     sk_buff_data_t transport_header;
     sk_buff_data_t network_header;
     sk_buff_data_t mac_header;

     sk_buff_data_t tail;
     sk_buff_data_t end;
     unsigned char *head;
                   *data;
}

重点关注最后四个字段,准确的说是四个指针,
走进Linux内核网络 报文是什么—sk_buff_第1张图片
如上图所示,这四个指针的外面两个限定了一个大的缓冲区,而中间两个则是包裹了有效数据,所谓有效数据便是报文。这么设计的原因就是避免拷贝! 以发送方向为例,我们知道发送过程是一层一层的在报文前方贴Header,因此,既然我们知道随着报文向下传递时,长度会增加。那么我们不如就在最初报文(应用层)的前面预留一定的空间,就可以避免向下传递时还要重新申请内存再拷贝了。

走进Linux内核网络 报文是什么—sk_buff_第2张图片
上图描述了在接收方向报文上送时的指针移动方向,之前说的去掉Header就是指这个。

控制字段cb是一个很有意思的字段,它是一个杂货间,没有标准的格式,因此可以存放各种数据,最多40个字节。当报文在协议栈中流动时,流到哪一层,哪一层就将自己的私有数据放到这个区域。比如TCP用这个区域储存收到的TCP报文头中的一些字段。

struct tcp_skb_cb{
......
u32 seq;     /* Starting sequence number  */
u32 end_seq; /* SEQ + FIN + SYN + dalalen */
u32 when;    /* used to compute rtt       */
u32 flags    /* TCP header flags          */
}

struct sock *skstruct net_device *dev则分别代表了sk_buff的起点和终点。对于发送方向,报文由应用层产生,自然是通过一个套接字到内核,最终从一个设备发送出去,这时sk是起点,dev是终点。而对于接收方向,报文由一个设备接收,最终被应用层通过一个套接字拿到,这时,这时dev是起点,sk是终点.

总结

  1. 协议栈使用sk_buff表示所有报文,sk_buff在协议栈中传递时以指针传递并且不会拷贝
  2. cb是一个杂货间,每一层都可以使用
  3. skdev是报文的起点和终点,对发送方向和接收方向来说正好相反。

你可能感兴趣的:(走进Linux内核网络 报文是什么—sk_buff)