## 17.14.?多播
一個多播報文是一個會被多個主機接收的網絡報文, 但不是所有主機. 這個功能通過給一組主機分配特殊的硬件地址來獲得. 發向一個特殊地址的報文應當被那個組當中的所有主機接收. 在以太網的情況下, 一個多播地址在目的地址的第一個字節的最低位為 1, 而每個設備板在它自己的硬件地址的這一位上為 0.
處理主機組和硬件地址的技巧由應用程序和內核處理, 接口驅動不必處理這個問題.
多播報文的傳送是一個簡單問題, 因為它們看起來就如同其他的報文. 接口發送它們通過通訊媒介, 不查看目的地址. 內核必須要安排一個正確的硬件目的地址; hard_header 設備方法, 如果定義了, 不必查看它安排的數據.
內核來跟蹤在任何給定時間對哪些多播地址感興趣. 這個列表可能經常改變, 因為它是在任何給定時間和按照用戶意愿運行的應用程序的功能. 驅動的工作是接收感興趣的多播地址列表并遞交給內核任何發向這些地址的報文. 驅動如何實現多播列表是依賴于底層硬件是如何工作的. 典型地, 在多播的角度上, 硬件屬于 3 類中的 1 種:
-
不能處理多播的接口. 這樣的接口要么接收特別地發向它們的硬件地址(加上廣播報文)的報文, 要么接收每一個報文. 它們只能通過接收每一個報文來接收多播報文, 因此, 潛在地壓垮操作系統, 使用大量的"不感興趣"報文. 你不經常認為這樣的接口是有多播能力的, 驅動不會在 dev->flags 設置 IFF_MULTICAST.
點對點接口是特殊情況, 因為它們一直接收每個報文, 不進行任何硬件過濾.
-
能夠區別多播報文和其他報文(主機到主機, 或者廣播). 這些接口能夠被命令來接收每個多播報文, 讓軟件決定地址是否是主機感興趣的. 這種情況下的開銷是可接受的, 因為在一個典型網絡上的多播報文的數目是少的.
-
可以進行硬件檢測多播地址的接口. 可以傳遞一個多播地址的列表給這些接口, 這些地址的報文接收, 并忽略其他多播地址的報文. 對內核這是優化的情況, 因為它不浪費處理器時間來丟棄接口收到的"不感興趣"的報文.
內核盡力利用高級接口的能力, 通過支持第 3 種設備類型, 它是最通用的. 因此, 內核通知驅動, 在任何有效多播地址列表發生改變時, 并且它傳遞新的列表給驅動, 因此它能夠根據新的信息來更新硬件過濾器.
### 17.14.1.?多播的內核支持
對多播報文的支持有幾項組成:一個設備方法, 一個數據結構, 以及設備標識:
void (*dev->set_multicast_list)(struct net_device *dev);
設備方法, 在與設備相關的機器地址改變時調用. 它也在 dev->flags 被修改時調用, 因為一些標志(例如, IFF_PROMISC) 可能也要求你重新編程硬件過濾器. 這個方法接收一個 struct net_device 指針作為一個參數, 并返回 void. 一個對實現這個方法不感興趣的驅動可以聽任它為 NULL.
struct dev_mc_list *dev->mc_list;
所有設備相關的多播地址的列表. 這個結構的實際定義在本節的末尾介紹.
int dev->mc_count;
鏈表里的項數. 這個信息有些重復, 但是用 0 來檢查 mc_count 是檢查這個列表的有用的方法.
IFF_MULTICAST
除非驅動在 dev->flags 中設置這個標志, 接口不會被要求來處理多播報文. 然而, 內核調用驅動的 set_multicast_list 方法, 當 dev->flags 改變時, 因為多播列表可能在接口未激活時改變了.
IFF_ALLMULTI
在 dev->flags 中設置的標志, 網絡軟件來告知驅動從網絡上接收所有多播報文. 這發生在當多播路由激活時. 如果標志設置了, dev->ma_list 不該用來過濾多播報文.
IFF_PROMISC
在 dev->flags 中設置的標志, 當接口在混雜模式下. 接口應當接收每個報文, 不管 dev->ma_list.
驅動開發者需要的最后一點信息是 struct dev_mc_list 的定義, 在 <linux/netdevice.h>:
~~~
struct dev_mc_list { struct dev_mc_list *next; /* Next address in list */
__u8 dmi_addr[MAX_ADDR_LEN]; /* Hardware address */
unsigned char dmi_addrlen; /* Address length */
int dmi_users; /* Number of users */
int dmi_gusers; /* Number of groups */
};
~~~
因為多播和硬件地址是獨立于真正的報文發送, 這個結構在網絡實現中是可移植的, 每個地址由一個字符串和一個長度標識, 就像 dev->dev_addr.
### 17.14.2.?典型實現
描述 set_multicast_list 的設計的最好方法是給你看一些偽碼.
下面的函數是一個典型函數實現在一個全特性(ff)驅動中. 這個驅動是全模式的, 它控制的接口有一個復雜的硬件報文過濾器, 它能夠持有一個主機要接收的多播地址表. 表的最大尺寸是 FF_TABLE_SIZE.
所有以 ff_ 前綴的函數是給特定硬件操作的占位者:
~~~
void ff_set_multicast_list(struct net_device *dev) { struct dev_mc_list *mcptr;
if (dev->flags & IFF_PROMISC) {
ff_get_all_packets();
return;
}
/* If there's more addresses than we handle, get all multicast
packets and sort them out in software. */
if (dev->flags & IFF_ALLMULTI || dev->mc_count > FF_TABLE_SIZE) {
ff_get_all_multicast_packets();
return;
}
/* No multicast? Just get our own stuff */
if (dev->mc_count == 0) {
ff_get_only_own_packets();
return;
}
/* Store all of the multicast addresses in the hardware filter */
ff_clear_mc_list();
for (mc_ptr = dev->mc_list; mc_ptr; mc_ptr = mc_ptr->next)
ff_store_mc_address(mc_ptr->dmi_addr);
ff_get_packets_in_multicast_list();
}
~~~
這個實現可以簡化, 如果接口不能為進入報文存儲多播表在硬件過濾器中. 這種情況下, FF_TABLE_SIZE 減為 0, 并且代碼的最后 4 行不需要了.
如同前面提過的, 不能處理多播報文的接口不需要實現 set_multicast_list 方法來獲取 dev->flags 改變的通知. 這個辦法可能被稱為一個"非特性的"(nf)實現. 實現非常簡單, 如下面代碼所示:
~~~
void nf_set_multicast_list(struct net_device *dev)
{
if (dev->flags & IFF_PROMISC)
nf_get_all_packets();
else
nf_get_only_own_packets();
}
~~~
實現 IFF_PROMISC 是非常重要的, 因為不這樣用戶就不能運行 tcpdump 或任何其他網絡分析器. 如果接口運行一個點對點連接, 另一方面, 根本沒有必要實現 set_multicast_list, 因為用戶接收每個報文.
- 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. 快速參考