1. 程式人生 > >Linux SKB基本結構剖析

Linux SKB基本結構剖析

基於核心版本2.6.37

本文主要剖析:sk_buff結構體、sk_buff操作函式、各協議層對其處理

主要原始檔:linux-2.6.37/ include/ linux/ skbuff.h

                        linux-2.6.37/ include/ linux/ skbuff.c

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

 一些相關資料結構

  1. 在include/linux/ktime.h中,
  2. union ktime {
  3. s64 tv64 ;
  4. #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
  5. struct {
  6. # ifdef __BIG_ENDIAN
  7. s32 sec , nsec ;
  8. #else
  9. s32 nsec , sec ;
  10. #endif
  11. } tv ;
  12. #endif
  13. } ;
  14. typedef union ktime ktime_t ;
  15. struct sk_buff_head {
  16. /* These two members must be first. */
  17. struct sk_buff *next;
  18. struct sk_buff *prev;
  19. __u32 qlen;
  20. spinlock_t lock;
  21. };
  22. /* 關於sk_buff_data_t */
  23. # if BITS_PER_LONG > 32
  24. # define NET_SKBUFF_DATA_USES_OFFSET 1
  25. # endif
  26. # ifdef NET_SKBUFF_DATA_USES_OFFSET
  27. typedef unsigned int sk_buff_data_t ;
  28. # else
  29. typedef unsigned char *sk_buff_data_t ;
  30. #endif

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

sk_buff結構體

  1. /* struct sk_buff - socket buffer */
  2. struct sk_buff {
  3. /* These two members must be first */
  4. struct sk_buff *next ; /* Next buffer in list */
  5. struct sk_buff *prev ; /* Previous buffer in list */
  6. ktime_t tstamp ; /* Time we arrived,記錄接收或傳送報文的時間戳*/
  7. struct sock *sk ; /* Socket we are owned by */
  8. /* Device we arrived on / are leaving by
  9. * 通過該裝置接收或傳送,記錄網路介面的資訊和完成操作
  10. */
  11. struct net_device *dev ;
  12. /* This is the control buffer. It is free to use for every
  13. * layer. Please put your private variables there.
  14. */
  15. char cb[48] __aligned (8) ;
  16. ...
  17. /* data_len為分頁資料所包含的全部報文長度
  18. * len為某時刻的報文總長度
  19. * 那麼,線性資料的長度為:skb->len - skb->data_len
  20. */
  21. unsigned int len , data_len ;
  22. /* 儲存了下一個協議層的資訊,在處理報文時由當前協議層設定 */
  23. __be16 protocol ;
  24. ...
  25. /* head指向線性資料區的開始
  26. * data指向駐留線性資料區中資料的起始位置
  27. */
  28. unsigned char *head , *data ;
  29. ...
  30. /* 協議頭表示 */
  31. sk_buff_data_t transport_header ; /* 傳輸層協議頭 */
  32. sk_buff_data_t network_header ; /* 網路層協議頭 */
  33. sk_buff_data_t mac_header ; /* 鏈路層協議頭 */
  34. sk_buff_data_t tail ; /* 指向駐留線上性資料區的最後一位元組資料*/
  35. sk_buff_data_end ; /* 指向線性資料區的結尾,確保不超出可用儲存緩衝區 */
  36. atomic_t users ; /* 引用該sk_buff的數量*/
  37. /* 該緩衝區所分配的總記憶體,包括sk_buff結構大小 + 資料塊大小 (應該不包括分頁大小?)*/
  38. unsigned int truesize ;
  39. }
  40. /* This data is invariant across clones and lives at
  41. * the end of the header data, ie. at skb->end.
  42. */
  43. struct skb_shared_info {
  44. /* number of fragments belonged to this sk_buff
  45. * 此sk_buff分頁段的數目,它表示frags[]陣列的元素數量,該陣列包含sk_buff的分頁資料
  46. */
  47. unsigned short nr_frags;
  48. ...
  49. /* 指向其分段列表,此sk_buff的總長度為frag_list連結串列中每個分段長度(skb->len)的和,
  50. * 再加上原始的sk_buff的長度
  51. * 通過此域可進行報文分段!!
  52. */
  53. struct sk_buff *frag_list ;
  54. /*
  55. * Warning : all fields before dataref are cleared in __alloc_skb()
  56. * 此sk_buff被引用的次數
  57. */
  58. atomic_t dataref ;
  59. /*
  60. * must be last field
  61. * 分段的陣列,包含sk_buff的分頁資料
  62. */
  63. skb_frag_t frags[MAX_SKB_FRAGS] ;
  64. }
  65. /* To allow 64K frame to be packed as single skb without frag_list
  66. * 允許小於64K的資料不用分段,即不適用frag_list
  67. */
  68. #define MAX_SKB_FRAGS (65536 / PAGE_SIZE + 2 )
  69. typedef struct skb_frag_struct skb_frag_t ;
  70. struct skb_frag_struct {
  71. struct page *page ; /* 該頁的虛擬地可用page_address()得到*/
  72. #if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
  73. __u32 page_offset ;
  74. __u32 size;
  75. #else
  76. __u16 page_offset ;
  77. __u16 size ;
  78. #endif
  79. };

注意:分段和分頁是兩個不同的概念。

分頁,即使用非線性資料區,非線性區的含義是包含在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

  1. static inline struct sk_buff *alloc_skb( unsigned int size ,
  2. gfp_t priority)
  3. {
  4. return __alloc_skb(size , priority , 0 , NUMA_NO_NONE) ;
  5. }

size是資料包的大小。

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

2. skb_reserve

