SKB_BUFF整理笔记

来源:互联网 发布:广州数控980ta怎么编程 编辑:程序博客网 时间:2024/06/06 07:14

. SKB_BUFF的基本概念
1.
一个完整的skb buff组成
(1) struct sk_buff--
用于维护socket buffer状态和描述信息
(2) header data--
独立于sk_buff结构体的数据缓冲区,用来存放报文分组,使各层协议的header存储在连续的空间中,以方便协议栈对其操作
(3) struct skb_shared_info --
作为header data的补充,用于存储ip分片,其中sk_buff *frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区
skb buff
结构图如下:
 


struct skb_buff
表示接收或发送数据包的包头信息,其成员变量在从一层向另一层传递时会发生修改。例如L3L2传递前,会添加一个L3的头部,所以在添加头部前调用skb_reserve在缓冲区的头部给协议头预留一定的空间;L2L3传递时候,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核不会把L2的头部从缓冲区中删除,

sk_buff->h
sk_buff->nh
sk_buff->mac
指向TCP/IP各层协议头的指针:h指向L4(传输层)nh指向L3(网络层)mac指向L2(数据链路层)。每个指针的类型都是一个联合,包含多个数据结构,


sk_buff->head
sk_buff->data
sk_buff->tail
sk_buff->end
  
表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。headend指向缓冲区的头部和尾部,而data tail指向实际数据的头部和尾部。每一层会在headdata之间填充协议头,或者在tailend之间添加新的协议数据。数据部分会在尾部包含一个附加的头部。
下图是TCPL4)向下发送数据给链路层L2的过程。注意skb_buff->data在从L4L2穿越过程中的变化
 


几个len的区别?
(1)sk_buff->len:
表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。比如,处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。

(2)sk_buff->data_len: data_len只计算分片中数据的长度,即skb_shared_info中有效数据总长度(包括frag_list,frags[]中的扩展数据),一般为0

(3)sk_buff->truesize:这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。当skb->len变化时,这个变量也会变化。

通常,Data Buffer只是一个简单的线性 buffer,这时候 len就是线性 buffer中的数据长度;
但在有paged data情况下, Data Buffer 不仅包括第一个线性 buffer,还包括多个 page buffer;这种情况下,data_len指的是 page buffer 中数据的长度,len指的是线性 buffer 加上 page buffer的长度;len data_len就是线性 buffer的长度。

 

 . sk_buff结构操作函数
内核通过alloc_skb()dev_alloc_skb()为套接字缓存申请内存空间。这两个函数的定义位于net/core/skbuff.c文件内。通过这alloc_skb()申请的内存空间有两个,一个是存放实际报文数据的内存空间,通过kmalloc()函数申请;一个是sk_buff数据结构的内存空间,通过 kmem_cache_alloc()函数申请。dev_alloc_skb()的功能与alloc_skb()类似,它只被驱动程序的中断所调用,与alloc_skb()比较只是申请的内存空间长度多了16个字节。

内核通过kfree_skb()dev_kfree_skb()释放为套接字缓存申请的内存空间。dev_kfree_skb()被驱动程序使用,功能与kfree_skb()一样。当skb->users1kfree_skb()才会执行释放内存空间的动作,否则只会减少skb->users的值。skb->users1表示已没有其他用户使用该缓存了。

skb_reserve()函数为skb_buff缓存结构预留足够的空间来存放各层网络协议的头信息。该函数在在skb缓存申请成功后,加载报文数据前执行。在执行skb_reserve()函数前,skb->headskb->dataskb->tail指针的位置的一样的,都位于skb内存空间的开始位置。这部份空间叫做headroom。有效数据后的空间叫tailroomskb_reserve的操作只是把skb->dataskb->tail指针向后移,但缓存总长不变。

运行skb_reserve()sk_buff的结构
 

        sk_buff
 ----------------------   ---------->  skb->headskb->dataskb->tail
|                      
|
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
|                      |
 ---------------------    ---------->  skb->end
 
