Linux網路協議棧(二)——套接字快取(socket buffer)
阿新 • • 發佈:2019-01-23
Linux網路核心資料結構是套接字快取(socket buffer),簡稱skb。它代表一個要傳送或處理的報文,並貫穿於整個協議棧。
1、 套接字快取
skb由兩部分組成:
(1) 報文資料:它儲存了實際在網路中傳輸的資料;
(2) 管理資料:供核心處理報文的額外資料,這些資料構成了協議之間交換的控制資訊。
當應用程式向一個socket傳輸資料之後,該socket將建立相應的套接字快取,並將使用者資料拷貝到快取中。當報文在各協議層傳達輸的過程中,每一導的報文頭將插入到使用者資料之前。skb為報文頭申請了足夠的空間,所以避免了由於插入報文頭而對報文進行多次拷貝。使用者資料只拷貝了兩次:一是從使用者空間拷貝到核心;二是報文資料從核心傳送到網路介面卡。
1.1、sk_buff
套接字快取結構:
//套接字快取struct sk_buff {
/* These two members must be first. */struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;
struct sock *sk; //指向建立報文的socketstruct timeval stamp; //此報文收到時的時間struct net_device *dev; //收到此報文的網路裝置 struct net_device *input_dev;
struct net_device *real_dev;
//TCP報頭 union {
struct tcphdr *th; //tcp頭struct udphdr *uh; //udp頭struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char*raw;
} h;
//IP報頭 union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char*raw;
} nh;
//鏈路層幀頭 union {
unsigned char*raw;
} mac;
struct dst_entry *dst; //此報文的路由,路由確定後賦此值struct sec_path *sp;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/char cb[40];
//此報文的長度,這是指網路報文在不同協議層中的長度,包括頭部和資料。在協議棧的不同層,這個長度是不同的。 unsigned int len,
data_len,
mac_len,
csum;
unsigned char local_df,
cloned,
pkt_type, //網路報文的型別,常見的有PACKET_HOST,代表發給本機的報文;還有PACKET_OUTGOING,代表本機發出的報文。 ip_summed;
__u32 priority;
unsigned short protocol,//鏈路層協議 security;
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
unsigned long nfmark;
__u32 nfcache;
__u32 nfctinfo;
struct nf_conntrack *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif#endif /* CONFIG_NETFILTER */#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */#endif#endif/* These elements must be at the end, see alloc_skb() for details. *///此報文儲存區的長度,這個長度是16位元組對齊的,一般要比報文的長度大 unsigned int truesize;
atomic_t users;
/*head和end指向報文資料的整個單元.head與data之間的空間稱為headroom,tail與end之間的空間稱為tailroom.
*/
unsigned char*head,
*data,
*tail,
*end;
};
1.2、與sk_buff相關的函式
與sk_buff相關的函式涉及到網路報文儲存結構和控制結構的分配、複製、釋放,以及控制結構裡的各指標的操作,還有各種標誌的檢查。重要的函式說明如下:
struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
分配大小為size的儲存空間存放網路報文,同時分配它的控制結構。size的值是16位元組對齊的,gfp_mask是記憶體分配的優先順序。常見的記憶體分配優先順序有GFP_ATOMIC,代表分配過程不能被中斷,一般用於中斷上下文中分配記憶體;GFP_KERNEL,代表分配過程可以被中斷,相應的分配請求被放到等待佇列中。分配成功之後,因為還沒有存放具體的網路報文,所以sk_buff的 data,tail指標都指向儲存空間的起始地址,len的大小為0,而且 is_clone和cloned兩個標記的值都是0。
struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)
從控制結構skb中 clone出一個新的控制結構,它們都指向同一個網路報文。clone成功之後,將新的控制結構和原來的控制結構的 is_clone,cloned兩個標記都置位。同時還增加網路報文的引用計數(這個引用計數存放在儲存空間的結束地址的記憶體中,由函式atomic_t *skb_datarefp(struct sk_buff *skb)訪問,引用計數記錄了這個儲存空間有多少個控制結構)。由於存在多個控制結構指向同一個儲存空間的情況,所以在修改儲存空間裡面的內容時,先要確定這個儲存空間的引用計數為1,或者用下面的拷貝函式複製一個新的儲存空間,然後才可以修改它裡面的內容。
struct sk_buff *skb_copy(struct sk_buff *skb, int gfp_mask)
複製控制結構skb和它所指的儲存空間的內容。複製成功之後,新的控制結構和儲存空間與原來的控制結構和儲存空間相對獨立。所以新的控制結構裡的is_clone,cloned兩個標記都是0,而且新的儲存空間的引用計數是1。
void kfree_skb(struct sk_buff *skb)
釋放控制結構skb和它所指的儲存空間。由於一個儲存空間可以有多個控制結構,所以只有在儲存空間的引用計數為1的情況下才釋放儲存空間,一般情況下,只釋放控制結構skb。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
將tail指標下移,並增加skb的 len值。data和 tail之間的空間就是可以存放網路報文的空間。這個操作增加了可以儲存網路報文的空間,但是增加不能使tail的值大於end的值,skb的 len值大於truesize的值。
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
將data指標上移,並增加skb的 len值。這個操作在儲存空間的頭部增加了一段可以儲存網路報文的空間,上一個操作在儲存空間的尾部增加了一段可以儲存網路報文的空間。但是增加不能使data的值小於head的值,skb的 len值大於truesize的值。
unsigned char * skb_pull(struct sk_buff *skb, unsigned int len)
將data指標下移,並減小skb的 len值。這個操作使data指標指向下一層網路報文的頭部。
void skb_reserve(struct sk_buff *skb, unsigned int len)
將data指標和tail指標同時下移。這個操作在儲存空間的頭部預留 len長度的空隙。
void skb_trim(struct sk_buff *skb, unsigned int len)
將網路報文的長度縮減到 len。這個操作丟棄了網路報文尾部的填充值。
int skb_cloned(struct sk_buff *skb)
判斷skb是否是一個 clone的控制結構。如果是clone的,它的cloned標記是1,而且它指向的儲存空間的引用計數大於1。
2、 套接字快取佇列(Socket-Buffer Queues)
2.1、sk_buff_head
在網路協議棧的實現中,有時需要把許多網路報文放到一個佇列中做非同步處
理。LINUX 為此定義了相關的資料結構 sk_buff_head。這是一個雙向連結串列的
頭,它把sk_buff連結成一個雙向連結串列。
//套接字快取佇列頭struct sk_buff_head {
/* These two members must be first. */struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen; //佇列的長度,即佇列中報文的數量 spinlock_t lock;
};
2.2、與 sk_buff_head相關的函式
void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk)
將newsk加到連結串列 list的頭部。
void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
將newsk加到連結串列 list的尾部。
struct sk_buff *skb_dequeue(struct sk_buff_head *list)
從連結串列 list的頭部取下一個 sk_buff。
struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)
從連結串列 list的尾部取下一個 sk_buff。
skb_insert(struct sk_buff *old, struct sk_buff *newsk)
將newsk加到old所在的連結串列上,並且 newsk在old的前面。
void skb_append(struct sk_buff *old, struct sk_buff *newsk)
將newsk加到old所在的連結串列上,並且 newsk在old的後面。
void skb_unlink(struct sk_buff *skb)
將skb從它所在的連結串列上取下。
以上的連結串列操作都是先關中斷的。這在中斷上下文中是不需要的,所以另外有一套與上面函式同名但是有字首“__”的函式供執行在中斷上下文中的函式呼叫。
1、 套接字快取
skb由兩部分組成:
(1) 報文資料:它儲存了實際在網路中傳輸的資料;
(2) 管理資料:供核心處理報文的額外資料,這些資料構成了協議之間交換的控制資訊。
當應用程式向一個socket傳輸資料之後,該socket將建立相應的套接字快取,並將使用者資料拷貝到快取中。當報文在各協議層傳達輸的過程中,每一導的報文頭將插入到使用者資料之前。skb為報文頭申請了足夠的空間,所以避免了由於插入報文頭而對報文進行多次拷貝。使用者資料只拷貝了兩次:一是從使用者空間拷貝到核心;二是報文資料從核心傳送到網路介面卡。
1.1、sk_buff
套接字快取結構:
//套接字快取struct
/* These two members must be first. */struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;
struct sock *sk; //指向建立報文的socketstruct timeval stamp; //此報文收到時的時間struct net_device *dev; //收到此報文的網路裝置 struct net_device
struct net_device *real_dev;
//TCP報頭 union {
struct tcphdr *th; //tcp頭struct udphdr *uh; //udp頭struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char*raw;
} h;
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char*raw;
} nh;
//鏈路層幀頭 union {
unsigned char*raw;
} mac;
struct dst_entry *dst; //此報文的路由,路由確定後賦此值struct sec_path *sp;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/char cb[40];
//此報文的長度,這是指網路報文在不同協議層中的長度,包括頭部和資料。在協議棧的不同層,這個長度是不同的。 unsigned int len,
data_len,
mac_len,
csum;
unsigned char local_df,
cloned,
pkt_type, //網路報文的型別,常見的有PACKET_HOST,代表發給本機的報文;還有PACKET_OUTGOING,代表本機發出的報文。 ip_summed;
__u32 priority;
unsigned short protocol,//鏈路層協議 security;
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
unsigned long nfmark;
__u32 nfcache;
__u32 nfctinfo;
struct nf_conntrack *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif#endif /* CONFIG_NETFILTER */#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */#endif#endif/* These elements must be at the end, see alloc_skb() for details. *///此報文儲存區的長度,這個長度是16位元組對齊的,一般要比報文的長度大 unsigned int truesize;
atomic_t users;
/*head和end指向報文資料的整個單元.head與data之間的空間稱為headroom,tail與end之間的空間稱為tailroom.
*/
unsigned char*head,
*data,
*tail,
*end;
};
1.2、與sk_buff相關的函式
與sk_buff相關的函式涉及到網路報文儲存結構和控制結構的分配、複製、釋放,以及控制結構裡的各指標的操作,還有各種標誌的檢查。重要的函式說明如下:
struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
分配大小為size的儲存空間存放網路報文,同時分配它的控制結構。size的值是16位元組對齊的,gfp_mask是記憶體分配的優先順序。常見的記憶體分配優先順序有GFP_ATOMIC,代表分配過程不能被中斷,一般用於中斷上下文中分配記憶體;GFP_KERNEL,代表分配過程可以被中斷,相應的分配請求被放到等待佇列中。分配成功之後,因為還沒有存放具體的網路報文,所以sk_buff的 data,tail指標都指向儲存空間的起始地址,len的大小為0,而且 is_clone和cloned兩個標記的值都是0。
struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)
從控制結構skb中 clone出一個新的控制結構,它們都指向同一個網路報文。clone成功之後,將新的控制結構和原來的控制結構的 is_clone,cloned兩個標記都置位。同時還增加網路報文的引用計數(這個引用計數存放在儲存空間的結束地址的記憶體中,由函式atomic_t *skb_datarefp(struct sk_buff *skb)訪問,引用計數記錄了這個儲存空間有多少個控制結構)。由於存在多個控制結構指向同一個儲存空間的情況,所以在修改儲存空間裡面的內容時,先要確定這個儲存空間的引用計數為1,或者用下面的拷貝函式複製一個新的儲存空間,然後才可以修改它裡面的內容。
struct sk_buff *skb_copy(struct sk_buff *skb, int gfp_mask)
複製控制結構skb和它所指的儲存空間的內容。複製成功之後,新的控制結構和儲存空間與原來的控制結構和儲存空間相對獨立。所以新的控制結構裡的is_clone,cloned兩個標記都是0,而且新的儲存空間的引用計數是1。
void kfree_skb(struct sk_buff *skb)
釋放控制結構skb和它所指的儲存空間。由於一個儲存空間可以有多個控制結構,所以只有在儲存空間的引用計數為1的情況下才釋放儲存空間,一般情況下,只釋放控制結構skb。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
將tail指標下移,並增加skb的 len值。data和 tail之間的空間就是可以存放網路報文的空間。這個操作增加了可以儲存網路報文的空間,但是增加不能使tail的值大於end的值,skb的 len值大於truesize的值。
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
將data指標上移,並增加skb的 len值。這個操作在儲存空間的頭部增加了一段可以儲存網路報文的空間,上一個操作在儲存空間的尾部增加了一段可以儲存網路報文的空間。但是增加不能使data的值小於head的值,skb的 len值大於truesize的值。
unsigned char * skb_pull(struct sk_buff *skb, unsigned int len)
將data指標下移,並減小skb的 len值。這個操作使data指標指向下一層網路報文的頭部。
void skb_reserve(struct sk_buff *skb, unsigned int len)
將data指標和tail指標同時下移。這個操作在儲存空間的頭部預留 len長度的空隙。
void skb_trim(struct sk_buff *skb, unsigned int len)
將網路報文的長度縮減到 len。這個操作丟棄了網路報文尾部的填充值。
int skb_cloned(struct sk_buff *skb)
判斷skb是否是一個 clone的控制結構。如果是clone的,它的cloned標記是1,而且它指向的儲存空間的引用計數大於1。
2、 套接字快取佇列(Socket-Buffer Queues)
2.1、sk_buff_head
在網路協議棧的實現中,有時需要把許多網路報文放到一個佇列中做非同步處
理。LINUX 為此定義了相關的資料結構 sk_buff_head。這是一個雙向連結串列的
頭,它把sk_buff連結成一個雙向連結串列。
//套接字快取佇列頭struct sk_buff_head {
/* These two members must be first. */struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen; //佇列的長度,即佇列中報文的數量 spinlock_t lock;
};
2.2、與 sk_buff_head相關的函式
void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk)
將newsk加到連結串列 list的頭部。
void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
將newsk加到連結串列 list的尾部。
struct sk_buff *skb_dequeue(struct sk_buff_head *list)
從連結串列 list的頭部取下一個 sk_buff。
struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)
從連結串列 list的尾部取下一個 sk_buff。
skb_insert(struct sk_buff *old, struct sk_buff *newsk)
將newsk加到old所在的連結串列上,並且 newsk在old的前面。
void skb_append(struct sk_buff *old, struct sk_buff *newsk)
將newsk加到old所在的連結串列上,並且 newsk在old的後面。
void skb_unlink(struct sk_buff *skb)
將skb從它所在的連結串列上取下。
以上的連結串列操作都是先關中斷的。這在中斷上下文中是不需要的,所以另外有一套與上面函式同名但是有字首“__”的函式供執行在中斷上下文中的函式呼叫。