6.   Sk_buffer 的秘密
 当调用 alloc_skb() 构造 SKB 和 data buffer时,需要的 buffer 大小是这样计算的:
 
 data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
 
 除了指定的 size 以外,还包括一个 struct skb_shared_info 结构的空间大小。也就是说,当调用 alloc_skb(size) 要求分配 size 大小的 buffer 的时候,同时还创建了一个 skb_shared_info 。
 
 这个结构定义如下:
 
 struct skb_shared_info {
  
             atomic_t            dataref;
             unsigned int       nr_frags;
             unsigned short   tso_size;
             unsigned short   tso_segs;
             struct sk_buff     *frag_list;
             skb_frag_t         frags[MAX_SKB_FRAGS];
 };
 
 我们只要把 end 从 char* 转换成skb_shared_info* ,就能访问到这个结构
 Linux 提供一个宏来做这种转换:
 
 #define skb_shinfo(SKB)             ((struct skb_shared_info *)((SKB)->end))
 
 那么,这个隐藏的结构用意何在?
 它至少有两个目的:
 1、 用于管理 paged data
 2、 用于管理分片
 
 接下来分别研究 sk_buffer 对paged data 和分片的处理。
 
 7.   对 paged data 的处理
 
 某些情况下,希望能将保存在文件中的数据,通过 socket 直接发送出去,这样,避免了把数据先从文件拷贝到缓冲区,从而提高了效率。
 
 Linux 采用一种 “paged data” 的技术,来提供这种支持。这种技术将文件中的数据直接被映射为多个 page。
 Linux 用 struct skb_frag_strut 来管理这种 page:
 
 typedef struct skb_frag_struct skb_frag_t;
 
 struct skb_frag_struct {
  
 
         struct page *page;
 
         __u16 page_offset;
 
         __u16 size;
 
 };
 
 并在shared info 中,用数组 frags[] 来管理这些结构。
 如此一来,sk_buffer 就不仅管理着一个 buffer 空间的数据了,它还可能通过 share info 结构管理一组保存在 page 中的数据。
 在采用 “paged data” 时,data_len 成员派上了用场,它表示有多少数据在 page 中。因此,
 如果 data_len 非0,这个 sk_buffer 管理的数据就是“非线性”的。
 Skb->len – skb->data_len 就是非 paged 数据的长度。
 
 在有 “paged data” 情况下, skb_put()就无法使用了,必须使用 pskb_put() 。。。
 
 8.   对分片的处理
 
 9.   SKB 的管理函数
 
 9.1.                    Data Buffer 的基本管理函数
 
 ·              unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
 
 “推”入数据
 在 buffer 的结束位置,增加数据,len是要增加的长度。
 这个函数有两个限制,需要调用者自己注意,否则后果由调用者负责
 1)、不能用于 “paged data” 的情况
 这要求调用者自己判断是否为 “paged data” 情况
 2)、增加新数据后,长度不能超过 buffer 的实际大小。
 这要求调用者自己计算能增加的数据大小
 
 ·              unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
 “压”入数据
 从 buffer 起始位置,增加数据,len 是要增加的长度。
 实际就是将新的数据“压”入到 head room 中
 
 ·              unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
 “拉”走数据
 从 buffer 起始位置,去除数据, len 是要去除的长度。
 如果 len 大于 skb->len,那么,什么也不做。
 在处理接收到的 packet 过程中,通常要通过 skb_pull() 将最外层的协议头去掉;例如当网络层处理完毕后,就需要将网络层的 header 去掉,进一步交给传输层处理。
 
 
 ·              void skb_trim(struct sk_buff *skb, unsigned int len)
 调整 buffer 的大小,len 是调整后的大小。
 如果 len 比 buffer 小,则不做调整。
 因此,实际是将 buffer 底部的数据去掉。
 对于没有 paged data 的情况,很好处理;
 但是有 paged data 情况下,则需要调用 __pskb_trim() 来进行处理。
 
 9.2.                    “Paged data” 和 分片的管理函数
 
 ·              char *pskb_pull(struct sk_buff *skb, unsigned int len)
 
 “拉“走数据
 如果 len 大于线性 buffer 中的数据长度,则调用__pskb_pull_tail() 进行处理。
 (Q:最后, return skb->data += len; 是否会导致 skb->data 超出了链头范围?)
 
 ·              int pskb_may_pull(struct sk_buff *skb, unsigned int len)
 在调用 skb_pull() 去掉外层协议头之前,通常先调用此函数判断一下是否有足够的数据用于“pull”。
 如果线性 buffer足够 pull,则返回1;
 如果需要 pull 的数据超过 skb->len,则返回0;
 最后,调用__pskb_pull_tail() 来检查 page buffer 有没有足够的数据用于 pull。
 
 
 ·              int pskb_trim(struct sk_buff *skb, unsigned int len)
 将 Data Buffer 的数据长度调整为 len
 在没有 page buffer 情况下,等同于 skb_trim();
 在有 page buffer 情况下,需要调用___pskb_trim() 进一步处理。
 
 ·              int skb_linearize(struct sk_buff *skb, gfp_t gfp)
 
 
 ·              struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
 ‘clone’ 一个新的 SKB。新的 SKB 和原有的 SKB 结构基本一样,区别在于:
 1)、它们共享同一个 Data Buffer
 2)、它们的 cloned 标志都设为1
 3)、新的 SKB 的 sk 设置为空
 (Q:在什么情况下用到克隆技术?)
 
 ·              struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
 
 ·              struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)
 
 ·              struct sk_buff *skb_pad(struct sk_buff *skb, int pad)
 
 ·              void skb_clone_fraglist(struct sk_buff *skb)
 
 ·              void skb_drop_fraglist(struct sk_buff *skb)
 
 ·              void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
 
 ·              pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, gfp_t gfp_mask)
 
 ·              int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
 
 ·              int skb_store_bits(const struct sk_buff *skb, int offset, void *from, int len)
 
 ·              struct sk_buff *skb_dequeue(struct sk_buff_head *list)
 
 ·              struct sk_buff *skb_dequeue(struct sk_buff_head *list)
 
 ·              void skb_queue_purge(struct sk_buff_head *list)
 
 ·              void skb_queue_purge(struct sk_buff_head *list)
 
 ·              void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
 
 ·              void skb_unlink(struct sk_buff *skb, struct sk_buff_head *list)
 
 ·              void skb_append(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
 
 ·              void skb_insert(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
 
 ·              int skb_add_data(struct sk_buff *skb, char __user *from, int copy)
 
 ·              struct sk_buff *skb_padto(struct sk_buff *skb, unsigned int len)
 
 ·              int skb_cow(struct sk_buff *skb, unsigned int headroom)
 
 这个函数要对 SKB 的 header room 调整,调整后的 header room 大小是 headroom.
 如果 headroom 长度超过当前header room 的大小,或者 SKB 被 clone 过,那么需要调整,方法是:
 分配一块新的 data buffer 空间,SKB 使用新的 data buffer 空间,而原有空间的引用计数减1。在没有其它使用者的情况下,原有空间被释放。
 
 
 
 ·              struct sk_buff *dev_alloc_skb(unsigned int length)
 
 ·              void skb_orphan(struct sk_buff *skb)
 
 ·              void skb_reserve(struct sk_buff *skb, unsigned int len)
 
 ·              int skb_tailroom(const struct sk_buff *skb)
 
 ·              int skb_headroom(const struct sk_buff *skb)
 
 ·              int skb_pagelen(const struct sk_buff *skb)
 
 ·              int skb_headlen(const struct sk_buff *skb)
 
 ·              int skb_is_nonlinear(const struct sk_buff *skb)
 
 ·              struct sk_buff *skb_share_check(struct sk_buff *skb, gfp_t pri)
 
 如果skb 只有一个引用者,直接返回 skb否则 clone 一个 SKB,将原来的 skb->users 减1,返回新的 SKB需要特别留意 pskb_pull() 和 pskb_may_pull() 是如何被使用的:
 
 1)、在接收数据的时候,大量使用 pskb_may_pull(),其主要目的是判断 SKB 中有没有足够的数据,例如在 ip_rcv() 中:
 
 if (!pskb_may_pull(skb, sizeof(struct iphdr)))
                         goto inhdr_error;
 
 iph = skb->nh.iph;
 
 它的目的是拿到 IP header,但取之前,先通过 pskb_may_pull() 判断一下有没有足够一个 IP header 的数据。
 2)、当我们构造 IP 分组的时候,对于数据部分,通过 put向下扩展空间(如果一个sk_buffer 不够用怎么分片?);对于 传输层、网络层、链路层的头,通过 push 向上扩展空间;
 3)、当我们解析 IP 分组的时候,通过 pull(),从头开始,向下压缩空间。
 
 因此,put 和 push 主要用在发送数据包的时候;而pull 主要用在接收数据包的时候。
 
 10.                     各种 header
 
 union {
  
                         struct tcphdr      *th;
                         struct udphdr     *uh;
                         struct icmphdr   *icmph;
                         struct igmphdr   *igmph;
                         struct iphdr        *ipiph;
                         struct ipv6hdr     *ipv6h;
                         unsigned char    *raw;
             } h;
 
             union {
  
                         struct iphdr        *iph;
                         struct ipv6hdr     *ipv6h;
                         struct arphdr      *arph;
                         unsigned char    *raw;
             } nh;
 
             union {
  
                         unsigned char    *raw;
             } mac;
  详细解决方案
                sk_buffer 详细分析 (下)
热度:18   发布时间:2024-01-09 09:56:40.0