运行skb_reserve()sk_buff的结构

 

        sk_buff
 ----------------------   ---------->  skb->head
|                      |
|      headroom        |
|                      |
|--------------------- |  ---------->  skb->dataskb->tail
|         
            |
|                      |
|                      |
|                      |
|                      |
 ---------------------    ---------->  skb->end
       
skb_put()向后扩大数据区空间,tailroom空间减少,skb->data指针不变,skb->tail指针下移。

skb_push()向前扩大数据区空间,headroom空间减少,skb->tail指针不变,skb->data指针上移

skb_pull()缩小数据区空间,headroom空间增大,skb->data指针下移,skb->tail指针不变。

skb_shared_info结构位于skb->end后,用skb_shinfo函数申请内存空间。该结构主要用以描述data内存空间的信息。

 ---------------------  ----------->  skb->head
|                     |
|                     |
|      sk_buff        |
|                     |
|                     |
|                     |
|---------------------| ----------->  skb->end
|                     |
|   skb_share_info    |
|                     |
 ---------------------
skb_cloneskb_copy可拷贝一个sk_buff结构,skb_clone方式是clone,只生成新的sk_buff内存区,不会生成新的data内存区,新sk_buffskb->data指向旧data内存区。skb_copy方式是完全拷贝,生成新的sk_buff内存区和data内存区。。

 

. skb的分配细节
1.
关于 SKB的分配细节.

LINUX SKB的分配最终是由函数 : struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,int fclone)来完成.
SKB
可以分为 SKB描述符与 SKB数据区两个部分,其中描述符必须从 CACHE中来分配 :或者从skbuff_fclone_cache中分配,或者从 skbuff_head_cache中来分配
.
如果从分配描述符失败,则直接反悔 NULL,表示 SKB 分配失败.

SKB描述符分配成功后,即可分配数据区.
在具体分配数据区之前首先要对数据区的长度进行 ALIGN操作,通过宏 SKB_DATA_ALIGN来重新确定 size大小.然后戏台调用 kmalloc函数分配数据区
:
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
需要注意的是数据区的大小是 SIZE的大小加上 skb_shared_info结构的大小.

数据区分配成功后,便对 SKB 描述符进行与此数据区相关的赋值操作 :
   
memset(skb, 0, offsetof(struct sk_buff, truesize));
    skb->truesize = size + sizeof(struct sk_buff);
    atomic_set(&skb->users, 1);
    skb->head = data;
    skb->data = data;
    skb->tail = data;
    skb->end  = data + size;
需要主意的是, SKB truesize的大小并不包含 skb_shared_info结构的大小.另外,skb end成员指针也就是skb_shared_info结构的起始指针,系统用

一个宏 : skb_shinfo来完成寻找 skb_shared_info结构指针的操作.

最后,系统初始化 skb_shared_info 结构的成员变量 :
   
atomic_set(&(skb_shinfo(skb)->dataref), 1);
    skb_shinfo(skb)->nr_frags  = 0;
    skb_shinfo(skb)->tso_size = 0;
    skb_shinfo(skb)->tso_segs = 0;
    skb_shinfo(skb)->frag_list = NULL;
    skb_shinfo(skb)->ufo_size = 0;
    skb_shinfo(skb)->ip6_frag_id = 0;

最后,返回 SKB 的指针.

2. SKB的分配时机
SKB
的分配时机主要有两种,最常见的一种是在网卡的中断中,有数据包到达的时,系统分配 SKB 包进行包处理;第二种情况是主动分配 SKB包用于各种调试或者其他处理环境.

3. SKB reserve操作
SKB
在分配的过程中使用了一个小技巧 :即在数据区中预留了 128个字节大小的空间作为协议头使用,通过移动 SKB data tail指针的位置来实现这个功能.

