## 17.6.?報文接收
從網絡上接收報文比發送它要難一些, 因為必須分配一個 sk_buff 并從一個原子性上下文中遞交給上層. 網絡驅動可以實現 2 種報文接收的模式: 中斷驅動和查詢. 大部分驅動采用中斷驅動技術, 這是我們首先要涉及的. 有些高帶寬適配卡的驅動也可能采用查詢技術; 我們在"接收中斷緩解"一節中了解這個方法.
snull 的實現將"硬件"細節從設備獨立的常規事務中分離. 因此, 函數 snull_rx 在硬件收到報文后從 snull 的"中斷"處理中調用, 并且報文現在已經在計算機的內存中. snull_rx 收到一個數據指針和報文長度; 它唯一的責任是發走這個報文和運行附加信息給上層的網絡代碼. 這個代碼獨立于獲得數據指針和長度的方式.
~~~
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;
}
~~~
這個函數足夠普通以作為任何網絡驅動的一個模板, 但是在你有信心重用這個代碼段前需要一些解釋.
第一步是分配一個緩存區來保存報文. 注意緩存分配函數 (dev_alloc_skb) 需要知道數據長度. 函數用這些信息來給緩存區分配空間. dev_alloc_skb 使用 atomic 優先級調用 kmalloc , 因此它可以在中斷時間安全使用. 內核提供了其他接口給 socket 緩存分配, 但是它們不值得在此介紹; socket 緩存在"socket 緩存"一節中詳細介紹.
當然, dev_alloc_skb 的返回值必須檢查, snull 這樣做了. 我們調用 printk_ratelimit 在抱怨失敗之前, 但是. 每秒鐘產生成百上千的控制臺消息是完全陷死系統和隱藏問題的真正源頭的好方法; printk_ratelimit 幫助阻止這個問題, 通過在有太多輸出到了控制臺時返回 0, 事情需要慢下來一點.
一旦有一個有效的 skb 指針, 通過調用 memcpy, 報文數據被拷貝到緩存區; skb_put 函數更新緩存中的數據末尾指針并返回指向新建空間的指針.
如果你在編寫一個高性能驅動, 為一個可以進行完全總線占據 I/O 的接口, 一個可能的優化值得在此考慮下. 一些驅動在報文接收前分配 sokcet 緩存, 接著使接口將報文數據直接放入 socket 緩存空間. 網絡層通過在可 DMA 的空間( 如果你的設備設置了 NETIF_F_HIGHDMA 標志, 這個空間有可能在高端內存)中分配所有 socket 緩存來配合這個策略. 這樣避免了單獨的填充 socket 緩存的拷貝操作, 但是需要小心緩存區的大小, 因為你無法提前知道進來的報文大小. change_mtu 方法的實現在這種情況下也重要, 因為它允許驅動對最大報文大小改變作出響應.
網絡層在搞懂報文的意思前需要清楚一些事情. 為此, dev 和 protocol 成員必須在緩存向上傳遞前賦值. 以太網支持代碼輸出一個幫助函數( eth_type_trans ), 它發現一個合適值來賦給 protocol. 接著我們需要指出校驗和要如何進行或者已經在報文上完成( snull 不需要做任何校驗和 ). 對于 skb->ip_summed 可能的策略有:
CHECKSUM_HW
設備已經在硬件里做了校驗. 一個硬件校驗的例子使 APARC HME 接口.
CHECKSUM_NONE
校驗和還沒被驗證, 必須由系統軟件來完成這個任務. 這個是缺省的, 在新分配的緩存中.
CHECKSUM_UNNECESSARY
不要做任何校驗. 這是 snull 和 環回接口的策略.
你可能奇怪為什么校驗和狀態必須在這里指定, 當我們已經在我們的 net_device 結構的特性成員中設置了標志. 答案是特性標志告訴內核我們的設備如何對待外出的報文. 它不用于進入的報文, 相反, 進入報文必須單獨標記.
最后, 驅動更新它的統計計數來記錄收到一個報文。 統計結構由幾個成員組成; 最重要的是 rx_packet, rx_bytes, 和 tx_bytes, 分別含有收到的報文數目, 發送的數目, 和發送的字節總數. 所有的成員在"統計信息"一節中完全描述.
報文接收的最后一步由 netif_rx 進行, 它遞交 socket 緩存給上層. 實際上 netif_rx 返回一個整數; NET_RX_SUCCESS(0) 意思是報文成功接收; 任何其他值指示錯誤. 有 3 個返回值 (NET_RX_CN_LOW, NET_RX_CN_MOD, 和 NET_RX_CN_HIGH )指出網絡子系統的遞增的擁塞級別; NET_RX_DROP 意思是報文被丟棄. 一個驅動在擁塞變高時可能使用這些值來停止輸送報文給內核, 但是, 實際上, 大部分驅動忽略從 netif_rx 的返回值. 如果你在編寫一個高帶寬設備的驅動, 并且希望正確處理擁塞, 最好的辦法是實現 NAPI, 我們在快速討論中斷處理后討論它.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考