LINUX網路通訊協定棧實現分析(-) SKBUFF的實現

來源:互聯網
上載者:User

LINUX網路通訊協定棧實現分析(-)
SKBUFF的實現
本文是我嘗試分析LINUX網路通訊協定棧實現的一系列文章中的第一篇,主要分析
LINUX網路通訊協定棧中SKBUFF的實現。分析以LINUX2.2.x為基礎,同時也包
括了相同的描述對象在LINUX2.4.x中的新變化。本文引用的代碼的版本分別
是:LINUX2.2.25,LINUX2.4.20。
1 簡介
瞭解網路通訊協定棧的人都知道,網路通訊協定棧是一個有層次的軟體結構,層與層之
間通過預定的介面傳遞網路報文。網路報文中包含了在協議棧各層使用到的各
種資訊。網路報文的長度是不固定的,因此採用什麼樣的資料結構來儲存這些
網路報文就顯得非常重要。在BSD的實現中,採用的資料結構是mbuf,它所
能儲存的資料的長度是固定的,如果一個網路報文需要多個mbuf,這些mbuf
連結成一個鏈表。所以同一個網路報文裡的資料在記憶體中的儲存可能是不連續
的。在LINUX的實現中,同一個網路報文的資料在記憶體中是連續存放的,每個
網路報文都有一個控制結構,叫做sk_buff。當然,這隻是在LINUX2.2.x裡面
的情況,sk_buff在LINUX2.4.x有一點變化,將會在下面講到。
2 LINUX2.2.x中的SKBUFF
2.1 sk_buff的定義
前面提到,sk_buff是一個控制結構,通過它,才可以訪問網路報文裡的各種數
據。所以在分配網路報文儲存空間時,同時也分配它的控制結構sk_buff。在這
個控制結構裡,有指向網路報文的指標,也有描述網路報文的變數。下面是
sk_buff的定義,依次注釋如下:
struct sk_buff {
struct sk_buff * next;
struct sk_buff * prev;
struct sk_buff_head * list;
以上三個變數將sk_buff連結到一個雙向迴圈鏈表中,鏈表的結構會在後面講
到。
struct sock *sk;
此報文所屬的sock結構,此值在本機發出的報文中有效,從網路裝置收到的報
文此值為空白。
struct timeval stamp; //此報文收到時的時間
struct device *dev; //收到此報文的網路裝置
union
{
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct spxhdr *spxh;
unsigned char *raw;
} h;
union
{
PDF created with pdfFactory trial version www.pdffactory.com
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
struct ipxhdr *ipxh;
unsigned char *raw;
} nh;
union
{
struct ethhdr *ethernet;
unsigned char *raw;
} mac;
以上三個union結構依次是傳輸層,網路層,鏈路層的頭部結構指標。這些指
針在網路報文進入這一層時被賦值,其中raw是一個無結構的字元指標,用於
擴充的協議。
struct dst_entry *dst; //此報文的路由,路由確定後賦此值
char cb[48]; //用於在協議棧之間傳遞參數,參數內容的涵義由
使用它的函數確定。
unsigned int len;
此報文的長度,這是指網路報文在不同協議層中的長度,包括頭部和資料。在
協議棧的不同層,這個長度是不同的。
unsigned char is_clone,
cloned,
以上兩個變數描述此控制結構是否是clone的控制結構。一個網路報文可以對
應多個控制結構,其中只有一個是原始的結構,其他的都是clone出來的。由
於可能存在多個控制結構,所以在釋放網路報文時要確定它所有的控制結構都
已被釋放。
pkt_type,
網路報文的類型,常見的有PACKET_HOST,代表發給原生報文;還有
PACKET_OUTGOING,代表本機發出的報文。
unsigned short protocol; //鏈路層協議
unsigned int truesize; //此報文儲存區的長度,這個長度是16位元組
對齊的,一般要比報文的長度大。
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
以上四個變數指向此報文儲存區,具體的涵義後面會解釋。
__u32 fwmark; //防火牆在報文中做的標記
};
網路報文的儲存空間是在網路裝置收到網路報文或者應用程式發送資料時分配
的,分配的空間以16位元組對齊。分配成功之後,將網路報文填充到這個儲存空
間中去。填充時先在儲存空間的頭部預留了一定數量的空隙,然後將網路報文
放到剩餘的空間中去。但是網路報文不一定填滿整個儲存空間,有可能在儲存
空間的後部還有一定數量的空隙,所以sk_buff裡面的head指標指向儲存空間
的起始地址,end指標指向儲存空間的結束位址,data指標指向網路報文的起始
地址,tail指標指向網路報文的結束位址。網路報文在儲存空間裡的存放的順序
依次是:鏈路層的頭部,網路層的頭部,傳輸層的頭部,傳輸層的資料。在協
PDF created with pdfFactory trial version www.pdffactory.com
議棧的不同層,sk_buff的指標data指向這一層的網路報文的頭部。同時,在
sk_buff裡,也有相關的資料結構來表示不同層頭部資訊。sk_buff和網路報文之
間的關係:
[圖2.1 sk_buff與網路報文之間的關係]
(註:控制結構sk_buff和網路報文的儲存空間是從兩個不同的緩衝中分配的,
所以它們在記憶體中不是連續存放的。在參考資料裡也有一個關於sk_buff和網路
報文之間的關係的一個圖,但是不要誤解它們在記憶體中是連續存放的)
2.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)
PDF created with pdfFactory trial version www.pdffactory.com
釋放控制結構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.3 sk_buff_head的定義
在網路通訊協定棧的實現中,有時需要把許多網路報文放到一個隊列中做非同步處
理。LINUX 為此定義了相關的資料結構sk_buff_head。這是一個雙向鏈表的
頭,它把sk_buff連結成一個雙向鏈表,
[圖2.2 sk_buff_head與sk_buff的關係]
2.4 與 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)
PDF created with pdfFactory trial version www.pdffactory.com
將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從它所在的鏈表上取下。
以上的鏈表操作都是先關中斷的。這在中斷上下文中是不需要的,所以另外有
一套與上面函數同名但是有首碼“__”的函數供運行在中斷上下文中的函數調
用。
3 LINUX2.4.x中的SKBUFF
LINUX2.4.x中的網路報文在記憶體中不一定是連續儲存的,同一個網路報文有可
能被分成幾片存放在記憶體的不同位置,這一點與LINUX2.2.x不同(注意不要和
IP的分區混淆,IP分區是將一個網路報文分成多個網路報文,這裡是將一個網
絡報文分成幾片存放在不同的記憶體空間中)。一個大概的如下:
[3.1 LINUX2.4.x的sk_buff與網路報文之間的關係]
圖中的frags是一個數組,frag_list是一個單向鏈表。它們所指向的儲存空間是
一個頁的大小(即4k)。這些額外的儲存空間並不是一開始就使用的,只有在
data所指的儲存空間不夠用的情況下才使用這些儲存空間。以頁為單位劃分的
儲存空間有利於和使用者空間的程式共用這一塊記憶體的資料。
為了記錄網路報文的長度,在sk_buff裡增加了一個變數data_len。這個變數記
錄的是在frags和frag_list裡面儲存的報文的長度。原有的變數len記錄網路報
文的總長度。truesize是head所指的儲存區的大小。
LINUX2.2.x裡分配,複製,釋放sk_buff以及儲存區的函數在LINUX2.4.x中的
涵義沒有變化,只是在操作時增加了對frags和frag_list的分配,複製和釋放,
並且在需要的時候將分散儲存的網路報文整合成一個連續儲存的網路報文。具
體的函數可以參考原始碼。
PDF created with pdfFactory trial version www.pdffactory.com
LINUX2.4.x中對sk_buff_head的操作與LINUX2.2.x基本相同,只是多加了一
個spinlock使隊列可以在SMP的機器上更好地共用。具體地例子可以參考源代
碼,在此不做贅述。
4 小結
網路報文的儲存結構是實現網路通訊協定棧的基礎。網路報文在協議棧各層之間傳
遞,因此,如何快速地定位本層關心的資料,並盡量避免在處理時複製網路報
文成為提高協議棧效能的關鍵。本文分析了LINUX2.2.x和LINUX2.4.x中網路
報文的儲存結構,以及對儲存結構的操作。可以看到,在LINUX的協議棧實現
中,一般情況下只分配一個網路報文的儲存空間,只要不修改網路報文的內
容,不同層或不同的處理函數都是通過控制結構sk_buff來共用這個網路報文
的。只有在需要修改此報文的情況下,才複製一份。這樣即節約的儲存空間也
方便了資料的定位,使得LINUX的網路通訊協定棧的效能在應用中表現良好。
5 參考資料
1:《 TCP/IP詳解卷2:實現》, Gray R. Wright,W.Richard Stevens,機械工
業出版社
2:Kernel Korner: Network Buffers and Memory Management,
www.linuxjournal.com
3:Linux IP Networking by Glenn Herrin
4:Building Into The Linux Network Layer,phrack55
PDF created with pdfFactory trial version www.pdffactory.com

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.