首页 > 代码库 > struct sk_buff结构体详解

struct sk_buff结构体详解

    struct sk_buff是linux网络系统中的核心结构体,linux网络中的所有数据包的封装以及解封装都是在这个结构体的基础上进行。

struct sk_buff_head 
{
    struct sk_buff *next;
    struct sk_buff *prev;
    
    __u32 qlen;
    spinlock_t lock;
}


struct sk_buff
{
    struct sk_buff *next;
    struct sk_buff *prev;
    struct sock *sock ;//struct sock是socket在网络层的表示,其中存放了网络层的信息
    
    unsigned int len;//下面有介绍
    unsigned int data_len; //下面有介绍
    __u16   mac_len ; //数路链路层的头长度
    __u16   hdr_len ; //writable header length of cloned skb
    unsigned int truesize ; //socket buffer(套接字缓存区的大小)
    atomic_t users ; //对当前的struct sk_buff结构体的引用次数;
    __u32   priority ; //这个struct sk_buff结构体的优先级
    
    sk_buff_data_t transport_header ; //传输层头部的偏移量
    sk_buff_data_t network_header ;   //网络层头部的偏移量
    sk_buff_data_t mac_header ;       //数据链路层头部的偏移量
    
    char *data ; //socket buffer中数据的起始位置;
    sk_buff_data_t tail ; //socket buffer中数据的结束位置;
    char *head ; //socket buffer缓存区的起始位置;
    sk_buffer_data_t end ; //socket buffer缓存区的终止位置;
    
    struct net_device *dev; //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收
                            //网络设备
    int iif;  //网络设备的接口索引号;
    
    
    struct timeval tstamp ; //用于存放接受的数据包的到达时间;
    
    __u8  local_df : 1 ,  //allow local fragmentaion;
          cloned   : 1 , // head may be cloned
          ;
    
    __u8  pkt_type : 3 , //数据包的类型;
          fclone   : 2,  // struct sk_buff clone status
    
}


   下图展示了一个很大的数据块,linux内核是如何利用不同的struct sk_buff来把它组织起来的

   而struct sk_buff中的len字段的含义为:

   len = l(1) + l(2) + l(3) + l(n+1);

   data_len =  l(2) + l(3) + l(n+1);

   所以线性数据的长度 l(1) = skb->len - skb->data_len;

    data_len 中存放的是非线性的数据,也就是整体上不是连续的数据;

    l(1)  表示的是线性的数据,是连续的。

wKiom1SBTrmjU_9mAAGxQnga-r8480.jpg

    下图解释了struct sk_buff中head, end, data, tail字段的含义:

wKioL1R-_8KTjAVtAAFAABRaZmU892.jpg


struct sk_buff结构体中的pkt_type字段的取值为:


wKiom1R_GsqQHNxAAAOTvV_MN8I617.jpg


   通过struct sk_buff中的pkt_type字段中的值,可以判断出接收到的数据包是不是发送给本机的数据包。

   如果pkt_type == PACKET_HOST,说明收到的数据包是发送给本机的单播数据包

   如果pkt_type == PACKET_BROADCAST,说明收到的数据包是发送给本机的广播数据包

   如果pkt_type == PACKET_MULTICAST,说明收到的数据包是发送给本机的组播数据包

   如果pkt_type == PACKET_OTHERHOST,说明收到的数据包不是发送给本机的数据包,需要转发出去。



struct sk_buff中的len字段的含义:

wKioL1R_HWyjx6cZAACQEnuD1qQ184.jpg

 


   1.分配一个struct sk_buff结构体(socket buffer套接字缓存区):

   static inline struct sk_buff *alloc_skb( unsigned int size,

                 gfp_t priority)

   size : 为将要分配的缓存区的大小;

   priority : 取值为GFP_ATOMIC, GFP_KERNEL等;

 

