1. 程式人生 > >《Linux Device Drivers》 第十七章 網路驅動程式——note

《Linux Device Drivers》 第十七章 網路驅動程式——note

  • 簡介
    • 網路介面是第三類標準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);
    • 模組的解除安裝
      • 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);
  • 資料包傳輸
    • 無論何時核心要傳輸一個數據包,它都會呼叫驅動程式的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
  • 定製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函式,作用是在缺少裝置中斷時,還能對控制器做出響應