4. SKB put操作
put
操作是 SKB中一个非常频繁也是非常重要的操作,但是, skb_put()函数其实什么也没做!
它只是根据数据的长度移动了 tail指针并改写了 skb->len的值,其他的什么都没做,然后就返回了 skb->data 指针(就是 tail指针在移动之前的位置).看上去此函数仿佛要拷贝数据到 skb的数据区中,其实这事儿是 insl这个函数干的, skb_put()函数毫不相关,不过它仍然很重要.

5.中断环境下 SKB的分配流程
当数据到达网卡后,会触发网卡的中断,从而进入 ISR ,系统会在 ISR中计算出此次接收到的数据的字节数 : pkt_len,然后调用 SKB分配函数来分配 SKB :
skb = dev_alloc_skb(pkt_len+5);
我们可以看到,实际上传入的数据区的长度还要比实际接收到的字节数多,这实际上是一种保护机制.实际上, dev_alloc_skb函数调用 __dev_alloc_skb函数, __dev_alloc_skb函数又调用 alloc_skb函数时,其数据区的大小又增加了 128字节, 128字节就事前面我们所说的 reserve机制预留的 header空间.

 

 

.不同情况下构造skb数据包的实现

http://blog.csdn.net/efan_linux/archive/2009/09/23/4580024.aspx

 在我这个网络接口的程序中(can0),其实难点就是怎样组包。怎样在原来数据包的基础加上自己的数据,怎样构造ip头,怎样构造udp头。

调试了两个星期,终于是调通了,在这个过程中,通过看内核源代码和自己组包的尝试,大概对组包的方法有了些了解,记录在此,留做备忘,也希望能给需要这方面信息的朋友一点帮助吧。

1,正常网卡收到数据包后的情况:

她的工作就是剥离mac头,然后给一些字段赋值,最后调用netif_rx将剥离mac头后的数据报(比如ip数据包)发送到上层协议。由协议栈处理。在此以ldd3中的snull为例,虽然snull跟硬件不相关,但这个过程都是类似的。

   struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);


   
skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) {
        if (printk_ratelimit())
            printk(KERN_NOTICE "snull rx: low on mem - packet dropped/n");
        priv->stats.rx_dropped++;
        goto out;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

   /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
   skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += pkt->datalen;
    netif_rx(skb);
 

注意:上面代码中红色放大的地方是重要的。

因为此刻收到的数据包的格式如下:macipudp/udpdata

这时候的处理就是剥离mac头,然后需要更新的一些域值。这些都是在函数eth_type_trans函数里做的。需要注意的是,skb->dev = dev;这条语句是很重要的,如果没有此语句,将会导致系统错误而死机(至少在我的板子上是这样的)。

注意:eth_type_trans()函数主要赋值的是:

skb->mac.rawskb->protocolskb->pkt_type。见下面的代码有无mac头的情况。

2,完全从一个字符串开始构造一个新的skb数据包。

以前只是看过如何修改数据包,自己构造数据包,这还是头一次,刚开始确实给我难住了,来来经过看内核代码和自己摸索,我自己写的代码如下:

/*假设:data是一个指向字符串的指针,data_lendata的长度*/

struct ipv6hdr *ipv6h;
struct udphdr *udph;
struct sk__buff * new_skb;

int length = data_len + sizeof(struct ipv6hdr) + sizeof(udphdr);
new_skb = dev_alloc_skb(length);
if(!new_skb)
   
{
        printk("low memory.../n"):
        return -1;
    }

skb_reserve(new_skb,length);

memcpy(skb_push(new_skb,data_len),data,data_len);

new_skb->h.uh = udph = (struct udphdr *)skb_push(new_skb,sizeof(struct udphdr));
memcpy(udph,&udph_tmp,sizeof(struct udphdr)); //
注意,此刻我的udph_tmp是在另一个过程中截获的数据包的udp头,如果完全是自己构造数据包,则需要自己填充udp数据头中的字段。


udph->len = .............. ; //
此处需要给udph->len赋值。注意udph->len__u16的。存储时是高低位互换的,所以你应该先将你要更新的数字编成16进制的数,然后高低位互换,在赋值给udh->len

udplen = new_skb->len;

new_skb->nh.ipv6h = ipv6h = (struct ipv6hdr *)skb_push(new_skb,sizeof(struct ipv6hdr));
memcpy(ipv6h,&ipv6h_tmp,sizeof(struct ipv6hdr)); //
udp头注释。

ipb6h->payload_len = .......... //此处同udph->len.需要注意的是,此处所指的长度并不包括ipv6头的长度,而是去掉ipv6头后的长度。

udph->check = 0;
udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph, udplen, 0));

