走進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;
}

重點關注最後四個字段,準確的說是四個指針,
skbuff
如上圖所示,這四個指針的外面兩個限定了一個大的緩衝區,而中間兩個則是包裹了有效數據,所謂有效數據便是報文。這麼設計的原因就是避免拷貝! 以發送方向爲例,我們知道發送過程是一層一層的在報文前方貼Header,因此,既然我們知道隨着報文向下傳遞時,長度會增加。那麼我們不如就在最初報文(應用層)的前面預留一定的空間,就可以避免向下傳遞時還要重新申請內存再拷貝了。

move
上圖描述了在接收方向報文上送時的指針移動方向,之前說的去掉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是報文的起點和終點,對發送方向和接收方向來說正好相反。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章