17.10. Socket 緩存

2018-02-24 15:50 更新

17.10.?Socket 緩存

我們現(xiàn)在已經(jīng)涵蓋到了大部分關(guān)于網(wǎng)絡(luò)接口的問(wèn)題. 還缺乏的是對(duì) sk_buff 結(jié)構(gòu)的詳細(xì)描述.這個(gè)結(jié)構(gòu)處于 Linux 內(nèi)核網(wǎng)絡(luò)子系統(tǒng)的核心, 我們現(xiàn)在介紹這個(gè)結(jié)構(gòu)的重要成員和操作它們的函數(shù).

盡管沒(méi)有嚴(yán)格要求去理解 sk_buff 的內(nèi)部, 能夠查看它的內(nèi)容的能力在你追蹤問(wèn)題和試圖優(yōu)化代碼時(shí)是有幫助的. 例如, 如果你看 loopback.c, 你會(huì)發(fā)現(xiàn)一個(gè)基于對(duì) sk_buff 內(nèi)部了解的優(yōu)化. 這里適用的通常的警告是: 如果你編寫(xiě)利用 sk_buff 結(jié)構(gòu)的知識(shí)的代碼, 你應(yīng)當(dāng)準(zhǔn)備好在以后內(nèi)核發(fā)行中它壞掉. 仍然, 有時(shí)性能優(yōu)勢(shì)值得額外的維護(hù)開(kāi)銷(xiāo).

我們這里不會(huì)描述整個(gè)結(jié)構(gòu), 只是那些在驅(qū)動(dòng)里可能用到的. 如果你想看到更多, 你可以查看 <linux/skbuff.h>, 那里定義了結(jié)構(gòu)和函數(shù)原型. 關(guān)于如何使用這些成員和函數(shù)的額外的細(xì)節(jié)可以通過(guò)搜索內(nèi)核源碼很容易獲取.

17.10.1.?重要成員變量

這里介紹的成員是驅(qū)動(dòng)可能需要存取的. 以非特別的順序列出它們.

struct net_device *dev;
接收或發(fā)送這個(gè)緩存的設(shè)備

union { / ... / } h;union { / ... / } nh;union { /... /} mac;
指向報(bào)文中包含的各級(jí)的頭的指針. union 中的某個(gè)成員都是一個(gè)不同數(shù)據(jù)結(jié)構(gòu)類(lèi)型的指針. h 含有傳輸層頭部指針(例如, struct tcphdr th); nh 包含網(wǎng)絡(luò)層頭部(例如 struct iphdr iph); 以及 mac 包含鏈路層頭部指針(例如 struct ethkr * ethernet).

如果你的驅(qū)動(dòng)需要查看 TCP 報(bào)文的源和目的地址, 可以在 skb->h.th 中找到. 看頭文件來(lái)找到全部的可以這樣存取的頭部類(lèi)型.

注意網(wǎng)絡(luò)驅(qū)動(dòng)負(fù)責(zé)設(shè)置進(jìn)入報(bào)文的 mac 指針. 這個(gè)任務(wù)正常是由 eth_type_trans 處理, 但是 非以太網(wǎng)驅(qū)動(dòng)不得不直接設(shè)置 skb->mac.raw, 如同"非以太網(wǎng)頭部"一節(jié)所示.

unsigned char head;unsigned char data;unsigned char tail;unsigned char end;
用來(lái)尋址報(bào)文中數(shù)據(jù)的指針. head 指向分配內(nèi)存的開(kāi)始, data 是有效字節(jié)的開(kāi)始(并且常常稍微比 head 大一些), tail 是有效字節(jié)的結(jié)尾, end 指向 tail 能夠到達(dá)的最大地址. 查看它的另一個(gè)方法是可用緩存空間是 skb->end - skb->head, 當(dāng)前使用的空間是 skb->tail - skb->data.

unsigned int len;unsigned int data_len;
len 是報(bào)文中全部數(shù)據(jù)的長(zhǎng)度, 而 data_len 是報(bào)文存儲(chǔ)于單個(gè)片中的部分的長(zhǎng)度. 除非使用發(fā)散/匯聚 I/O, data_len 成員的值為 0.

