天天看點

Linux網絡協定棧(二)——套接字緩存(socket buffer)

Linux網絡核心資料結構是套接字緩存(socket buffer),簡稱skb。它代表一個要發送或處理的封包,并貫穿于整個協定棧。

1、    套接字緩存

skb由兩部分組成:

(1)    封包資料:它儲存了實際在網絡中傳輸的資料;

(2)    管理資料:供核心處理封包的額外資料,這些資料構成了協定之間交換的控制資訊。

當應用程式向一個socket傳輸資料之後,該socket将建立相應的套接字緩存,并将使用者資料拷貝到緩存中。當封包在各協定層傳達輸的過程中,每一導的封包頭将插入到使用者資料之前。skb為封包頭申請了足夠的空間,是以避免了由于插入封包頭而對封包進行多次拷貝。使用者資料隻拷貝了兩次:一是從使用者空間拷貝到核心;二是封包資料從核心傳送到網絡擴充卡。

1.1、sk_buff

套接字緩存結構:

Linux網絡協定棧(二)——套接字緩存(socket buffer)
Linux網絡協定棧(二)——套接字緩存(socket buffer)

Code

//套接字緩存

struct sk_buff {

    /* These two members must be first. */

    struct sk_buff        *next;  

    struct sk_buff        *prev;

    struct sk_buff_head    *list;  

    struct sock        *sk;          //指向建立封包的socket

    struct timeval        stamp;  //此封包收到時的時間

    struct net_device    *dev;          //收到此封包的網絡裝置 

    struct net_device    *input_dev;

    struct net_device    *real_dev;

    //TCP報頭

    union {  

        struct tcphdr    *th;   //tcp頭

        struct udphdr    *uh;  //udp頭

        struct icmphdr    *icmph;

        struct igmphdr    *igmph;

        struct iphdr    *ipiph;

        struct ipv6hdr    *ipv6h;

        unsigned char    *raw;

    } h;

    //IP報頭

    union {

        struct iphdr    *iph;

        struct arphdr    *arph;

    } nh;

    //鍊路層幀頭

          unsigned char     *raw;

    } mac;

    struct  dst_entry    *dst;  //此封包的路由,路由确定後賦此值

    struct    sec_path    *sp;

    /*

     * This is the control buffer. It is free to use for every

     * layer. Please put your private variables there. If you

     * want to keep them across layers you have to do a skb_clone()

     * first. This is owned by whoever has the skb queued ATM.

     */

    char            cb[40];

    //此封包的長度,這是指網絡封包在不同協定層中的長度,包括頭部和資料。在協定棧的不同層,這個長度是不同的。 

    unsigned int        len,

                data_len,

                mac_len,

                csum;

    unsigned char        local_df,

                cloned,

                pkt_type, //網絡封包的類型,常見的有PACKET_HOST,代表發給本機的封包;還有PACKET_OUTGOING,代表本機發出的封包。 

                ip_summed;

    __u32            priority;

    unsigned short        protocol,//鍊路層協定

                security;

    void            (*destructor)(struct sk_buff *skb);

#ifdef CONFIG_NETFILTER

        unsigned long        nfmark;

    __u32            nfcache;

    __u32            nfctinfo;

    struct nf_conntrack    *nfct;

#ifdef CONFIG_NETFILTER_DEBUG

        unsigned int        nf_debug;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

    struct nf_bridge_info    *nf_bridge;

#endif /* CONFIG_NETFILTER */

#if defined(CONFIG_HIPPI)

        __u32        ifield;

    } private;

#ifdef CONFIG_NET_SCHED

       __u32            tc_index;        /* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

    __u32           tc_verd;               /* traffic control verdict */

    __u32           tc_classid;            /* traffic control classid */

    /* These elements must be at the end, see alloc_skb() for details.  */

       //此封包存儲區的長度,這個長度是16位元組對齊的,一般要比封包的長度大

    unsigned int        truesize;

    atomic_t        users;

    /*head和end指向封包資料的整個單元.head與data之間的空間稱為headroom,tail與end之間的空間稱為tailroom.

    */

    unsigned char        *head,

                *data,

                *tail,

                *end;

};

1.2、與sk_buff相關的函數

與sk_buff相關的函數涉及到網絡封包存儲結構和控制結構的配置設定、複制、釋放,以及控制結構裡的各指針的操作,還有各種标志的檢查。重要的函數說明如下:

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)

