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
==================================================================================================
一些相關資料結構
- 在include/linux/ktime.h中,
- union ktime {
- s64 tv64 ;
- #if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
- struct {
- # ifdef __BIG_ENDIAN
- s32 sec , nsec ;
- #else
- s32 nsec , sec ;
- #endif
- } tv ;
- #endif
- } ;
- typedef union ktime ktime_t ;
- struct sk_buff_head {
- /* These two members must be first. */
- struct sk_buff *next;
- struct sk_buff *prev;
- __u32 qlen;
- spinlock_t lock;
- };
- /* 關於sk_buff_data_t */
- # if BITS_PER_LONG > 32
- # define NET_SKBUFF_DATA_USES_OFFSET 1
- # endif
- # ifdef NET_SKBUFF_DATA_USES_OFFSET
- typedef unsigned int sk_buff_data_t ;
- # else
- typedef unsigned char *sk_buff_data_t ;
- #endif
==================================================================================================
sk_buff結構體
- /* struct sk_buff - socket buffer */
- struct sk_buff {
- /* These two members must be first */
- struct sk_buff *next ; /* Next buffer in list */
- struct sk_buff *prev ; /* Previous buffer in list */
- ktime_t tstamp ; /* Time we arrived,記錄接收或傳送報文的時間戳*/
- struct sock *sk ; /* Socket we are owned by */
- /* Device we arrived on / are leaving by
- * 通過該裝置接收或傳送,記錄網路介面的資訊和完成操作
- */
- struct net_device *dev ;
- /* This is the control buffer. It is free to use for every
- * layer. Please put your private variables there.
- */
- char cb[48] __aligned (8) ;
- ...
- /* data_len為分頁資料所包含的全部報文長度
- * len為某時刻的報文總長度
- * 那麼,線性資料的長度為:skb->len - skb->data_len
- */
- unsigned int len , data_len ;
- /* 儲存了下一個協議層的資訊,在處理報文時由當前協議層設定 */
- __be16 protocol ;
- ...
- /* head指向線性資料區的開始
- * data指向駐留線性資料區中資料的起始位置
- */
- unsigned char *head , *data ;
- ...
- /* 協議頭表示 */
- sk_buff_data_t transport_header ; /* 傳輸層協議頭 */
- sk_buff_data_t network_header ; /* 網路層協議頭 */
- sk_buff_data_t mac_header ; /* 鏈路層協議頭 */
- sk_buff_data_t tail ; /* 指向駐留線上性資料區的最後一位元組資料*/
- sk_buff_data_end ; /* 指向線性資料區的結尾,確保不超出可用儲存緩衝區 */
- atomic_t users ; /* 引用該sk_buff的數量*/
- /* 該緩衝區所分配的總記憶體,包括sk_buff結構大小 + 資料塊大小 (應該不包括分頁大小?)*/
- unsigned int truesize ;
- }
- /* This data is invariant across clones and lives at
- * the end of the header data, ie. at skb->end.
- */
- struct skb_shared_info {
- /* number of fragments belonged to this sk_buff
- * 此sk_buff分頁段的數目,它表示frags[]陣列的元素數量,該陣列包含sk_buff的分頁資料
- */
- unsigned short nr_frags;
- ...
- /* 指向其分段列表,此sk_buff的總長度為frag_list連結串列中每個分段長度(skb->len)的和,
- * 再加上原始的sk_buff的長度
- * 通過此域可進行報文分段!!
- */
- struct sk_buff *frag_list ;
- /*
- * Warning : all fields before dataref are cleared in __alloc_skb()
- * 此sk_buff被引用的次數
- */
- atomic_t dataref ;
- /*
- * must be last field
- * 分段的陣列,包含sk_buff的分頁資料
- */
- skb_frag_t frags[MAX_SKB_FRAGS] ;
- }
- /* To allow 64K frame to be packed as single skb without frag_list
- * 允許小於64K的資料不用分段,即不適用frag_list
- */
- #define MAX_SKB_FRAGS (65536 / PAGE_SIZE + 2 )
- typedef struct skb_frag_struct skb_frag_t ;
- struct skb_frag_struct {
- struct page *page ; /* 該頁的虛擬地可用page_address()得到*/
- #if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
- __u32 page_offset ;
- __u32 size;
- #else
- __u16 page_offset ;
- __u16 size ;
- #endif
- };
注意:分段和分頁是兩個不同的概念。
分頁,即使用非線性資料區,非線性區的含義是包含在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
- static inline struct sk_buff *alloc_skb( unsigned int size ,
- gfp_t priority)
- {
- return __alloc_skb(size , priority , 0 , NUMA_NO_NONE) ;
- }
size是資料包的大小。
The returned buffer has no headroom and a tail room of size bytes.
2. skb_reserve
用來為協議頭預留空間。拓展head room。
- /**
- * skb_reserve - ajust 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 , int len )
- {
- skb->data += len ;
- skb->tail += len ;
- }
此時,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的可使用空間。
- /**
- * 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.
- */
- unsigned char *skb_put( struct sk_buff *skb , unsigned int len )
- {
- unsigned char *tmp = skb_tail_pointer(skb) ;
- /* 如果存在非線性區,即data_len > 0 ,則報bug */
- SKB_LINEAR_ASSERT(skb) ;
- skb->tail += len ;
- skb->len += len ;
- if (unlikely(skb->tail > skb->end ))
- skb_over_panic(skb , len , __builtin_return_address(0)) ;
- return tmp ;
- }
4. skb_push
用來拓展data room。和skb_put不同的是,它不是向tail room擴充套件,而是向head room擴充套件。
- /**
- * 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.
- */
- unsigned char *skb_push( struct sk_buff *skb , unsigned int len )
- {
- skb->data -= len ;
- skb->len += len ;
- if ( unlikely(skb->data < skb->head ) )
- skb_under_panic(skb , len , __builtin_return_address(0)) ;
- return skb->data ;
- }
注意:
傳送報文一般要呼叫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擴充套件。
- /**
- * 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.
- */
- unsigned char *skb_pull( struct sk_buff *skb , unsigned int len )
- {
- return skb_pull_inline(skb , len ) ;
- }
- static inline unsigned char *skb_pull_inline(struct sk_buff *skb , unsigned int len)
- {
- return unlikely(len > skb->len ) ? NULL : __skb_pull(skb , len) ;
- }
- static inline unsigned char *__skb_pull(struct sk_buff *skb , unsigned int len)
- {
- skb->len -= len ;
- BUG_ON(skb->len < skb->data_len ) ;
- return skb->data += len ;
- }
====================================================================================================
- # ifdef NET_SKBUFF_DATA_USES_OFFSET
- static inline unsigned char *skb_transport_header(const struct sk_buff skb)
- {
- return skb->head + skb->transport_header ;
- }
- static inline void skb_reset_transport_header(struct sk_buff *skb)
- {
- skb->transport_header = skb->data - skb->head ;
- }
- # else
- static inline unsigned char *skb_transport_header(const struct sk_buff skb)
- {
- return skb->transport_header ;
- }
- static inline void skb_reset_transport_header(struct sk_buff *skb)
- {
- skb->transport_header = skb->data ;
- }
- static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)
- {
- return (struct tcphdr *) skb_transport_header(skb) ;
- }
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頭分配空間。
- /* This routine actually transmit TCP packets queued in by tcp_do_sendmsg().
- * This is used by both the initial transmission and possible later retransmissions.
- * All SKB's seen here are completely headerless. It is our job to build the TCP
- * header, and pass the packet down to IP so it can do the same plus pass the
- * packet off to the device.
- *
- * We are working here with either a clone of the original SKB, or a fresh unique
- * copy made by the retransmit engine.
- */
- static int tcp_transmit_skb(struct sock *sk , struct sk_buff *skb , int clone_it ,
- gfp_t gfp_mask)
- {
- ...
- struct inet_sock *inet = inet_sk(sk) ;
- unsigned tcp_option_size, tcp_header_size ;
- struct tcphdr *th ;
- ...
- tcp_header_size = tcp_option_size + sizeof(struct tcphdr) ;
- ...
- skb_push(skb , tcp_header_size) ;
- skb_reset_transport_header(skb) ;
- ...
- /* Build TCP header and checksum it. */
- th = tcp_hdr(skb) ;
- th->source = inet->inet_sport ;
- th->dest = inet->inet_dport ;
- ...
- }
2. 新增IP頭
ip_build_and_send_pkt()構造報文的IP頭,併發送給鏈路層。
- /*
- * Add an ip header to a sk_buff and sent it out.
- */
- int ip_build_and_sent_pkt(struct sk_buff *skb , struct sock *sk ,
- __be32 saddr , __be32 daddr , struct ip_options *opt)
- {
- struct inet_sock *inet = inet_sk(sk) ;
- ...
- struct iphdr *iph ;
- /* Build the IP header. */
- skb_push(skb , sizeof(struct iphdr) + (opt ? opt->optlen : 0) ) ;
- skb_reset_network_header(skb) ;
- iph = ip_hdr(skb) ;
- iph->version = 4 ;
- iph->ihl = 5 ;
- iph->tos = inet->tos ;
- ...
- }
3. 新增鏈路層頭
eth_header構造乙太網幀協議頭。
- #define ETH_HLEN 14
- /**
- * eth_header - create the Ethernet header
- * @skb : buffer to alter
- * @dev : source device
- * @type : Ethernet type field
- * @daddr : destination address
- * @saddr : source address
- * @len : packet length (<= skb->len)
- *
- * Set the protocal type. For a packet of type ETH_P_802_3/2 we put
- * the length in here instead.
- */
- int eth_header(struct sk_buff *skb , struct net_device *dev ,
- unsigned short type , const void *daddr , const void *saddr,
- unsigned len)
- {
- struct ethhdr *eth = (struct ethhdr *) skb_push(skb , ETH_HLEN) ;
- ...
- }
=======================================================================================================
向上遍歷協議層(接收資料包)時,解析協議頭
1. 解析乙太網頭
當新報文到達時,要為新報文分配一個新的sk_buff,其大小等於報文的長度。sk_buff
的data域指向報文的起始位置(乙太網頭)。使用skb_pull來提取不同的協議層頭。
該例程在sk_buff到IP backlog佇列排隊之前完成。
- /**
- * eth_type_trans - determine the packet's protocol ID.
- * @skb : received socket data
- * @dev : receiving network device
- *
- * The rule here is that we
- * assume 802.3 if the type field is short enough to be a length.
- * This is normal practice and works for any 'now in use' protocol.
- */
- __be16 eth_type_trans(struct sk_buff *skb , struct net_device *dev )
- {
- struct ethhdr *eth ;
- skb->dev = dev ;
- skb_reset_mac_header(skb) ; /* 更新mac_header */
- skb_pull_inline(skb , ETH_HLEN) ; /* 此後data指向IP頭 */
- eth = eth_hdr(skb) ;
- ...
- }
2. 解析IP頭
現在sk_buff處於IP backlog佇列中,由netif_receive_skb()負責處理,該函式將sk_buff
從backlog佇列中取出。
netif_receive_skb() 接收資料包得主要處理函式。
- /**
- * netif_receive_skb - process receive buffer from network
- * @skb : buffer to process
- * netif_receive_skb() is the main receive data processing function.
- * It always succeeds. The buffer may be dropped during processing
- * for congestion control or by the protocol layers.
- *
- * This function may only be called from softirq context and interrupts
- * should be enabled.
- *
- * Return values (usually ignored) :
- * NET_RX_SUCCESS : no congestion
- * NET_RX_DROP : packet was dropped
- */
- int netif_receive_skb(struct sk_buff *skb)
3. 解析tcp頭
網路層處理完報文,在將data指標指向傳輸層起始位置,並更新transport_header後,
將報文遞給傳輸層,這些工作有ip_local_deliver_finish()來完成。
- static int ip_local_deliver_finish(struct sk_buff *skb)
- {
- ...
- __skb_pull(skb , ip_hdrlen(skb)) ;
- skb_reset_transport_header(skb) ;
- ...
- }
- static inline unsigned int ip_hdrlen(const struct sk_buff *skb)
- {
- return ip_hdr(skb)->ihl * 4 ;
- }
傳輸層呼叫tcp_v4_do_rcv()處理傳輸層頭報文。如果連線已建立,並且TCP報文中有資料,
就呼叫skb_copy_datagram_iovec()將從skb->data偏移tcp_header_len開始的資料複製給
使用者應用程式。如果由於某些原因不能複製資料給使用者應用程式,就將sk_buff的data指標
向前移動tcp_header_len,再將其發往套接字的接受佇列排隊。
</div>
</div>