///////////////注意,如果是ipv4,则还需要计算ip校验和,但此处是ipv6,不用计算ip检验和,所以此处没有ipv6头的校验。//////////////////////////

new_skb->mac.raw = new_skb->data; //因为无mac
new_skb->protocol = htons(ETH_P_IPV6); //
表明包是ipv6数据包
new_skb->pkt_type = PACKET_HOST; //
表明是发往本机的包

new_skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。

netif_rx(new_skb);
 

3,当需要改变原有skb的数据域的情况。

此时,有两种办法:
可以先判断skbtailroom,如果空间够大,则我们可以把需要添加的数据放在skbtailroom里。如果tailroom不够大,则需要调用skb_copy_expand函数来扩充tailroom或者headroom
 

例如我们需要在skb的后面加上一个16个字节的字符串,则代码类似如下:
if(skb_tailroom(skb) < 16)
{
   
nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb)+16,GFP_ATOMIC);
    if(!nskb)
    {
        printk("low memory..../n");      
        dev_kfree_skb(skb);
        return -1;
    }
  
    else
    {
        kfree_skb(skb); // 注意,如果此时是钩子函数钩出来的,则skb不能在这里释放,否则会造成死机。

       skb = nskb;
    }
  
    memcpy(skb_put(skb,16),ipbuf,16); //ipbuf为要加到skb后面的字符串

  
    udplen = skb->len - sizeof(struct ipv6hdr);
  
    udph->len += 0x1000; //换成十进制为 + 16

   ipv6h->payload_len += 0x1000;
  
    udph->check = 0;
    udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph,udplen,0));  
  
    skb->mac.raw = new_skb->data; //因为无mac

   skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包

   skb->pkt_type = PACKET_HOST; //表明是发往本机的包


   
skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。


 
netif_rx(skb);
}
 

注意:当调用skb_copy_expand或者修改了skb的数据域后,一定要更新udph->lenipv6h->payload_len。否则上层应用(比如udp套接字)收到的数据包还是原来的数据包而不是修改后的数据包,因为udph->len的原因。

 

 

12.4.4套接字缓冲区提供的函数

 

1.操纵sk_buff链表的函数

   sk_buff链表是一个双向链表,它包括一个链表头而且每一个缓冲区都有一个prevnext指针,指向链表中前一个和后一个缓冲区结点。

 

structsk_buff *skb_dequeue(struct skb_buff_head *list)

这个函数作用是把第一个缓冲区从链表中移走。返回取出的sk_buff,如果队列为空,就返回空指针。添加缓冲区用到skb_queue_headskb_queue_tail两个例程。

intskb_peek(struct sk_buff_head *list)

返回指向缓冲区链表第一个节点的指针。

intskb_queue_empty(struct sk_buff_head *list)

如果链表为空,返回  true 。

voidskb_queue_head(struct sk_buff *skb)

这个函数在链表头部添加一个缓冲区。

voidskb_queue_head_init(struct sk_buff_head *list)

初始化 sk_buff_head结构 。该函数必须在所有的链表操作之前调用,而且它不能被重复执行。

__u32 skb_queue_len(struct sk_buff_head *list)

返回队列中排队的缓冲区的数目。

voidskb_queue_tail(struct sk_buff *skb)

这个函数在链表的尾部添加一个缓冲区,这是在缓冲区操作函数中最常用的一个函数。