static inline struct sk_buff *alloc_skb(unsigned int size, gft_t priority)
{
    return __alloc_skb(size, priority, 0, -1);
}

    struct sk_buff *__alloc_skb(unsigned int size, gft_t priority,

                      int fclone, int node);

    EXPORT_SYMBOL(__alloc_skb);

    size : 为将要分配的缓存区的大小;

    priority : 同上;

    fclone   : 取1时,表示的是对当前的struct sk_buff进行克隆;

               取0时,表示不对当前struct sk_buff进行克隆;

   

struct sk_buff *__alloc_skb(unsigned int size, gft_t priority, int fclone, int node)
{
   struct sk_buff *skb;
   struct skb_shared_info *shinfo;
   u8 *data;
   
   //分配struct sk_buff结构体;
   skb = kmem_cache_alloc_node(cache, gft_mask & ~__GFP_DMA, node);
   
   //对size的大小进行边界对其处理;
   size = SKB_DATA_ALIGN(size);
   
   //分配size大小的缓存区以及struct skb_shared_info结构体;
   data = kmalloc_node_track_caller( size + sizeof(struct skb_shared_info), gft_mask, node);
   
   skb->truesize = size + sizeof(struct sk_buff);
   atomic_set( &skb->users, 1); //引用用户数设置为1
   
   skb->head = data;  // head 指向缓存区的起始位置
   skb->data = data;  // data 指向缓存区的起始位置
   skb_reset_tail_pointer(skb); // 等价于 skb->tail = data
   skb->end = skb->tail + size; // end 指向缓存区的终止位置
   
   //对struct skb_shared_info中的字段进行初始化处理;
   shinfo = skb_shinfo(skb); // #define skb_shinfo(skb) (struct skb_shared_info *)(skb->end)
   atomic_set(&shinfo->dataref, 1);
   shinfo->nr_frags = 0;
   shinfo->gso_size = 0;
   shinfo->gso_segs = 0;
   shinfo->gso_type = 0;
   shinfo->ipv6_frag_id = 0;
   shinfo->tx_flags.flags = 0;
   skb_frag_list_init(skb); // 等价于 skb_shinfo(skb)->frag_list = NULL
   
   return skb;
}


wKioL1R_MnKwmTiDAAGCKAMkv_0553.jpg


  

    2.释放一个struct sk_buff结构体:

      void kfree_skb(struct sk_buff *skb);

      EXPORT_SYMBOL(kfree_skb);

      当在使用kfree_skb()函数释放一个struct sk_buff结构体时,先会判断

skb->users的值是否为1。

     如果skb->users > 1,则只进行atomic_dec(&skb->user)操作,然后返回

     如果skb->users = 1,则才会将struct sk_buff结构体所占的内存还给系统。


      3.向sk_buff套接字缓存区的数据区的尾部加入len长的数据。

       unsigend char *skb_put(struct sk_buff *skb, u32 len);

       EXPORT_SYMBOL(skb_put);

wKioL1SAJe3RINouAALOf8UzJIU980.jpg


      skb_put()函数执行之前的sk_buff中的data,tail字段的位置:

wKioL1SAJm6SDDkbAACDtwaRY5M123.jpg

         skb_put(struct sk_buff *skb, unsigned int n)执行之后的效果:

wKioL1SAJsrQaTUxAACOIJxXqDM629.jpg

      可以通过使用skb_put()函数,来向sk_buff套接字缓存区的尾部去添加数据;


    4.向sk_buff套接字缓存区的数据区的头部加入len个字节的数据:

      unsigned char *skb_push(struct sk_buff *skb, u32 len);

      EXPORT_SYMBOL(skb_push);

      wKiom1SAJ9zxJ_SUAAOgBlZv8j8018.jpg


     skb_push()之前的sk_buff中的data,tail字段的位置:

wKiom1SAKE2h9AosAACNyHYoHq0296.jpg

      skb_push(struct sk_buff *skb, unsigned int len)之后的效果图:

wKiom1SAKMbTKcMWAACGtVImCHg523.jpg


     5.删除sk_buff缓存区中的数据区头部的len个字节的数据:

      unsigned char *skb_pull(struct sk_buff *skb, u32 len);

      EXPORT_SYMBOL(skb_pull);

wKiom1SAKnaiinwGAAFK_CGXVy0660.jpg


wKioL1SAKxfwx71aAAMo06t7jZo341.jpg


      skb_pull()执行之前的sk_buff中的data, tail指针位置:

wKiom1SAKuzhZmuTAACIyeC1O3U422.jpg

      skb_pull(struct sk_buff *skb, unsigned int len)执行之后的效果图:

wKiom1SAKzSR0nxwAACTH7MNyMs251.jpg


  


    skb_put(struct sk_buff *skb, u32 len)向套接字缓存区中添加数据的方式跟队列中添加数据的方式是一样样的。每次添加数据向尾部添加。

    skb_push(struct sk_buff *skb, u32 len)像向套接字缓存区中的数据区的头部添加数据,跟堆栈中添加数据的方式一样。

   skb_pull(struct sk_buff *skb, u32 len)从套接字缓存区的数据区中删除

len字节的数据。


   6.从套接字缓存区的数据区头部删除len个字节的数据,然后并对其中的csum字段值进行更新;

     unsigend char *skb_pull_rcsum(struct sk_buff *skb, u32 len);

     EXPORT_SYMBOL(skb_pull_rcsum);


wKiom1SAVq7hwIgDAAMdMxaOr7E773.jpg

 

wKioL1SAV1ODakdbAAPx1zVCN1E131.jpg

   



    7.修改套接字缓存区中的数据区的大小(通过调整tail的位置来改变数据区的大小):

     void skb_trim(struct sk_buff *skb, u32 len);

     EXPORT_SYMBOL(skb_trim);

     通过skb_trim()函数之后,struct sk_buff中的len字段的大小为:len;


wKiom1SAS0yCyBG6AAGKXqS-L4A781.jpg


wKioL1SATBSS0N11AAD6iuGXhO8893.jpg



    第二种:修改套接字缓存区中的数据区的大小(推荐使用):

   static inline int pskb_trim(struct sk_buff *skb, u32 len);

  

wKioL1SAXd2jYsHcAAHyx2wr68I277.jpg  

   wKioL1SAXkSSx-w1AAJcpLdy8co638.jpg


    pskb_trim()与skb_trim()的区别是:

   skb_trim()要求:struct sk_buff中的data_len 必须为0;

   pskb_trim()并没有次要求,在它的内部已经考虑了data_len = 0 和 data_len != 0,所以推荐使用pskb_trim()来调整套接字缓存区中数据的大小。



     8.将套接字缓存区中的数据区中的len个字节复制到一个buffer之中去:

      int skb_copy_bits(struct sk_buff *skb, int offset,

                         void *buffer, int len)

      EXPORT_SYMBOL(skb_copy_bits);

      返回值:成功返回0

              失败返回-EFAULT;

      wKioL1SAYg3TgDqWAAJbWacZPl8508.jpg



     9.将buffer中的len字节个数据复制到套接字缓存区中的数据区之中去:

      int skb_store_bits(struct sk_buff *skb, int offset,

                          const void *from, int len)

        EXPORT_SYMBOL(skb_store_bits);

       返回值:成功返回0;

               失败返回-EFAULT;

wKiom1SAZOyjP4XxAAOYvLevwBs266.jpg


 

     10.对套接字缓存去中的数据区中的len个字节的数据进行checksum ;

     u32 skb_checksum(struct sk_buff *skb, int offset, int len,

                       u32 csum);

     EXPORT_SYMBOL(skb_checksum);

    

