《Linux Device Drivers》 第十七章 網路驅動程式——note
阿新 • • 發佈:2018-11-13
- 簡介
- 網路介面是第三類標準Linux裝置,本章將描述網路介面是如何與核心其餘的部分互動的
- 網路介面必須使用特定的核心資料結構註冊自身,以備與外界進行資料線包交換時呼叫
- 對網路介面的常用檔案操作是沒有意義的,因此在它們身上無法體現Unix的“一切都是檔案”的思想
- 網路驅動程式非同步自外部世界的資料包
- 網路裝置向核心請求把外部獲得的資料包傳送給核心
- Linux核心中的網路子系統被設計成完全與協議無關
- 在網路世界中使用術語“octet”指一組8個的資料位,它是能為網路裝置和協議所能理解的最小單位
- 協議頭(header)是在資料包中的一系列位元組,它將通過網路子系統的不同層
- 連線到核心
- loopback.c、plip.c和e100.c
- 設備註冊
- 驅動程式對每個新檢測到的介面,向全域性的網路裝置連結串列中插入一個數據結構
- <linux/netdevice.h>
- struct net_device
- struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup) (struct net_device *));
- name是介面的名字,這個名字可以使用類似printf中%d的格式,核心將用下一個可用的介面號替代%d
- <linux/etherdevie.h>
- struct net_device *alloc_etherdev(int sizeof_priv);
- 光纖通道裝置使用alloc_fcdev(<linux/fcdevice.h>)
- FDDI裝置使用alloc_fddidev(<linux/fddidevice.h>)
- 令牌環裝置使用alloc_trdev(<linux/trdevice.h>)
- register_netdev函式
- 初始化每個裝置
- example
- ether_setup(dev);
- dev->open = open_function;
- dev->stop = release_function;
- dev->set_config = config_function;
- dev->hard_start_xmid = tx_function;
- dev->do_ioctl = ioctl_function;
- dev->get_stats = stats_function;
- dev->rebuild_header = rebuild_header_function;
- dev->hard_header = header_function;
- dev->tx_timeout = tx_timeout_function;
- dev->watchdog_timo = timeout;
- dev->flags |= IFF_NOARP;
- dev->features |= NETIF_F_NO_CSUM;
- dev->hard_header_cache = NULL;
- priv = netdev_priv(dev);
- example
- 模組的解除安裝
- unregister_netdev函式從系統中刪除介面
- free_netdev函式將net_device結構返回給系統
- net_device結構細節
- 全域性資訊
- char name[IFNAMSIZ];
- unsigned long state;
- struct net_device *next;
- int (*init) (struct net_device *dev);
- 硬體資訊
- unsigned long rmem_end;
- unsigned long rmem_start;
- unsigned long mem_end;
- unsigned long mem_start;
- unsigned long base_addr;
- unsigned char irq;
- unsigned char if_port;
- unsigned char dma;
- 介面資訊
- drivers/net/net_init.c
- void ltalk_setup(struct net_device *dev);
- void fs_setup(struct net_device *dev);
- void fddi_setup(struct net_device *dev);
- void hippi_setup(struct net_device *dev);
- void tr_setup(struct net_device *dev);
- unsigned short hard_header_len;
- 對乙太網介面,該值是14
- unsigned mtu;
- 最大傳輸單元,乙太網的MTU是1500個octet
- unsigned long tx_queue_len;
- unsigned short type;
- ARP使用type成員判斷介面所支援的硬體地址型別
- <linux/if_arp.h>
- unsigned char addr_len;
- unsigned char broadcast[MAX_ADDR_LEN];
- unsigned char dev_addr[MAX_ADDR_LEN];
- unsigned short flags;
- int features;
- 該標誌成員是一個位掩碼
- IFF_字首表示“介面標誌”,有效的標誌定義在<linux/if.h>
- 裝置方法
- 網路介面的裝置方法可劃分為兩個型別:基本的和可選的
- 基本方法
- int (*open) (struct net_device *dev);
- int (*stop) (struct net_device *dev);
- int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
- int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);
- int (*rebuild_header) (struct sk_buff *skb);
- void (*tx_timeout) (struct net_device *dev);
- struct net_device_stats *(*get_stats) (struct net_device *dev);
- int (*set_config) (struct net_device *dev, struct ifmap *map);
- 可選方法
- int (*poll) (struct net_device *dev, int *quota);
- void (*poll_controller) (struct net_device *dev);
- int (*do_ioctl) (struct net_device *dev, struct ifreq *ifr, int cmd);
- void (*set_multicast_list) (struct net_device *dev);
- int (*set_mac_address) (struct net_device *dev, void *addr);
- int (*change_mtu) (struct net_device *dev, int net_mtu);
- int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);
- int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);
- int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);
- 工具成員
- unsigned long trans_start;
- unsigned long last_rx;
- int watchdog_timeo;
- void *priv;
- struct dev_mc_list *mc_list;
- int mc_count;
- spinlock_t xmit_lock;
- int xmit_lock_owner;
- 全域性資訊
- 開啟和關閉
- 在使用ifconfig向介面賦予地址時,要執行兩個任務
- 首先,通過ioctl(SIOCSIFADDR)賦予地址
- 然後,通過ioctl(SIOCSIFFLAGS)設定dev->flag中的IFF_UP標誌以開啟介面
- 對裝置而言,無需對ioctl(SIOCSIFADDR)做任何工作,後一個命令會呼叫裝置的open方法
- 在介面被關閉時,ifconfig使用ioctl(SIOSIFFLAGS)來清除IFF_UP標誌,然後呼叫stop函式
- 此外,還要執行其他一些步驟
- 首先,在介面有夠和外界通訊之前,要將硬體地址(MAC)從硬體裝置複製到dev->dev_addr
- 應該啟動介面的傳輸佇列
- void netif_start_queue(struct net_device *dev);
- 在使用ifconfig向介面賦予地址時,要執行兩個任務
- 資料包傳輸
- 無論何時核心要傳輸一個數據包,它都會呼叫驅動程式的hard_start_transmit函式將資料放入外發佇列
- 核心處理的每個資料包位於一個套接字緩衝區結構(sk_buff)中,該結構定義在<linux/skbuff.h>中
- 傳遞經全hard_start_xmit的套接字緩衝區包含了物理資料包,並擁有完整的傳輸層資料包頭
- 該傳輸函式只執行了對資料包的一致性檢查,然後通過硬體相關的函式傳輸資料
- 如果執行成功,則hard_start_xmit返回0
- 控制併發傳輸
- 通過net_device結構中的一個自旋鎖獲得併發呼叫時的保護
- 實際的硬體介面是非同步傳輸資料包的,而且可用來儲存外發資料包的儲存空間非常有限
- void netif_wake_queu(struct net_device *dev);
- 通知網路系統可再次開始傳輸資料包
- void netif_tx_disable(struct net_device *dev);
- 禁止資料包的傳送
- 傳輸超時
- 如果當前的系統時間超過裝置的trans_start時間至少一個超時週期,網路層將最終呼叫驅動程式的tx_timeout函式
- Scatter/Gather I/O
- 在網路上為傳輸工作建立資料包的過程,包括了組裝多個數據片段的過程
- 如果負責傳送資料包的網路介面實現了分散/聚焦I/O,則資料包就不用組裝成一個大的資料包
- 分散/聚焦I/O還能用“零拷貝”的方法,把網路資料直接從使用者緩衝區內傳輸出來
- 如果在device結構中的feature成員內設定了NETIF_F_SG標誌位,核心才將分散的資料包傳遞給hard_start_xmit函式
- struct skb_frag_struct
- struct page *page;
- __u16 page_offset;
- __u16 size;
- 資料包的接收
- 從網路上接收資料要比傳輸資料複雜一點,因為必須在原子上下文中分配一個sk_buff並傳遞給上層處理
- 網路驅動程式實現了兩種模式接收資料包:中斷驅動方式和輪詢方式
- 過程
- 第一步是分配一個儲存資料包的緩衝區
- dev_alloc_skb
- 檢查dev_alloc_skb函式的返回值
- 一旦擁有一個合法的skb指標,則呼叫memcpy將資料包資料拷貝到緩衝區內
- 最後,驅動程式更新其統計計數器
- 第一步是分配一個儲存資料包的緩衝區
- 接收資料包過程中的最後一個步驟由netif_rx執行
- 中斷處理例程
- 介面在兩種可能的事件下中斷處理器
- 新資料包到達
- 外發資料包的傳輸已經完成
- 通常中斷例程通過檢查物理裝置中的狀態暫存器,以區分新資料包到達中斷和資料傳輸完畢中斷
- 傳輸結束時,統計資訊要被更新,而且要將套接字緩衝區返回全系統
- dev_kfree_skb(struct sk_buff *skb);
- dev_kfree_skb_irq(struct sk_buff *skb);
- dev_kfree_skb_any(struct sk_buff *skb);
- 介面在兩種可能的事件下中斷處理器
- 不使用接收中斷
- 為了能提高Linux在寬頻系統上的效能,網路子系統開發者建立了另外一種基於輪詢方法的介面(稱之為NAPI)
- 停止使用中斷會減輕處理器的負荷
- struct net_device的poll成員必須設定為驅動程式的輪詢函式
- 當介面通知資料到達的時候,中斷程式不能處理該資料包,相反它還要禁止接收中斷,並且告訴核心,從現在開始啟動輪詢介面
- 用netif_receive_skb函式將資料包傳遞給核心,而不是使用netif_rx
- 呼叫netif_rx_complete關閉輪詢函式
- 鏈路狀態的改變
- 大多數涉及實際的物理連線的網路技術提供載波狀態資訊,載波的存在意味著硬體功能是正常的
- void netif_carrier_off(struct net_device *dev);
- void netif_carrier_on(struct net_device *dev);
- int netif_carrier_ok(struct net_device *dev);
- 用來檢測當前的載波狀態
- 套接字緩衝區
- <linux/skbuff.h>
- 重要的成員
- struct net_device *dev
- union { /* … */ } h;
- union { /* … */ } nh;
- union { /* … */ } mac;
- unsigned char *head;
- unsigned char *data;
- unsigned char *tail;
- unsigned char *end;
- unsigned int len;
- unsigned int data_len;
- unsigned char ip_summed;
- unsigned char pkt_type;
- shinfo (struct sk_buff *skb);
- unsigned int shinfo(skb)->nr_frags;
- skb_frag_t shinfo(skb)->frags;
- 操作套接字緩衝區的函式
- struct sk_buff *alloc_skb(unsigned int len, int priority);
- struct sk_buff *dev_alloc_skb(unsigned int len);
- 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);
- unsigned char *skb_put(struct sk_buff *skb, int len);
- unsigned char *__skb_put(struct sk_buff *skb, int len);
- unsigned char *skb_push(struct sk_buff *skb, int len);
- unsigned char *__skb_push(struct sk_buff *skb, int len);
- int skb_tailroom(struct sk_buff *skb);
- int skb_headroom(struct sk_buff *skb);
- void skb_reserve(struct sk_buff *skb, int len);
- unsigned char *skb_pull(struct sk_buff *skb, int len);
- int skb_is_nonlinear(struct sk_buff *skb);
- int skb_headlen(struct sk_buff *skb);
- void *kmap_skb_frag(skb_frag_t *frag);
- void kunmap_skb_frag(void *vaddr);
- MAC地址解析
- 在乙太網中使用ARP
- ARP由核心維護,而乙太網介面不需要做任何特殊工作就能支援ARP
- 過載ARP
- 如果裝置希望使用常用的硬體頭,而不執行ARP,則需要過載預設的dev->hard_header函式
- 非乙太網頭
- 硬體頭中除目標地址之外,還包含其他一些資訊,其中最重要的是通訊協議
- drivers/net/appletalk/cops.c
- drivers/net/irda/smc_ircc.c
- drivers/net/pp_generic.c
- 在乙太網中使用ARP
- 定製ioctl命令
- 當為某個套接字使用ioctl系統呼叫時,命令號是定義在<linux/sockios.h>中的某個符號
- 函式sock_ioctl直接呼叫一個協議相關的函式
- 任何協議層不能識別的ioctl命令都會傳遞到裝置層
- 這些裝置相關的ioctl命令從使用者空間接受第三個引數,即一個struct ifreq *指標
- <linux/if.h>
- 統計資訊
- 驅動程式需要的最後一個函式是get_stats,這個函式返回裝置統計結構的指標
- struct net_device_stats
- unsigned long rx_packets;
- unsigned long tx_packets;
- unsigned long rx_bytes;
- unsigned long tx_bytes;
- unsigned long rx_errors;
- unsigned long tx_errors;
- unsigned long rx_dropped;
- unsigned long tx_dropped;
- unsigned long collisions;
- unsigned long multicast;
- 組播
- 對乙太網而言,組播地址在目標地址的第一個octet的最低位設定為1,而所有裝置板卡將自己的硬體地址的相應位清零
- 核心在任意給定時刻均要跟蹤組播地址
- 驅動程式實現組播清單的方法,在某種程式上依賴於底層硬體的工作方式
- 通常來說,考慮組播時,硬體可劃分為三類
- 不有處理組播的介面
- 能夠區分組播資料包和其他資料包的介面
- 能夠為組播地址進行硬體檢測的介面
- 對組播的核心支援
- 對組播資料包的支援由如下幾項組成:一個裝置函式、一個數據結構以及若干裝置標誌
- void (*dev_set_multicast_list) (struct net_device *dev);
- struct dev_mc_list *dev->mc_list;
- int dev->mc_count;
- <linux/netdevice.h>
- struct dev_mc_list
- struct dev_mc_list *next
- __u8 dmi_addr[MAX_ADDR_LEN];
- unsigned char dmi_addrlen;
- int dmi_users;
- int dmi_gusers;
- 其他知識點詳解
- 對介質無關介面的支援
- 介質無關介面(Media Independent Interface, MII)是一個IEEE802.3標準,它描述了乙太網收發器是如何與網路控制器連線的
- <linux/mii.h>
- int (*mdio_read) (struct net_device *dev, int phy_id, int location);
- void (*mdio_write) (struct net_device *dev, int phy_id, int location, int val);
- drivers/net/mii.c
- ethtool支援
- ethtool是為系統管理員提供的用於控制網路介面的工具
- 只有當驅動程式支援ethtool時,使用ethtool才能控制包括速度、介質型別、雙工操作、DMA設定、硬體檢驗、LAN喚醒操作在內的許多介面引數
- http://sf.net/projects/gkernel/
- <linux/ethtool.h>
- struct ethtool_ops
- Netpoll
- 它出現的目的是讓核心在網路和I/O子系統尚不能完整可用時,依然能傳送和接收資料包
- 用於網路控制檯和遠端核心除錯
- 實現netpoll的驅動程式需要實現poll_controller函式,作用是在缺少裝置中斷時,還能對控制器做出響應
- 對介質無關介面的支援