voidskb_unlink(struct sk_buff *skb)

这个函数从链表中移去一个缓冲区。它只是将缓冲区从链表中移去,但并不释放它。

 

许多更复杂的协议,如TCP协议,当它接收到数据时,需要保持链表中数据帧的顺序或对数据帧进行重新排序。有两个函数完成这些工作:

 void skb_append(struct sk_buff *entry, struct sk_buff *new_entry)

 void skb_insert(struct sk_buff *entry, struct sk_buff *new_entry)

它们可以使用户把一个缓冲区放在链表中任何一个位置。

 

2.创建或取消一个缓冲区结构的函数

    这些操作用到内存处理方法,它们的正确使用对管理内存非常重要。sk_buff结构的数量和它们占用内存大小会对机器产生很大的影响,因为网络缓冲区的内存组合是最主要一种的系统内存组合。

structsk_buff *alloc_skb(int size, int priority)

创建一个新的sk_buff结构并将它初始化。

voidkfree_skb(struct sk_buff *skb, int rw)

释放一个skb_buff

structsk_buff *skb_clone(struct sk_buff *old, int priority)

复制一个sk_buff,但不复制数据部分。

structsk_buff *skb_copy(struct sk_buff *skb)

完全复制一个sk_buff

 

3.对sk_buff 结构数据区进行操作的操作。

    这些函数用到了套接字结构体中两个域:缓冲区长度(skb->len) 和缓冲区中数据包的实际起始地址 (skb->data)。这些两个域对用户来说是可见的,而且它们具有只读属性。

unsignedchar *skb_headroom(struct sk_buff *skb)

返回sk_buff结构头部空闲空间的字节数大小

unsignedchar *skb_pull(struct sk_buff *skb, int len)

该函数将 data 指针向数据区的末尾移动,减少了len 字段的长度。该函数可用于从接收到的数据头上移去数据或协议头。

unsignedchar *skb_push(struct sk_buff *skb, int len)

该函数将 data 指针向数据区的前端移动,增加 了len 字段的长度。在发送数据的过程中,利用该函数可在数据的前端添加数据或协议头。

unsignedchar *skb_put(struct sk_buff *skb, int len)

该函数将tail 指针向数据区的末尾移动,增加了 len 字段的长度。在发送数据的过程中,利用该函数可在数据的末端添加数据或协议尾。

unsignedchar *skb_reserve(struct sk_buff *skb, int len)

该函数在缓冲区头部创建一块额外的空间,这块空间在skb_push 添加数据时使用。因为套接字建立时并没有为 skb_push 预留空间。它也可以用于在缓冲区的头部增加一块空白区域,从而调整缓冲区的大小,使缓冲区的长度统一。这个函数只对一个空的缓冲区才能使用。

unsignedchar *skb_tailroom(struct sk_buff *skb)

返回sk_buff尾部空闲空间的字节数大小

unsignedchar *skb_trim(struct sk_buff *skb, int len)

该函数和 put 函数的功能相反,它将tail  指针向数据区的前端移动,减小了 len 字段的长度。该函数可用于从接收到的数据尾上移去数据或协议尾。如果缓冲区的长度比“len”还长,那么它就通过移去缓冲区尾部若干字节,把缓冲区的大小缩减到“len”长度。

 

 

 

包缓冲区操作的几个内嵌函数
==========================

包缓冲由sk_buff结构描述,包缓冲数据区由其headend成员界定,
而包数据体则由包数据区内datatail界定的子区域来描述,
采用这种结构可以使添加或去除包头的操作变得非常方便.

skb_put(skb,len) 
在包体尾部扩展长度为len的数据块,返回扩展块的地址,
__skb_put()
为未校验版本
skb_push(skb,len) 在包体前部扩展长度为len的数据块,返回扩展块的地址,
__skb_push()
为未校验版本
skb_pull(skb,len) 去除包体前部长度为len的数据块,返回新包体的起始地址,
__skb_pull()
为未校验版本
skb_headroom(skb) 返回包体前部距离包区开始的长度
skb_tailroom(skb) 返回包体尾部距离包区结束的长度
skb_reserve(skb,len) 设置包体起始位置为包区开始的len字节
skb_trim(skb,len) 将包体截断为len字节, __skb_trim()为未校验版本