wKiom1SAaH3Q5rwjAANPJtdvoJE843.jpg


    

    u32 skb_copy_and_chechsum_bits( struct sk_buff *skb, int offset,

                    void *buffer, int len, u32 csum );

   EXPORT_SYMBOL(skb_copy_and_checksum_bits);

   skb_copy_and_checksum_bits()将skb套接字缓存区中的数据区从offset偏移量开始的len个字节的数据复制到buffer之中,并对offset -- offset+len之间的数据进行checksum。


 

   11.对struct sk_buff结构体的克隆(即在分配一个新的struct sk_buff结构体,但是这个新的struct sk_buff结构体与之前的那个struct sk_buff结构体共用skb->data所指的数据区)

    struct sk_buff *skb_clone(struct sk_buff *skb, gft_t gfp_mask);

    EXPORT_SYMBOL(skb_clone);

wKiom1SBIVPChOT4AALbx1FKWzM960.jpg

struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)
{
    n->next = n->prev = NULL;
    n->sk = NULL;
    __copy_skb_header(n,skb); // 将skb中的所有相关的信息复制到n之中
    
    n->cloned = 1; //表示,这个n结构体是克隆结构体
    atomic_set(&n->users, 1);
    
    atomic_inc(&skb_shinfo(skb)->dataref); // 又有一个struct sk_buff结构体引用了这个数据区
    
    skb->cloned = 1;
    return n;
}

struct sk_buff *skb_clone(struct sk_buff *skb, gft_t gfp_mask)
{
    n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
    return __skb_clone(n, skb);
}

 

 

    12.创建一个struct sk_buff new的结构体和缓存区,让后将struct sk_buff old以及其缓存区中的内容全部复制到struct sk_buff new之后去。

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

      EXPORT_SYMBOL(skb_copy);

wKioL1SBKD-DAO13AAQ2lJWpF-k538.jpg

struct sk_buff *skb_copy(struct sk_buff *skb, gfp_t gfp_mask)
{
     struct sk_buff *n;
     
     //分配了一个struct sk_buff结构体和(skb->end - skb->head + skb->data_len)个字节的内存
     n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask) ;
     
     
     //skb->head开始,复制headerlen + skb->len 个字节的数据到 n->head处
     skb_copy_bits( skb, -headerlen, n->head, headerlen + skb->len );
     
     //将struct sk_buff *skb中的数据复制到struct sk_buff *n 之中去
     copy_skb_header(n, skb);
     
     return n;
}


   执行完skb_copy()后的结果图:

wKioL1SBUQ7AczQtAAFUv5EMkYI879.jpg



  13.struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)

      EXPORT_SYMBOL(pskb_buff);

    执行完pskb_copy()之后的结果图:

wKiom1SBURnBGTJdAAERfW6J5wU786.jpg



wKioL1SAbqyjbxxmAAKjnTWwgpw964.jpg


     由于linux内核中对struct sk_buff的组织:是以双链表的形式组织。所以,下面的函数是对双链表的操作:

  

    0.对一个struct sk_buff_head头结构体进行初始化:

  static inline void skb_queue_head_init(struct sk_buff_head *list)

  {

     spin_lock_init(&list->lock);


    //等价于:list->next = list->prev = NULL;

     __skb_queue_head_init(list);

  } 

    1.删除sk_buff双链表中的第一个元素:

      struct sk_buff *skb_dequeue(struct skb_buff_head *list)

      EXPORT_SYMBOL(skb_dequeue);

      list : 为struct sk_buff的链表头;

      须知:如果list链表为空,则返回NULL;


    2.删除sk_buff双链表中的最末尾的元素:

      struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)

      EXPORT_SYMBOL(skb_dequeue_tail);

      list : 为struct sk_buff的链表头;


    3.每次往sk_buff链表的头部后面添加一个struct sk_buff:

      void skb_queue_head(struct sk_buff_head *list,

                          struct sk_buff *new)

      EXPORT_SYMBOL(skb_queue_head);


    4.每次往sk_buff链表的尾部添加一个struct sk_buff:

      void skb_queue_tail(struct sk_buff_head *list,

                           struct sk_buff *new);

      EXPORT_SYMBOL(skb_queue_tail);


    5.从sk_buff链表中删除一个struct sk_buff:

      void skb_unlink(struct sk_buff *skb,

                       struct sk_buff_head *list)

      EXPORT_SYMBOL(skb_unlink);


    6.在list链表中的old后面添加一个new结构体:

      void skb_append(struct sk_buff *old, struct sk_buff *new,

                       struct sk_buff_head *list);

      EXPORT_SYMBOL(skb_append);


    7.在list链表中的old的前面添加一个new结构体:

      void skb_insert(struct sk_buff *old, struct sk_buff *new,

                       struct sk_buff_head *list)

      EXPORT_SYMBOL(skb_insert);