配置設定大小為size的存儲空間存放網絡封包,同時配置設定它的控制結構。size的值是16位元組對齊的,gfp_mask是記憶體配置設定的優先級。常見的記憶體配置設定優先級有GFP_ATOMIC,代表配置設定過程不能被中斷,一般用于中斷上下文中配置設定記憶體;GFP_KERNEL,代表配置設定過程可以被中斷,相應的配置設定請求被放到等待隊列中。配置設定成功之後,因為還沒有存放具體的網絡封包,是以sk_buff的 data,tail指針都指向存儲空間的起始位址,len的大小為0,而且 is_clone和cloned兩個标記的值都是0。

struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)

從控制結構skb中 clone出一個新的控制結構,它們都指向同一個網絡封包。clone成功之後,将新的控制結構和原來的控制結構的 is_clone,cloned兩個标記都置位。同時還增加網絡封包的引用計數(這個引用計數存放在存儲空間的結束位址的記憶體中,由函數atomic_t *skb_datarefp(struct sk_buff *skb)通路,引用計數記錄了這個存儲空間有多少個控制結構)。由于存在多個控制結構指向同一個存儲空間的情況,是以在修改存儲空間裡面的内容時,先要确定這個存儲空間的引用計數為1,或者用下面的拷貝函數複制一個新的存儲空間,然後才可以修改它裡面的内容。

struct sk_buff *skb_copy(struct sk_buff *skb, int gfp_mask)

複制控制結構skb和它所指的存儲空間的内容。複制成功之後,新的控制結構和存儲空間與原來的控制結構和存儲空間相對獨立。是以新的控制結構裡的is_clone,cloned兩個标記都是0,而且新的存儲空間的引用計數是1。

void kfree_skb(struct sk_buff *skb)

釋放控制結構skb和它所指的存儲空間。由于一個存儲空間可以有多個控制結構,是以隻有在存儲空間的引用計數為1的情況下才釋放存儲空間,一般情況下,隻釋放控制結構skb。

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)

将tail指針下移,并增加skb的 len值。data和 tail之間的空間就是可以存放網絡封包的空間。這個操作增加了可以存儲網絡封包的空間,但是增加不能使tail的值大于end的值,skb的 len值大于truesize的值。

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

将data指針上移,并增加skb的 len值。這個操作在存儲空間的頭部增加了一段可以存儲網絡封包的空間,上一個操作在存儲空間的尾部增加了一段可以存儲網絡封包的空間。但是增加不能使data的值小于head的值,skb的 len值大于truesize的值。

unsigned char * skb_pull(struct sk_buff *skb, unsigned int len)

将data指針下移,并減小skb的 len值。這個操作使data指針指向下一層網絡封包的頭部。

void skb_reserve(struct sk_buff *skb, unsigned int len)

将data指針和tail指針同時下移。這個操作在存儲空間的頭部預留 len長度的空隙。

void skb_trim(struct sk_buff *skb, unsigned int len)

将網絡封包的長度縮減到 len。這個操作丢棄了網絡封包尾部的填充值。

int skb_cloned(struct sk_buff *skb)

判斷skb是否是一個 clone的控制結構。如果是clone的,它的cloned标記是1,而且它指向的存儲空間的引用計數大于1。

2、    套接字緩存隊列(Socket-Buffer Queues)

2.1、sk_buff_head

在網絡協定棧的實作中,有時需要把許多網絡封包放到一個隊列中做異步處

理。LINUX 為此定義了相關的資料結構 sk_buff_head。這是一個雙向連結清單的

頭,它把sk_buff連結成一個雙向連結清單。

//套接字緩存隊列頭

struct sk_buff_head {

    struct sk_buff    *next;

    struct sk_buff    *prev;

    __u32        qlen; //隊列的長度,即隊列中封包的數量

    spinlock_t    lock;

<a target="_blank">複制代碼</a>

2.2、與 sk_buff_head相關的函數

void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk)

将newsk加到連結清單 list的頭部。

void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)

将newsk加到連結清單 list的尾部。

struct sk_buff *skb_dequeue(struct sk_buff_head *list)

從連結清單 list的頭部取下一個 sk_buff。

struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)

從連結清單 list的尾部取下一個 sk_buff。

skb_insert(struct sk_buff *old, struct sk_buff *newsk)

将newsk加到old所在的連結清單上,并且 newsk在old的前面。

void skb_append(struct sk_buff *old, struct sk_buff *newsk)

将newsk加到old所在的連結清單上,并且 newsk在old的後面。

void skb_unlink(struct sk_buff *skb)

将skb從它所在的連結清單上取下。

以上的連結清單操作都是先關中斷的。這在中斷上下文中是不需要的,是以另外有一套與上面函數同名但是有字首“__”的函數供運作在中斷上下文中的函數調用。

繼續閱讀