• 关于skb_header_pointer函数


    摘自:https://blog.csdn.net/adamska0104/article/details/41245579
     
       最近一段时间看内核代码,总是看到skb_header_pointer函数,这个函数的主要功能很简单,就是从skb字段中获取指定长度到内容到缓存中。函数原型是这个样子的:
    static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
               int len, void *buffer)
       也就是从skb中skb->data开始的offset偏移处,获取len长度的内容到buff中。
      
       看起来这个并没有什么问题,不过无意中在网上搜了一下这个函数,却发现还真有一些别的意义在里面。。好吧,看一下代码,依然是2.6.30版本内核。
    static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
               int len, void *buffer)
        {
           int hlen = skb_headlen(skb);
     
           if (hlen - offset >= len)
              return skb->data + offset;
     
           if (skb_copy_bits(skb, offset, buffer, len) < 0)
              return NULL;
     
           return buffer;
        }
    参数为:
       skb:数据包struct sk_buff的指针
       offset:相对数据起始头(如IP头)的偏移量
       len:数据长度
       buffer:缓冲区,大小不小于len
     
       其中skb_headlen()函数的定义为:
    static inline unsigned int skb_headlen(const struct sk_buff *skb)
       {
           return skb->len - skb->data_len;
       }
       其中skb->len是数据包长度,在IPv4中就是单个完整IP包的总长,但这些数据并不一定都在当前内存页;skb->data_len表示在其他页的数据长度(包括本skb在其他页中的数据以及分片skb中的数据),因此skb->len - skb->data_len表示在当前页的数据大小。
     
       如果skb->data_len不为0,表示该IP包的数据分属不同的页,该数据包也就被成为非线性化的,函数skb_is_nonlinear()就是通过该参数判断,一般刚进行完碎片重组的skb包就属于此类。
       这样skb_header_pointer()函数就好理解了,先判断要处理的数据是否都在当前页面内,如果是,则返回可以直接对数据处理,返回所求数据指针,否则用skb_copy_bits()函数进行拷贝,下面再来看一下这个函数的实现过程,并不复杂。
     
       /* Copy some data bits from skb to kernel buffer. */
    int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
       {
          int i, copy;
          int start = skb_headlen(skb);
     
          if (offset > (int)skb->len - len)
             goto fault;
     
          /* Copy header. */
          /*拷贝在本页中的部分*/
          if ((copy = start - offset) > 0) {
             if (copy > len)
                 copy = len;
             skb_copy_from_linear_data_offset(skb, offset, to, copy);
          if ((len -= copy) == 0)
             return 0;
          offset += copy;
          to     += copy;
         }
     
          /*拷贝本skb中其他碎片的部分*/
          for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
             int end;
     
             WARN_ON(start > offset + len);
     
             end = start + skb_shinfo(skb)->frags[i].size;
             if ((copy = end - offset) > 0) {
                  u8 *vaddr;
     
                  if (copy > len)
                     copy = len;
     
                  vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
                  memcpy(to,
                        vaddr + skb_shinfo(skb)->frags[i].page_offset+
                        offset - start, copy);
                  kunmap_skb_frag(vaddr);
     
                  if ((len -= copy) == 0)
                      return 0;
                  offset += copy;
                  to     += copy;
             }
             start = end;
         }
     
         /*拷贝其他碎片skb中的数据部分,对于skb的递归调用*/
         if (skb_shinfo(skb)->frag_list) {
             struct sk_buff *list = skb_shinfo(skb)->frag_list;
     
             for (; list; list = list->next) {
                int end;
     
                WARN_ON(start > offset + len);
     
                end = start + list->len;
                if ((copy = end - offset) > 0) {
                    if (copy > len)
                        copy = len;
                    if (skb_copy_bits(list, offset - start,
                                   to, copy))
                        goto fault;
                    if ((len -= copy) == 0)
                        return 0;
                    offset += copy;
                    to     += copy;
                }
                start = end;
            }
       }
       if (!len)
           return 0;
     
    fault:
       return -EFAULT;
    }
     
       其中的skb_copy_from_linear_data_offset函数,就是一个线性拷贝的过程,内部是对memcpy的一个封装。
       kmap_skb_frap没有看明白,不过其意思应该是把分片数据所在的page地址映射到了一个内核可访问的虚拟地址上,通过这个虚拟地址完成数据的拷贝,最后再通过kunmap_skb_frag完成映射地址的释放。
     
     
       这些都不是重点,主要的是我在网上看到了这样一些话,便直接把它们拷贝过来吧:
     
       在2.4中是没有这一函数的,因为2.4的netfilter首先进行碎片包重组,随即进行skb的线性化检查,对非线性skb包进行线性化,因此合法skb包进入后续hook点操作时实际skb->data_len就都是0了,可以直接操作。
     
       netfilter的碎片重组函数为ip_ct_gather_frags(),在2.4中碎片重组完还进行线性化,而2.6中重组完就直接返回了,并不进行线性化操作,因此以后在使用的时候必须检查要处理的数据是否在内存页面中。
     
       由于2.6中的碎片重组操作后不进行skb数据包的线性化,因此数据可能存在于不同的内存页面中,对于不在同一页面中的情况不能直接进行数据操作,需要将数据拷贝到一个单独缓冲区后再进行处理。
     
       关于内核为什么这样做,我没找到标志答案,下面是一个网友的回复,也粘贴过来,有机会的话就慢慢了解了:
      
       非线性化很重要的一点是为了支持网卡芯片的一些功能,这些功能可以大大增加TCP的性能。
    
       如TSO(tcp segment offload),在sendfile中。 系统只是增加file对应的page cache 的引用数,接着将一个个页面放在SKB中发送,一次可以放接近65535 - TCP头的数据。网卡如E1000,会根据MSS大小切割报文并计算校验后发送。
     
  • 相关阅读:
    pycharm中python文件名使用中划线导致无法自动导入
    (笔记)Ubuntu20.04更换软件源
    (笔记)ROS学习——rosdep update 超时解决方法
    (笔记)国内如何访问GitHub
    java基于TreeMap或ConcurrentSkipListMap实现数据的范围查找
    数字正则式(整数、小数、负数、保留两位小数等)
    写了一个简易的本地缓存fastmap,支持键过期和键排序等
    Timer和ScheduledThreadPoolExecutor的区别及源码分析
    Jenkins Unstable 状态解释
    微信小程序部署流程
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/12269816.html
Copyright © 2020-2023  润新知