免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2060 | 回复: 1
打印 上一主题 下一主题

sk_buff 剖析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-28 21:36 |只看该作者 |倒序浏览

sk_buff 剖析








基于内核版本2.6.37

本文主要剖析:sk_buff结构体、sk_buff操作函数、各协议层对其处理



主要源文件:linux-2.6.37/ include/ linux/ skbuff.h

                        linux-2.6.37/ include/ linux/ skbuff.c



==================================================================================================

一些相关数据结构



view plaincopy to clipboard
  1. 01.在include/linux/ktime.h中,  
  2. 02.union ktime {  
  3. 03.        s64 tv64 ;  
  4. 04.#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)  
  5. 05.        struct {  
  6. 06.# ifdef __BIG_ENDIAN  
  7. 07.        s32 sec , nsec ;  
  8. 08.#else  
  9. 09.        s32 nsec , sec ;  
  10. 10.#endif  
  11. 11.        } tv ;  
  12. 12.#endif  
  13. 13.} ;  
  14. 14.  
  15. 15.typedef union ktime ktime_t ;  
  16. 16.   
  17. 17. struct sk_buff_head {  
  18. 18. /* These two members must be first. */  
  19. 19.        struct sk_buff *next;  
  20. 20.        struct sk_buff *prev;   
  21. 21.        __u32  qlen;  
  22. 22.        spinlock_t lock;  
  23. 23.};  
  24. 24.   
  25. 25./* 关于sk_buff_data_t */  
  26. 26.# if BITS_PER_LONG > 32  
  27. 27.# define NET_SKBUFF_DATA_USES_OFFSET 1  
  28. 28.# endif  
  29. 29.  
  30. 30.# ifdef NET_SKBUFF_DATA_USES_OFFSET  
  31. 31.typedef unsigned int sk_buff_data_t ;  
  32. 32.# else   
  33. 33.typedef unsigned char *sk_buff_data_t ;  
  34. 34.#endif  
复制代码
==================================================================================================

sk_buff结构体



view plaincopy to clipboard
  1. 01./* struct sk_buff - socket buffer */  
  2. 02.struct sk_buff {  
  3. 03.        /* These two members must be first */  
  4. 04.        struct sk_buff *next ; /* Next buffer in list */  
  5. 05.        struct sk_buff *prev ; /* Previous buffer in list */  
  6. 06.  
  7. 07.        ktime_t tstamp ; /* Time we arrived,记录接收或发送报文的时间戳*/  
  8. 08.  
  9. 09.        struct sock *sk ; /* Socket we are owned by */  
  10. 10.  
  11. 11.        /* Device we arrived on / are leaving by
  12. 12.         * 通过该设备接收或发送,记录网络接口的信息和完成操作
  13. 13.          */  
  14. 14.        struct net_device *dev ;  
  15. 15.  
  16. 16.        /* This is the control buffer. It is free to use for every
  17. 17.         * layer. Please put your private variables there.
  18. 18.         */  
  19. 19.        char cb[48] __aligned (8) ;  
  20. 20.        ...  
  21. 21.        /* data_len为分页数据所包含的全部报文长度
  22. 22.          * len为某时刻的报文总长度
  23. 23.          * 那么,线性数据的长度为:skb->len - skb->data_len
  24. 24.        */  
  25. 25.        unsigned int len , data_len ;  
  26. 26.  
  27. 27.        /* 保存了下一个协议层的信息,在处理报文时由当前协议层设置 */  
  28. 28.        __be16 protocol ;   
  29. 29.        ...  
  30. 30.        /* head指向线性数据区的开始
  31. 31.         * data指向驻留线性数据区中数据的起始位置
  32. 32.         */  
  33. 33.        unsigned char *head , *data ;  
  34. 34.        ...  
  35. 35.        /* 协议头表示 */  
  36. 36.        sk_buff_data_t  transport_header ; /* 传输层协议头 */  
  37. 37.        sk_buff_data_t  network_header ; /* 网络层协议头 */  
  38. 38.        sk_buff_data_t  mac_header ; /* 链路层协议头 */  
  39. 39.  
  40. 40.        sk_buff_data_t tail ; /* 指向驻留在线性数据区的最后一字节数据*/  
  41. 41.        sk_buff_data_end ; /* 指向线性数据区的结尾,确保不超出可用存储缓冲区 */  
  42. 42.        atomic_t users ; /* 引用该sk_buff的数量*/  
  43. 43.  
  44. 44.        /* 该缓冲区所分配的总内存,包括sk_buff结构大小 + 数据块大小 (应该不包括分页大小?)*/  
  45. 45.        unsigned int truesize ;  
  46. 46.}  
  47. 47.  
  48. 48./* This data is invariant across clones and lives at  
  49. 49. * the end of the header data, ie. at skb->end.
  50. 50. */  
  51. 51.struct skb_shared_info {  
  52. 52.        /* number of fragments belonged to this sk_buff  
  53. 53.         * 此sk_buff分页段的数目,它表示frags[]数组的元素数量,该数组包含sk_buff的分页数据
  54. 54.          */  
  55. 55.        unsigned short nr_frags;   
  56. 56.  
  57. 57.        ...  
  58. 58.  
  59. 59.        /* 指向其分段列表,此sk_buff的总长度为frag_list链表中每个分段长度(skb->len)的和,
  60. 60.          * 再加上原始的sk_buff的长度
  61. 61.          * 通过此域可进行报文分段!!
  62. 62.          */  
  63. 63.        struct sk_buff *frag_list ;  
  64. 64.  
  65. 65.        /*  
  66. 66.         * Warning : all fields before dataref are cleared in __alloc_skb()
  67. 67.         * 此sk_buff被引用的次数
  68. 68.          */  
  69. 69.        atomic_t dataref ;  
  70. 70.  
  71. 71.        /*  
  72. 72.         * must be last field
  73. 73.         * 分段的数组,包含sk_buff的分页数据
  74. 74.          */  
  75. 75.         skb_frag_t frags[MAX_SKB_FRAGS] ;  
  76. 76.}  
  77. 77.  
  78. 78./* To allow 64K frame to be packed as single skb without frag_list  
  79. 79. * 允许小于64K的数据不用分段,即不适用frag_list
  80. 80. */  
  81. 81.#define MAX_SKB_FRAGS (65536 / PAGE_SIZE + 2 )  
  82. 82.  
  83. 83.typedef struct skb_frag_struct skb_frag_t ;  
  84. 84.struct skb_frag_struct {  
  85. 85.        struct page *page ; /* 该页的虚拟地可用page_address()得到*/  
  86. 86.  
  87. 87.#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)  
  88. 88.        __u32 page_offset ;  
  89. 89.        __u32 size;  
  90. 90.#else  
  91. 91.        __u16 page_offset ;  
  92. 92.        __u16 size ;  
  93. 93.#endif  
  94. 94.};  