用來為協議頭預留空間。拓展head room。

  1. /**
  2. * skb_reserve - ajust headroom
  3. * @skb : buffer to alter
  4. * @len : bytes to move
  5. *
  6. * Increase the headroom of an empty &sk_buff by reducing the tail
  7. * room. This is only allowed for an empty buffer.
  8. */
  9. static inline void skb_reserve( struct sk_buff *skb , int len )
  10. {
  11. skb->data += len ;
  12. skb->tail += len ;
  13. }

此時,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的可使用空間。

  1. /**
  2. * skb_put - add data to a buffer
  3. * @skb : buffer to use
  4. * @len : amount of data to add
  5. *
  6. * This function extends the used data area of the buffer. If this would
  7. * exceed the total buffer size the kernel will panic. A pointer to the
  8. * first byte of the extra data is returned.
  9. */
  10. unsigned char *skb_put( struct sk_buff *skb , unsigned int len )
  11. {
  12. unsigned char *tmp = skb_tail_pointer(skb) ;
  13. /* 如果存在非線性區,即data_len > 0 ,則報bug */
  14. SKB_LINEAR_ASSERT(skb) ;
  15. skb->tail += len ;
  16. skb->len += len ;
  17. if (unlikely(skb->tail > skb->end ))
  18. skb_over_panic(skb , len , __builtin_return_address(0)) ;
  19. return tmp ;
  20. }

4. skb_push

用來拓展data room。和skb_put不同的是,它不是向tail room擴充套件,而是向head room擴充套件。

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

注意

傳送報文一般要呼叫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擴充套件。

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

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

  1. # ifdef NET_SKBUFF_DATA_USES_OFFSET
  2. static inline unsigned char *skb_transport_header(const struct sk_buff skb)
  3. {
  4. return skb->head + skb->transport_header ;
  5. }
  6. static inline void skb_reset_transport_header(struct sk_buff *skb)
  7. {
  8. skb->transport_header = skb->data - skb->head ;
  9. }
  10. # else
  11. static inline unsigned char *skb_transport_header(const struct sk_buff skb)
  12. {
  13. return skb->transport_header ;
  14. }
  15. static inline void skb_reset_transport_header(struct sk_buff *skb)
  16. {
  17. skb->transport_header = skb->data ;
  18. }
  19. static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)
  20. {
  21. return (struct tcphdr *) skb_transport_header(skb) ;
  22. }

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頭分配空間。

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

2. 新增IP頭

ip_build_and_send_pkt()構造報文的IP頭,併發送給鏈路層。

  1. /*
  2. * Add an ip header to a sk_buff and sent it out.
  3. */
  4. int ip_build_and_sent_pkt(struct sk_buff *skb , struct sock *sk ,
  5. __be32 saddr , __be32 daddr , struct ip_options *opt)
  6. {
  7. struct inet_sock *inet = inet_sk(sk) ;
  8. ...
  9. struct iphdr *iph ;
  10. /* Build the IP header. */
  11. skb_push(skb , sizeof(struct iphdr) + (opt ? opt->optlen : 0) ) ;
  12. skb_reset_network_header(skb) ;
  13. iph = ip_hdr(skb) ;
  14. iph->version = 4 ;
  15. iph->ihl = 5 ;
  16. iph->tos = inet->tos ;
  17. ...
  18. }

3. 新增鏈路層頭

eth_header構造乙太網幀協議頭。

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

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

向上遍歷協議層(接收資料包)時,解析協議頭

1. 解析乙太網頭

當新報文到達時,要為新報文分配一個新的sk_buff,其大小等於報文的長度。sk_buff

的data域指向報文的起始位置(乙太網頭)。使用skb_pull來提取不同的協議層頭。

該例程在sk_buff到IP backlog佇列排隊之前完成。

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

2. 解析IP頭

現在sk_buff處於IP backlog佇列中,由netif_receive_skb()負責處理,該函式將sk_buff

從backlog佇列中取出。

netif_receive_skb() 接收資料包得主要處理函式。

  1. /**
  2. * netif_receive_skb - process receive buffer from network
  3. * @skb : buffer to process
  4. * netif_receive_skb() is the main receive data processing function.
  5. * It always succeeds. The buffer may be dropped during processing
  6. * for congestion control or by the protocol layers.
  7. *
  8. * This function may only be called from softirq context and interrupts
  9. * should be enabled.
  10. *
  11. * Return values (usually ignored) :
  12. * NET_RX_SUCCESS : no congestion
  13. * NET_RX_DROP : packet was dropped
  14. */
  15. int netif_receive_skb(struct sk_buff *skb)

3. 解析tcp頭

網路層處理完報文,在將data指標指向傳輸層起始位置,並更新transport_header後,

將報文遞給傳輸層,這些工作有ip_local_deliver_finish()來完成。

  1. static int ip_local_deliver_finish(struct sk_buff *skb)
  2. {
  3. ...
  4. __skb_pull(skb , ip_hdrlen(skb)) ;
  5. skb_reset_transport_header(skb) ;
  6. ...
  7. }
  8. static inline unsigned int ip_hdrlen(const struct sk_buff *skb)
  9. {
  10. return ip_hdr(skb)->ihl * 4 ;
  11. }

傳輸層呼叫tcp_v4_do_rcv()處理傳輸層頭報文。如果連線已建立,並且TCP報文中有資料,

就呼叫skb_copy_datagram_iovec()將從skb->data偏移tcp_header_len開始的資料複製給

使用者應用程式。如果由於某些原因不能複製資料給使用者應用程式,就將sk_buff的data指標

向前移動tcp_header_len,再將其發往套接字的接受佇列排隊。

        </div>
            </div>