unsigned char ip_summed;
這個(gè)報(bào)文的校驗(yàn)和策略. 由驅(qū)動(dòng)在進(jìn)入報(bào)文上設(shè)置這個(gè)成員, 如在"報(bào)文接收"一節(jié)中描述的.

unsigned char pkt_type;
在遞送中使用的報(bào)文分類(lèi). 驅(qū)動(dòng)負(fù)責(zé)設(shè)置它為 PACKET_HOST (報(bào)文是給自己的), PACKET_OTHERHOST (不, 這個(gè)報(bào)文不是給我的), PACKET_BROADCAST, 或者 PACKET_MULTICAST. 以太網(wǎng)驅(qū)動(dòng)不顯式修改 pkt_type, 因?yàn)?eth_type_trans 為它們做.

shinfo(struct sk_buff *skb);unsigned int shinfo(skb)->nr_frags;skb_frag_t shinfo(skb)->frags;
由于性能的原因, 有些 skb 信息存儲(chǔ)于一個(gè)分開(kāi)的結(jié)構(gòu)中, 它在內(nèi)存中緊接著 skb. 這個(gè)"shared info"(這樣命名是因?yàn)樗梢栽诰W(wǎng)絡(luò)代碼中多個(gè) skb 拷貝中共享)必須通過(guò) shinfo 宏定義來(lái)存取. 這個(gè)結(jié)構(gòu)中有幾個(gè)成員, 但是大部分超出本書(shū)的范圍. 我們?cè)?發(fā)散/匯聚 I/O"一節(jié)中見(jiàn)過(guò) nr_frags 和 frags.

在結(jié)構(gòu)中剩下的成員不是特別有趣. 它們用來(lái)維護(hù)緩存列表, 來(lái)統(tǒng)計(jì) socket 擁有的緩存大小, 等等.

17.10.2.?作用于 socket 緩存的函數(shù)

使用一個(gè) sk_buff 結(jié)構(gòu)的網(wǎng)絡(luò)驅(qū)動(dòng)利用正式接口函數(shù)來(lái)操作它. 許多函數(shù)操作一個(gè) socket 緩存; 這里是最有趣的幾個(gè):

struct sk_buff alloc_skb(unsigned int len, int priority);struct sk_buff dev_alloc_skb(unsigned int len);
分配一個(gè)緩存區(qū). alloc_skb 函數(shù)分配一個(gè)緩存并且將 skb->data 和 skb->tail 都初始化成 skb->head. dev_alloc_skb 函數(shù)是使用 GFP_ATOMIC 優(yōu)先級(jí)調(diào)用 alloc_skb 的快捷方法, 并且在 skb->head 和 skb->data 之間保留了一些空間. 這個(gè)數(shù)據(jù)空間用在網(wǎng)絡(luò)層之間的優(yōu)化, 驅(qū)動(dòng)不要?jiǎng)铀?

void kfree_skb(struct sk_buff skb);void dev_kfree_skb(struct sk_buff skb);void dev_kfree_skb_irq(struct sk_buff skb);void dev_kfree_skb_any(struct sk_buff skb);
釋放緩存. kfree_skb 調(diào)用由內(nèi)核在內(nèi)部使用. 一個(gè)驅(qū)動(dòng)應(yīng)當(dāng)使用一種 dev_kfree_skb 的變體: 在非中斷上下文中使用 dev_kfree_skb, 在中斷上下文中使用 dev_kfree_skb_irq, 或者 dev_kfree_skb_any 在任何 2 種情況下.

unsigned char skb_put(struct sk_buff skb, int len);unsigned char __skb_put(struct sk_buff skb, int len);
更新 sk_buff 結(jié)構(gòu)中的 tail 和 len 成員; 它們用來(lái)增加數(shù)據(jù)到緩存的結(jié)尾, 每個(gè)函數(shù)的返回值是 skb->tail 的前一個(gè)值(換句話說(shuō), 它指向剛剛創(chuàng)建的數(shù)據(jù)空間). 驅(qū)動(dòng)可以使用返回值通過(guò)引用 memcpy(skb_put(...), data, len) 來(lái)拷貝數(shù)據(jù)或者一個(gè)等同的東東. 兩個(gè)函數(shù)的區(qū)別在于 skb_put 檢查以確認(rèn)數(shù)據(jù)適合緩存, 而 __skb_put 省略這個(gè)檢查.