复制代码
注意:分段和分页是两个不同的概念。

分页,即使用非线性数据区,非线性区的含义是包含在sk_buff中的数据长度超过了线性数据区

所能容纳的界限(一般为一页)。包含在非线性数据区中的数据是sk_buff结构中end域所指数据

的连续,全部数据的总长度包含在线性和非线性数据区中。

sk_buff数据的总长度存储在len域,非线性数据的长度存储在sk_buff的data_len域。

分页的实现:

在skb_shared_info中,skb_frag_t frags[MAX_SKB_FRAGS]

通过分页,使得一个sk_buff最多能存:64K的数据(非线性区)+ 一页数据(线性区)。



当DMA支持物理分散页的分散-聚集操作时,才有可能存在分页数据区。如果支持,就为线性数据区

分配一页的数据,其他数据则保存在分页数据区中,随后数据的每个sk_buff分段都会分配一页的数据。

如果不支持,就尝试在线性数据区为整个sk_buff数据分配连续的物理内存。



分段,主要指IP分段的实现。当一个数据报过大时,需要分为多个。即一个sk_buff分为多个

sk_buff,这些sk_buff形成一个链表。

分段的实现:

在skb_shared_info中,struct sk_buff *frag_list

通过frag_list可以遍历分段列表。



======================================================================================================

sk_buff的操作



1. alloc_skb



view plaincopy to clipboard
  1. 01.static inline struct sk_buff *alloc_skb( unsigned int size ,  
  2. 02.                                        gfp_t priority)  
  3. 03.{  
  4. 04.        return __alloc_skb(size , priority , 0 , NUMA_NO_NONE) ;  
  5. 05.}  
复制代码
size是数据包的大小。

The returned buffer has no headroom and a tail room of size bytes.



2. skb_reserve

用来为协议头预留空间。拓展head room。



view plaincopy to clipboard
  1. 01./**
  2. 02. * skb_reserve - ajust headroom
  3. 03. * @skb : buffer to alter
  4. 04. * @len : bytes to move
  5. 05. *
  6. 06. * Increase the headroom of an empty &sk_buff by reducing the tail
  7. 07. * room. This is only allowed for an empty buffer.
  8. 08. */  
  9. 09.  
  10. 10.static inline void skb_reserve( struct sk_buff *skb , int len )  
  11. 11.{  
  12. 12.        skb->data += len ;   
  13. 13.        skb->tail += len ;  
  14. 14.}  
