17.6. 報文接收

2018-02-24 15:50 更新

17.6.?報文接收

從網(wǎng)絡(luò)上接收報文比發(fā)送它要難一些, 因為必須分配一個 sk_buff 并從一個原子性上下文中遞交給上層. 網(wǎng)絡(luò)驅(qū)動可以實現(xiàn) 2 種報文接收的模式: 中斷驅(qū)動和查詢. 大部分驅(qū)動采用中斷驅(qū)動技術(shù), 這是我們首先要涉及的. 有些高帶寬適配卡的驅(qū)動也可能采用查詢技術(shù); 我們在"接收中斷緩解"一節(jié)中了解這個方法.

snull 的實現(xiàn)將"硬件"細(xì)節(jié)從設(shè)備獨立的常規(guī)事務(wù)中分離. 因此, 函數(shù) snull_rx 在硬件收到報文后從 snull 的"中斷"處理中調(diào)用, 并且報文現(xiàn)在已經(jīng)在計算機的內(nèi)存中. snull_rx 收到一個數(shù)據(jù)指針和報文長度; 它唯一的責(zé)任是發(fā)走這個報文和運行附加信息給上層的網(wǎng)絡(luò)代碼. 這個代碼獨立于獲得數(shù)據(jù)指針和長度的方式.


void snull_rx(struct net_device *dev, struct snull_packet *pkt)
{
    struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);
    /*
    *
    The packet has been retrieved from the transmission

    *
    medium. Build an skb around it, so upper layers can handle it
    */
    skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) {

        if (printk_ratelimit())
            printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n"); priv->stats.rx_dropped++; goto out;
    }
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
    skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += pkt->datalen;
    netif_rx(skb);
out:
    return;
}

這個函數(shù)足夠普通以作為任何網(wǎng)絡(luò)驅(qū)動的一個模板, 但是在你有信心重用這個代碼段前需要一些解釋.

第一步是分配一個緩存區(qū)來保存報文. 注意緩存分配函數(shù) (dev_alloc_skb) 需要知道數(shù)據(jù)長度. 函數(shù)用這些信息來給緩存區(qū)分配空間. dev_alloc_skb 使用 atomic 優(yōu)先級調(diào)用 kmalloc , 因此它可以在中斷時間安全使用. 內(nèi)核提供了其他接口給 socket 緩存分配, 但是它們不值得在此介紹; socket 緩存在"socket 緩存"一節(jié)中詳細(xì)介紹.

當(dāng)然, dev_alloc_skb 的返回值必須檢查, snull 這樣做了. 我們調(diào)用 printk_ratelimit 在抱怨失敗之前, 但是. 每秒鐘產(chǎn)生成百上千的控制臺消息是完全陷死系統(tǒng)和隱藏問題的真正源頭的好方法; printk_ratelimit 幫助阻止這個問題, 通過在有太多輸出到了控制臺時返回 0, 事情需要慢下來一點.

一旦有一個有效的 skb 指針, 通過調(diào)用 memcpy, 報文數(shù)據(jù)被拷貝到緩存區(qū); skb_put 函數(shù)更新緩存中的數(shù)據(jù)末尾指針并返回指向新建空間的指針.

如果你在編寫一個高性能驅(qū)動, 為一個可以進(jìn)行完全總線占據(jù) I/O 的接口, 一個可能的優(yōu)化值得在此考慮下. 一些驅(qū)動在報文接收前分配 sokcet 緩存, 接著使接口將報文數(shù)據(jù)直接放入 socket 緩存空間. 網(wǎng)絡(luò)層通過在可 DMA 的空間( 如果你的設(shè)備設(shè)置了 NETIF_F_HIGHDMA 標(biāo)志, 這個空間有可能在高端內(nèi)存)中分配所有 socket 緩存來配合這個策略. 這樣避免了單獨的填充 socket 緩存的拷貝操作, 但是需要小心緩存區(qū)的大小, 因為你無法提前知道進(jìn)來的報文大小. change_mtu 方法的實現(xiàn)在這種情況下也重要, 因為它允許驅(qū)動對最大報文大小改變作出響應(yīng).

網(wǎng)絡(luò)層在搞懂報文的意思前需要清楚一些事情. 為此, dev 和 protocol 成員必須在緩存向上傳遞前賦值. 以太網(wǎng)支持代碼輸出一個幫助函數(shù)( eth_type_trans ), 它發(fā)現(xiàn)一個合適值來賦給 protocol. 接著我們需要指出校驗和要如何進(jìn)行或者已經(jīng)在報文上完成( snull 不需要做任何校驗和 ). 對于 skb->ip_summed 可能的策略有:

CHECKSUM_HW
設(shè)備已經(jīng)在硬件里做了校驗. 一個硬件校驗的例子使 APARC HME 接口.

CHECKSUM_NONE
校驗和還沒被驗證, 必須由系統(tǒng)軟件來完成這個任務(wù). 這個是缺省的, 在新分配的緩存中.

CHECKSUM_UNNECESSARY
不要做任何校驗. 這是 snull 和 環(huán)回接口的策略.

你可能奇怪為什么校驗和狀態(tài)必須在這里指定, 當(dāng)我們已經(jīng)在我們的 net_device 結(jié)構(gòu)的特性成員中設(shè)置了標(biāo)志. 答案是特性標(biāo)志告訴內(nèi)核我們的設(shè)備如何對待外出的報文. 它不用于進(jìn)入的報文, 相反, 進(jìn)入報文必須單獨標(biāo)記.

最后, 驅(qū)動更新它的統(tǒng)計計數(shù)來記錄收到一個報文。 統(tǒng)計結(jié)構(gòu)由幾個成員組成; 最重要的是 rx_packet, rx_bytes, 和 tx_bytes, 分別含有收到的報文數(shù)目, 發(fā)送的數(shù)目, 和發(fā)送的字節(jié)總數(shù). 所有的成員在"統(tǒng)計信息"一節(jié)中完全描述.

報文接收的最后一步由 netif_rx 進(jìn)行, 它遞交 socket 緩存給上層. 實際上 netif_rx 返回一個整數(shù); NET_RX_SUCCESS(0) 意思是報文成功接收; 任何其他值指示錯誤. 有 3 個返回值 (NET_RX_CN_LOW, NET_RX_CN_MOD, 和 NET_RX_CN_HIGH )指出網(wǎng)絡(luò)子系統(tǒng)的遞增的擁塞級別; NET_RX_DROP 意思是報文被丟棄. 一個驅(qū)動在擁塞變高時可能使用這些值來停止輸送報文給內(nèi)核, 但是, 實際上, 大部分驅(qū)動忽略從 netif_rx 的返回值. 如果你在編寫一個高帶寬設(shè)備的驅(qū)動, 并且希望正確處理擁塞, 最好的辦法是實現(xiàn) NAPI, 我們在快速討論中斷處理后討論它.

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號