unsigned char skb_push(struct sk_buff skb, int len);unsigned char __skb_push(struct sk_buff skb, int len);
遞減 skb->data 和遞增 skb->len 的函數(shù). 它們與 skb_put 相似, 除了數(shù)據(jù)是添加到報(bào)文的開(kāi)始而不是結(jié)尾. 返回值指向剛剛創(chuàng)建的數(shù)據(jù)空間. 這些函數(shù)用來(lái)在發(fā)送報(bào)文之前添加一個(gè)硬件頭部. 又一次, __skb_push 不同在它不檢查空間是否足夠.

int skb_tailroom(struct sk_buff *skb);
返回可以在緩存中放置數(shù)據(jù)的可用空間數(shù)量. 如果驅(qū)動(dòng)放了多于它能持有的數(shù)據(jù)到緩存中, 系統(tǒng)傻掉. 盡管你可能反對(duì)說(shuō)一個(gè) printk 會(huì)足夠來(lái)標(biāo)識(shí)出這個(gè)錯(cuò)誤, 內(nèi)存破壞對(duì)系統(tǒng)是非常有害的以至于開(kāi)發(fā)者決定采取確定的動(dòng)作. 實(shí)際中, 你不該需要檢查可用空間, 如果緩存被正確地分配了. 因?yàn)轵?qū)動(dòng)常常在分配緩存前獲知報(bào)文的大小, 只有一個(gè)嚴(yán)重壞掉的驅(qū)動(dòng)會(huì)在緩存中安放太多的數(shù)據(jù), 這樣出亂子就可當(dāng)作一個(gè)應(yīng)得的懲罰.

int skb_headroom(struct sk_buff *skb);
返回 data 前面的可用空間數(shù)量, 就是, 可以 "push" 給緩存多少字節(jié).

void skb_reserve(struct sk_buff *skb, int len);
遞增 data 和 tail. 這個(gè)函數(shù)可用來(lái)在填充數(shù)據(jù)前保留空間. 大部分以太網(wǎng)接口保留 2 個(gè)字節(jié)在報(bào)文的前面; 因此, IP 頭對(duì)齊到 16 字節(jié), 在 14 字節(jié)的以太網(wǎng)頭后面. snull 也這樣做, 盡管沒(méi)有在"報(bào)文接收"一節(jié)中展現(xiàn)這個(gè)指令以避免在那時(shí)引入過(guò)多概念.

unsigned char skb_pull(struct sk_buff skb, int len);
從報(bào)文的頭部去除數(shù)據(jù). 驅(qū)動(dòng)不會(huì)需要使用這個(gè)函數(shù), 但是為完整而包含在這兒. 它遞減 skb->len 和遞增 skb->data; 這是硬件頭如何從進(jìn)入報(bào)文開(kāi)始被剝離.

int skb_is_nonlinear(struct sk_buff *skb);
返回一個(gè)真值, 如果這個(gè) skb 分離為多個(gè)片為發(fā)散/匯聚 I/O.

int skb_headlen(struct sk_buff *skb);
返回 skb 的第一個(gè)片的長(zhǎng)度(由 skb->data 指著).

void kmap_skb_frag(skb_frag_t frag);void kunmap_skb_frag(void *vaddr);
如果你必須從內(nèi)核中的一個(gè)非線性 skb 直接存取片, 這些函數(shù)為你映射以及去映射它們. 使用一個(gè)原子性 kmap, 因此你不能一次映射多于一個(gè)片.

內(nèi)核定義了幾個(gè)其他的作用于 socket 緩存的函數(shù), 但是它們是打算用于高層網(wǎng)絡(luò)代碼, 驅(qū)動(dòng)不需要它們.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)