复制代码
此时,head room 大小为len,data room 大小0,tail room大小为原长 - len。

当构造一个报文时,要为协议头预留最大可能的空间。

如,MAX_TCP_HEADER = MAX_TCP_HEADER + MAX_IP_HEADER + LL_MAX_HEADER



3. skb_put

用来拓展data room。当要向data room增加数据时,先增加data room的可使用空间。



view plaincopy to clipboard
  1. 01./**
  2. 02. * skb_put - add data to a buffer
  3. 03. * @skb : buffer to use
  4. 04. * @len : amount of data to add
  5. 05. *
  6. 06. * This function extends the used data area of the buffer. If this would  
  7. 07. * exceed the total buffer size the kernel will panic. A pointer to the
  8. 08. * first byte of the extra data is returned.
  9. 09. */  
  10. 10.  
  11. 11.unsigned char *skb_put( struct sk_buff *skb , unsigned int len )  
  12. 12.{  
  13. 13.        unsigned char *tmp = skb_tail_pointer(skb) ;  
  14. 14.        /* 如果存在非线性区,即data_len > 0 ,则报bug */  
  15. 15.        SKB_LINEAR_ASSERT(skb) ;  
  16. 16.        skb->tail += len ;  
  17. 17.        skb->len += len ;  
  18. 18.        if (unlikely(skb->tail > skb->end ))  
  19. 19.                skb_over_panic(skb , len , __builtin_return_address(0)) ;  
  20. 20.        return tmp ;  
  21. 21.}  
复制代码
4. skb_push

用来拓展data room。和skb_put不同的是,它不是向tail room扩展,而是向head room扩展。



view plaincopy to clipboard
  1. 01./**
  2. 02. * skb_push - add data to the start of a buffer
  3. 03. * @skb : buffer to use
  4. 04. * @len : amount of data to add
  5. 05. *
  6. 06. * This function extends the used data area of the buffer at the buffer
  7. 07. * start. If this would exceed the total buffer headroom the kernel will
  8. 08. * panic. A pointer to the first byte of the extra data is returned.
  9. 09. */  
  10. 10.  
  11. 11.unsigned char *skb_push( struct sk_buff *skb , unsigned int len )  
  12. 12.{  
  13. 13.        skb->data -= len ;  
  14. 14.        skb->len += len ;  
  15. 15.        if ( unlikely(skb->data < skb->head ) )  
  16. 16.                skb_under_panic(skb , len , __builtin_return_address(0)) ;  
  17. 17.        return skb->data ;  
  18. 18.}  
复制代码
注意:

发送报文一般要调用alloc_skb、skb_reserve、skb_put、skb_push。

发送报文时,在不同协议层处理数据时,该数据要添加相应的协议头。

因此,最高层添加数据和自身的协议头。alloc_skb用来申请一个sk_buff。

skb_reserve用来创建头空间。skb_put用来创建用户数据空间,用户数据复制到sk->data

指向的数据区。接下来是在用户数据的前面加上协议头,使用skb_push。



5. skb_pull

在报文到达时访问协议头,接收报文时调用。使head room向data room扩展。



view plaincopy to clipboard
  1. 01./**
  2. 02. * skb_pull - remove data from the start of a buffer
  3. 03. * @skb : buffer to use
  4. 04. * @len : amount of data to remove
  5. 05. *
  6. 06. * This function removes data from the start of a buffer, returning the memory to
  7. 07. * the headroom. A pointer to the next data in the buffer is returned. Once the
  8. 08. * data has been pulled future pushes will overwrite the old data.
  9. 09. */  
  10. 10.  
  11. 11.unsigned char *skb_pull( struct sk_buff *skb , unsigned int len )  
  12. 12.{  
  13. 13.        return skb_pull_inline(skb , len ) ;  
  14. 14.}  
  15. 15.  
  16. 16.static inline unsigned char *skb_pull_inline(struct sk_buff *skb , unsigned int len)  
  17. 17.{  
  18. 18.        return unlikely(len > skb->len ) ? NULL : __skb_pull(skb , len) ;  
  19. 19.}  
  20. 20.  
  21. 21.static inline unsigned char *__skb_pull(struct sk_buff *skb , unsigned int len)  
  22. 22.{  
  23. 23.        skb->len -= len ;  
  24. 24.        BUG_ON(skb->len < skb->data_len ) ;  
  25. 25.        return skb->data += len ;  
  26. 26.}  
复制代码
====================================================================================================