wKioL1SAdRmxKn_7AAHi2z5H7RM987.jpg

 

      因为struct sk_buff 的双链表组织结构,在内核中是属于临界资源,所以在每次访问这个双链表是,都要使用spin_lock_irqsave(&list->lock,flags)来获取锁,同时在操作完成以后通过spin_unlock_irqstore(&list->lock,flags)来释放锁。

    以上的提供的几个函数在其内部已经进行了对临界资源的并发访问的处理,所以可以任意使用。

 


    unsigned char *skb_transport_header(struct sk_buff *skb);

      用于获取skb中的TCP层协议头的位置;

wKioL1SBkezDBM5PAADSOhgW4Qo710.jpg

 

wKiom1SBkXGxNL9CAADZiVVt9h0070.jpg

     unsigned char *skb_network_header(struct sk_buff *skb);

       用于获取skb中的IP层协议头的位置;

    unsigned char *skb_mac_header(struct sk_buff *skb);

        用于获取skb中的链路层协议头的位置;


上面的关于skb_transport_header()的代码的实现,非常好的说明了在要获取响应的协议头的位置时,使用skb_***_header()这样的函数。因为在有的体系结构中char *transport_header类型,而在有的体系结构中unsigned int transport_header类型。而使用skb_***_header则不需要考虑它的类型。


    对struct sk_buff中的transport_header, network_header, mac_header等进行重新设置:

 static inline void skb_reset_transport_header(struct sk_buff *skb)

     {

          skb->transport_header = skb->data;

     }

    

static inline void skb_reset_network_header(struct sk_buff *skb)

     {

         skb->network_header = skb->data;

     }


 static inline void skb_reset_mac_header(struct sk_buff *skb)

     {

          skb->mac_header = skb->data;

     }


   

    对struct sk_buff中的transport_header, network_header, mac_header等进行offset偏移量的设置:

    static inline void skb_set_transport_header(struct sk_buff *skb, int offset)

    {

         skb->transport_header = skb->data + offset;

    }

   

    static inline void skb_set_network_header(struct sk_buff *skb, int offset)

    {

         skb->network_header = skb->data + offset;

    }


    static inline void skb_set_mac_header(struct sk_buff *skb, int offset)

    {

         skb->mac_header = skb->data + offset;

    }



    下面的这几个宏由于对内核中的struct sk_buff结构体组成的双链表进行各种遍历操作:

wKiom1SBmAvAxkUQAAQefpvNv8g866.jpg


    用于对一个struct sk_buff中的其他struct sk_buff结构体进行遍历:

wKioL1SBmR2TzOnAAAClCbeCoHE551.jpg



     如果想使用上面的函数,上面所有的函数在linux/skbuff.h中都有定义与声明,当然skbuff.h,skbuff.c,文件中还定义了其他一些函数,可以自己去看。

本文出自 “阿辉仔” 博客,请务必保留此出处http://weiguozhihui.blog.51cto.com/3060615/1586777

struct sk_buff结构体详解