; include/linux/skbuff.h:

struct sk_buff {
    ...
 unsigned int    len;        /* Length of actual data            */
  unsigned char   *head;      /* Head of buffer               */
  unsigned char   *data;      /* Data head pointer                */
  unsigned char   *tail;      /* Tail pointer                 */
  unsigned char   *end;       /* End pointer                  */
  ...
}

/*
 *    Add data to an sk_buff
 */

static inline unsigned char *__skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp=skb->tail;
    skb->tail+=len;
  skb->len+=len;
   return tmp;
}

/**
 *   skb_put - add data to a buffer
 *    @skb: buffer to use
 *  @len: amount of data to add
 *
 * This function extends the used data area of the buffer. If this would
 * exceed the total buffer size the kernel will panic. A pointer to the
 *  first byte of the extra data is returned.
 */

static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
   unsigned char *tmp=skb->tail;
    skb->tail+=len;
  skb->len+=len;
   if(skb->tail>skb->end) {
       skb_over_panic(skb, len, current_text_addr());
  }
   return tmp;
}

static inline unsigned char *__skb_push(struct sk_buff *skb, unsigned int len)
{
 skb->data-=len;
  skb->len+=len;
   return skb->data;
}

/**
 *  skb_push - add data to the start of a buffer
 *  @skb: buffer to use
 *  @len: amount of data to add
 *
 * This function extends the used data area of the buffer at the buffer
 *  start. If this would exceed the total buffer headroom the kernel will
 * panic. A pointer to the first byte of the extra data is returned.
 */

static inline unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
   skb->data-=len;
  skb->len+=len;
   if(skb->datahead) {
skb_under_panic(skb, len, current_text_addr());
}
return skb->data;
}

static inline char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
skb->len-=len;
return skb->data+=len;
}

/**
* skb_pull - remove data from the start of a buffer
* @skb: buffer to use
* @len: amount of data to remove
*
* This function removes data from the start of a buffer, returning
* the memory to the headroom. A pointer to the next data in the buffer
* is returned. Once the data has been pulled future pushes will overwrite
* the old data.
*/

static inline unsigned char * skb_pull(struct sk_buff *skb, unsigned int len)
{
if (len > skb->len)
return NULL;
return __skb_pull(skb,len);
}

/**
* skb_headroom - bytes at buffer head
* @skb: buffer to check
*
* Return the number of bytes of free space at the head of an &sk_buff.
*/

static inline int skb_headroom(const struct sk_buff *skb)
{
return skb->data-skb->head;
}

/**
* skb_tailroom - bytes at buffer end
* @skb: buffer to check
*
* Return the number of bytes of free space at the tail of an sk_buff
*/

static inline int skb_tailroom(const struct sk_buff *skb)
{
return skb->end-skb->tail;
}

/**
* skb_reserve - adjust headroom
* @skb: buffer to alter
* @len: bytes to move
*
* Increase the headroom of an empty &sk_buff by reducing the tail
* room. This is only allowed for an empty buffer.
*/

static inline void skb_reserve(struct sk_buff *skb, unsigned int len)
{
skb->data+=len;
skb->tail+=len;
}


static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{
skb->len = len;
skb->tail = skb->data+len;
}

/**
* skb_trim - remove end from a buffer
* @skb: buffer to alter
* @len: new length
*
* Cut the length of a buffer down by removing data from the tail. If
* the buffer is already under the length specified it is not modified.
*/

static inline void skb_trim(struct sk_buff *skb, unsigned int len)
{
if (skb->len > len) {
__skb_trim(skb, len);
}
}




原创粉丝点击