view plaincopy to clipboard
  1. 01.# ifdef NET_SKBUFF_DATA_USES_OFFSET  
  2. 02.static inline unsigned char *skb_transport_header(const struct sk_buff skb)  
  3. 03.{  
  4. 04.        return skb->head + skb->transport_header ;  
  5. 05.}  
  6. 06.  
  7. 07.static inline void skb_reset_transport_header(struct sk_buff *skb)  
  8. 08.{  
  9. 09.        skb->transport_header = skb->data - skb->head ;  
  10. 10.}  
  11. 11.# else   
  12. 12.  
  13. 13.static inline unsigned char *skb_transport_header(const struct sk_buff skb)  
  14. 14.{  
  15. 15.        return skb->transport_header ;  
  16. 16.}  
  17. 17.  
  18. 18.static inline void skb_reset_transport_header(struct sk_buff *skb)  
  19. 19.{  
  20. 20.        skb->transport_header = skb->data ;  
  21. 21.}  
  22. 22.   
  23. 23.static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)  
  24. 24.{  
  25. 25.        return (struct tcphdr *) skb_transport_header(skb) ;  
  26. 26.}  
复制代码
sk_buff中tcp协议头的表示:

sk_buff_data_t transport_header ;

用函数tcp_hdr(skb)来获取。

当tcp协议头地址有变化时,用skb_reset_transport_header(skb)来更新transport_header。

===============================================================================================



向下遍历协议层(即发送数据包)时,构建协议头



1. 添加TCP头

TCP调用tcp_transmit_skb()来为TCP数据段构建一个TCP头。

首先计算TCP头的长度,要考虑当前TCP连接所使用的选项。一旦完成该操作,就需要调用

skb_push()来为TCP头分配空间。



view plaincopy to clipboard
  1. 01./* This routine actually transmit TCP packets queued in by tcp_do_sendmsg().  
  2. 02. * This is used by both the initial transmission and possible later retransmissions.
  3. 03. * All SKB's seen here are completely headerless. It is our job to build the TCP
  4. 04. * header, and pass the packet down to IP so it can do the same plus pass the
  5. 05. * packet off to the device.
  6. 06. *
  7. 07. * We are working here with either a clone of the original SKB, or a fresh unique
  8. 08. * copy made by the retransmit engine.
  9. 09. */  
  10. 10.  
  11. 11.static int tcp_transmit_skb(struct sock *sk , struct sk_buff *skb , int clone_it ,  
  12. 12.                               gfp_t gfp_mask)  
  13. 13.{  
  14. 14.        ...  
  15. 15.        struct inet_sock *inet = inet_sk(sk) ;  
  16. 16.        unsigned tcp_option_size, tcp_header_size ;  
  17. 17.        struct tcphdr *th ;  
  18. 18.        ...  
  19. 19.        tcp_header_size = tcp_option_size + sizeof(struct tcphdr) ;  
  20. 20.        ...  
  21. 21.        skb_push(skb , tcp_header_size) ;  
  22. 22.        skb_reset_transport_header(skb) ;  
  23. 23.        ...  
  24. 24.        /* Build TCP header and checksum it. */  
  25. 25.        th = tcp_hdr(skb) ;  
  26. 26.        th->source = inet->inet_sport ;  
  27. 27.        th->dest = inet->inet_dport ;  
  28. 28.        ...  
  29. 29.}  
复制代码
2. 添加IP头

ip_build_and_send_pkt()构造报文的IP头,并发送给链路层。



view plaincopy to clipboard
  1. 01./*
  2. 02. * Add an ip header to a sk_buff and sent it out.
  3. 03. */  
  4. 04.int ip_build_and_sent_pkt(struct sk_buff *skb , struct sock *sk ,  
  5. 05.                     __be32 saddr , __be32 daddr , struct ip_options *opt)   
  6. 06.{  
  7. 07.        struct inet_sock *inet = inet_sk(sk) ;  
  8. 08.        ...  
  9. 09.        struct iphdr *iph ;  
  10. 10.        /* Build the IP header. */  
  11. 11.        skb_push(skb , sizeof(struct iphdr) + (opt ? opt->optlen : 0) ) ;  
  12. 12.        skb_reset_network_header(skb) ;  
  13. 13.        iph = ip_hdr(skb) ;  
  14. 14.        iph->version = 4 ;  
  15. 15.        iph->ihl = 5 ;  
  16. 16.        iph->tos = inet->tos ;  
  17. 17.        ...  
  18. 18.}  
复制代码
3. 添加链路层头

eth_header构造以太网帧协议头。



view plaincopy to clipboard
  1. 01.#define ETH_HLEN 14  
  2. 02./**
  3. 03. * eth_header - create the Ethernet header
  4. 04. * @skb : buffer to alter
  5. 05. * @dev : source device
  6. 06. * @type : Ethernet type field
  7. 07. * @daddr : destination address
  8. 08. * @saddr : source address
  9. 09. * @len : packet length (<= skb->len)
  10. 10. *
  11. 11. * Set the protocal type. For a packet of type ETH_P_802_3/2 we put  
  12. 12. * the length in here instead.
  13. 13. */  
  14. 14.int eth_header(struct sk_buff *skb , struct net_device *dev ,  
  15. 15.                unsigned short type , const void *daddr , const void *saddr,  
  16. 16.                unsigned len)  
  17. 17.{  
  18. 18.        struct ethhdr *eth = (struct ethhdr *) skb_push(skb , ETH_HLEN) ;  
  19. 19.        ...  
  20. 20.}  
复制代码
=======================================================================================================



向上遍历协议层(接收数据包)时,解析协议头



1. 解析以太网头

当新报文到达时,要为新报文分配一个新的sk_buff,其大小等于报文的长度。sk_buff

的data域指向报文的起始位置(以太网头)。使用skb_pull来提取不同的协议层头。

该例程在sk_buff到IP backlog队列排队之前完成。



view plaincopy to clipboard
  1. 01./**
  2. 02. * eth_type_trans - determine the packet's protocol ID.
  3. 03. * @skb : received socket data
  4. 04. * @dev : receiving network device
  5. 05. *
  6. 06. * The rule here is that we
  7. 07. * assume 802.3 if the type field is short enough to be a length.
  8. 08. * This is normal practice and works for any 'now in use' protocol.
  9. 09. */  
  10. 10.__be16 eth_type_trans(struct sk_buff *skb , struct net_device *dev )  
  11. 11.{  
  12. 12.        struct ethhdr *eth ;  
  13. 13.        skb->dev = dev ;  
  14. 14.        skb_reset_mac_header(skb) ; /* 更新mac_header */  
  15. 15.        skb_pull_inline(skb , ETH_HLEN) ; /* 此后data指向IP头 */  
  16. 16.        eth = eth_hdr(skb) ;  
  17. 17.        ...  
  18. 18.}  
复制代码
2. 解析IP头

现在sk_buff处于IP backlog队列中,由netif_receive_skb()负责处理,该函数将sk_buff

从backlog队列中取出。

netif_receive_skb() 接收数据包得主要处理函数。



view plaincopy to clipboard
  1. 01./**
  2. 02. * netif_receive_skb - process receive buffer from network
  3. 03. * @skb : buffer to process
  4. 04. * netif_receive_skb() is the main receive data processing function.
  5. 05. * It always succeeds. The buffer may be dropped during processing
  6. 06. * for congestion control or by the protocol layers.
  7. 07. *
  8. 08. * This function may only be called from softirq context and interrupts
  9. 09. * should be enabled.
  10. 10. *  
  11. 11. * Return values (usually ignored) :
  12. 12. * NET_RX_SUCCESS : no congestion
  13. 13. * NET_RX_DROP : packet was dropped
  14. 14. */  
  15. 15.  
  16. 16.int netif_receive_skb(struct sk_buff *skb)  
复制代码
3. 解析tcp头

网络层处理完报文,在将data指针指向传输层起始位置,并更新transport_header后,

将报文递给传输层,这些工作有ip_local_deliver_finish()来完成。



view plaincopy to clipboard
  1. 01.static int ip_local_deliver_finish(struct sk_buff *skb)  
  2. 02.{  
  3. 03.        ...  
  4. 04.        __skb_pull(skb , ip_hdrlen(skb)) ;  
  5. 05.        skb_reset_transport_header(skb) ;  
  6. 06.        ...  
  7. 07.}  
  8. 08.  
  9. 09.static inline unsigned int ip_hdrlen(const struct sk_buff *skb)  
  10. 10.{  
  11. 11.        return ip_hdr(skb)->ihl * 4 ;  
  12. 12.}  

复制代码
传输层调用tcp_v4_do_rcv()处理传输层头报文。如果连接已建立,并且TCP报文中有数据,

就调用skb_copy_datagram_iovec()将从skb->data偏移tcp_header_len开始的数据复制给

用户应用程序。如果由于某些原因不能复制数据给用户应用程序,就将sk_buff的data指针

向前移动tcp_header_len,再将其发往套接字的接受队列排队。

论坛徽章:
0
2 [报告]
发表于 2011-12-28 21:36 |只看该作者
谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP