14.3 詳解net_device結構 初次研究網路驅動程式的讀者可以略過本節,因為就算不了解它,也不會妨礙你的入門學習. 逐一描述每個欄位的意義與用途,參考性質,不需強記. 此結構可分為兩大部分:開放與隱藏. 開放-此結構宣告為靜態結構,可以預先設定初值的欄位. 隱藏-剩餘欄位,主要提供核心內部的網路層使用.
14.3.1 開放欄位(1/2) char name[IFNAMESIZ] 裝置名稱 unsigned long rmem_end; unsigned long rmem_start; unsigned long mem_end; unsigned long mem_start; 裝置的公共記憶體範圍,rmem是接收區的範圍,mem是傳送區的範圍. unsigned long base_addr; 網路卡I/O基底位址 unsigned char irq; 裝置使用的中斷編號
14.3.1 開放欄位(2/2) unsigned char if_port; 若網卡有多個連接埠,此欄代表正在使用的的那個連接埠. unsigned char dma; 分配給網路裝置的DMA通道. unsigned long state; 裝置的狀態,驅程不直接操作這些旗標,而是透過一組工具函式來改變或取得. struct net_device *next; 指向全域鏈結串列裡的下一個裝置,驅程不應該接觸到. int (*init)(struct net_device *dev); 指向初始函式的指標.
14.3.2 隱藏欄位 這些欄位大致可分成三大類: 第一類 提供網路介面資訊(ifconfig). 第二類 輔助驅動程式(核心用不到). 第三類 裝置作業方法,是kernel-driver介面的一部分. 其後說明上述欄位,並非表示在此結構的實際排列順序.
14.3.2.1 界面資訊欄位(1/5) 設定網路介面的資訊性欄位 (driver/net/net_init.c) void ether_setup(struct net_device *dev); void fddi_setup(struct net_device *dev); void hippi_setup(struct net_device *dev); void ltalk_setup(struct net_device *dev); void tr_setup(struct net_device *dev); void fc_setup(struct net_device *dev); 這六類已經涵括所有能夠找到的網路技術,若有更新奇的玩意兒,就只好老老實實自己填寫下面的欄位了.
14.3.2.1 界面資訊欄位(2/5) unsigned short hard_header_len; 硬體標頭長度,計算單位是octet. 對於Ethernet值為14 unsigned mtu; 傳輸單位上限.對於Ethernet值為1500 octets unsigned long tx_queue_len; 傳輸佇列可容納的訊框長度上限. ether_setup()設為100 unsigned short type; 介面硬體型態. 提供ARP判斷介面支援何種硬體位址
14.3.2.1 界面資訊欄位(3/5) unsigned char addr_len; 硬體位址(MAC)長度. unsigned char broadcast[MAX_ADDR_LEN]; 廣播位址. unsigned char dev_addr[MAX_ADDR_LEN]; 硬體位址. 以Ethernet而言,硬體位址長度為6 octets,廣播位址是6個連續0xff, ether_setup()能正確地填寫這兩值,但MAC位址則須靠驅動程式自己填寫.
14.3.2.1 界面資訊欄位(4/5) unsigned short flags; 介面旗標.flags是一個位元遮罩,<linux/if.h>定義了一系列代表各種位元值的IFF_符號(InterFace Flags),有效的旗標包括: IFF_UP 介面運作狀態.對驅程唯讀,只有核心能變更. IFF_BROADCAST 介面是否具備廣播能力. IFF_DEBUG 除錯模式.驅程用此控制printk的囉唆程度 IFF_LOOPBACK 只有loopback介面才能設立此旗標. IFF_POINTOPOINT 點對點介面. IFF_NOARP 是否支援ARP.
14.3.2.1 界面資訊欄位(5/5) IFF_PROMISC 啟動混雜模式. IFF_MULTICAST 介面是否具備群播能力. IFF_ALLMULTI 要求介面收下所有群播封包. IFF_MASTER 負載平衡. 驅程不需理會. IFF_SLAVE 負載平衡. 驅程不需理會. IFF_PORTSEL 支援具備切換傳媒能力的網卡. IFF_AUTOMEDIA 同上,多了自動. IFF_DYNAMIC 介面的硬體位址可以被改變.撥接裝置. IFF_RUNNING 與IFF_UP相同,只為了保持BSD相容性. IFF_NOTRAILERS Linux並未使用.為了BSD相容性
14.3.2.2 網路介面的作業方法(1/4) 大致可分成基本與額外兩組,你的驅程必須提供基本作業方法,網路介面才有用.額外作業方法是屬於比較高階的功能,並不嚴格要求一定要具備. 以下為基本作業方法: 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); 負責建構硬體標頭
14.3.2.2 網路介面的作業方法(2/4) int (*rebuild_header)(struct sk_buff *skb); 負責在傳輸封包之前重建硬體標頭. 2.4改用hard_header 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); 更改介面的硬體組態.
14.3.2.2 網路介面的作業方法(3/4) 以下為額外作業方法: int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); 執行特殊的ioctl命令. void (*set_multicast_list)(struct net_device *dev); 每當裝置的群播名單或flags位元遮罩有任何變動時,就會觸發此作業方法. int (*set_mac_address)(struct net_device *dev, void *addr); 若你的網卡提供改變硬體位址的能力,則可以實作此作業方法.
14.3.2.2 網路介面的作業方法(4/4) int (*change_mtu)(struct net_device *dev, int new_mtu); 當介面MTU有所變更時,由此作業方法負責相關動作. int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh); 將ARP查詢的結果填寫到hh_cache結構. int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr); MAC位址備改變時,修正hh_cache結構裡的目標位址. int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr); 從skb取出來源位址,然後複製到haddr所指的緩衝區.
14.3.2.3 工具欄位(1/2) unsigned long trans_start; 開始傳輸的系統時刻. unsigned long last_rx; 最近一次收到完整封包的系統時刻. int watchdog_timeo; 網路子系統斷定傳輸逾期的最短時間間隔. void *priv; 地位相當於filp->private_data. struct dev_mc_list *mc_list; int mc_count; 這兩個欄位用來處理群播傳輸. mc_count是mc_list的節點個數.
14.3.2.3 工具欄位(2/2) spinlock_t xmit_lock; 保護驅程的hard_start_xmit,避免它被同時多次重複呼叫. int xmit_lock_owner; 代表目前有多少個CPU取得了xmit_lock的擁有權. struct module *owner; 擁有本網路裝置結構的模組.用來維護模組的用量計次.
開啟與關閉 驅動程式可以在模組的載入期或核心的啟動期探測硬體介面是否存在。 在介面傳輸封包之前,核心必須先開啟介面,並賦予它一個軟體位址(IP 位址) uesr-space提供IP位址核心開啟、關閉、設定網路介面的user-space程式為ifconfig Ifconfig設定軟體位址給介面時: ioctl(SIOCSIFADDR)=>設定軟體位址給介面 Ioctl(SIOCSIFILAGS)=>要求驅動程式開啟、關閉介面=>觸動open及stop作業方法…
傳送 網路介面最重要的任務資料的”傳送”與“接收”。 當核心需要送出資料封包時: 將資料排入出境封包佇列(outgoing queue) 呼叫網路介面的hard_star_transmit()作業方法 核心經手的每一封包,都是包裝成一個struct sk_buff結構(socket buffer) 指向sk_buff的指標,通常取名為skb skb->data指向即將被送出的封包 Skb->len是該封包的長度,單位是octet
傳送 避免同時傳輸 為了避免hard_start_xmit()函式同時被呼叫,核心在net_device結構裡設置了一個xmit_lock空轉鎖來保護它… 傳輸逾期 就驅動程式觀點來看,硬體超過一段不合理的時間沒回應。因此,發覺問題的方法就是設置一些計時器……
接收方式 處理封包接收方式: 1.interrupt handler 2.polling (消率差) 網路硬體中斷觸發中斷,並配置sk_buff給handler.
snull void snull_rx(struct net_device *dev, int len, unsigned char *buf) { struct sk_buff *skb; struct snull_priv *priv = (struct snull_priv *) dev->priv; /* * The packet has been retrieved from the transmission * medium. Build an skb around it, so upper layers can handle it */ skb = dev_alloc_skb(len+2); if (!skb) { printk("snull rx: low on mem - packet dropped\n"); priv->stats.rx_dropped++; return; }
skb_reserve(skb, 2); /* align IP on 16B boundary */ memcpy(skb_put(skb, len), buf, len); /* 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++; #ifndef LINUX_20 priv->stats.rx_bytes += len; #endif netif_rx(skb); return; }
網路層所需資訊: 1.dev(收到該封包的介面) 2.protocol(硬體協定): eth_type_trans()計算 3.ip_summed(查核碼):計算方式 *CHECKSUM_HW *CHECKSUM_NONE *CHECKSUM_UNNECESSARY
接收函式必須更新統計資訊,紀錄以接收的封包數量 統計資訊: 1.rx_packets:已接收封包 2.rx_bytes:已接收位元組數 3.tx_packets:已送出封包 4.tx_bytes:已送出位元組數
14.7中斷處置 中斷發生事件: 1.新封包抵達 2.封包傳送完畢 ISR須搞清楚哪種事件發生:網路卡提供狀態暫存器
snull void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs) { int statusword; struct snull_priv *priv; /* * As usual, check the "device" pointer for shared handlers. * Then assign "struct device *dev" */ struct net_device *dev = (struct net_device *)dev_id; /* ... and check with hw if it's really ours */ if (!dev /*paranoid*/ ) return; /* Lock the device */ priv = (struct snull_priv *) dev->priv; spin_lock(&priv->lock);
/* retrieve statusword: real netdevices use I/O instructions */ statusword = priv->status; if (statusword & SNULL_RX_INTR) { /* send it to snull_rx for handling */ snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata); } if (statusword & SNULL_TX_INTR) { /* a transmission is over: free the skb */ priv->stats.tx_packets++; priv->stats.tx_bytes += priv->tx_packetlen; dev_kfree_skb(priv->skb); /* Unlock the device and we are done */ spin_unlock(&priv->lock); return;
傳輸完畢後ISR需: 1.更新統計資訊 2.呼叫dev_kfree_skb()將已經沒用的 sk_buff還給系統.
14.8連線狀態的變化 大部分實體線路的連線採用“載波(carrier)”來偵測. 改變連線狀態的函式: 1.void neti_carrier_off(struct net_device *dev) 當載波消失時,透過此函式通知系統 2.void neti_carrier_on(struct net_device *dev) 當載波恢復時,透過此函式通知系統
14.9 sk_buff結構 重要欄位: struct net_device *rx_dev; 接收此暫存區封包內容的裝置 struct net_device *dev; 傳送此暫存區封包內容的裝置 union{/*…*/}h; 含有傳輸層的各種標頭的指標,ex: struct tcphdr *th
union{/*…*/}nh; 含有網路層各種標頭的指標,ex: struct iphdr *iph union{/*…*/}mac; 含有連結層各種標頭的指標,ex: struct ethdr *ethernet
unsigned char *head; 指向配置空間的起點 unsigned char *data; 指向有效資料的起點 unsigned char *tail; 指向有效資料的最後一個octet unsigned char *end; 指向配置空間的終點
資料本身的長度 (skb->tail – skb->head) Unsigned char ip_summed; Unsigned long len; 資料本身的長度 (skb->tail – skb->head) Unsigned char ip_summed; 查核碼的計算方式 Unsigned char pkt_type; 封包遞送方式的分類,有四種: PACKET_HOST :自己的封包 PACKET_BROACAST:廣播封包 PACKET_MULTICAST:群播封包 PACKET_OTHERHOST:其他人的封包
維護sk_buff的工具函式 struct sk_buff *alloc_skb(unsigned int len,int priority); struct sk_buff *dev_alloc_skb(unsigned int len); 用於配置sk_buff的兩各函式,前者較原始, 後者為了供ISR使用.
void kree_skb(struct sk_buff *skb); void dev_kfree_skb(struct sk_buff *skb); 釋放暫存區,kfree_skb()供核心內部使用, 驅動程式使用dev_kfree_skb. unsigned char *skb_put(struct sk_buff *skb, int len); unsigned *__skb_put(struct sk_buff *skb, int len);
用來更新sk_buff的tail與len的欄位,用於將 資料加到暫存器末端,兩各函式差別在於 skb_put()會檢查暫存區是否有足夠空間. unsigned char*skb_push(struct sk_buff *skb, int len); unsigned char *__skb_push(struct sk_buff *skb, int len); 兩函式用會遞減skb->data,然後遞增 skb->len,資料會被放在封包開頭處.
int skb_tailroom(struct sk_buff *skb); 檢查暫存區還剩多少空間可以放資料. int skb_headroom(struct sk_buff *skb); 傳回skb->data之前還有多少可用的記 憶空間. void skb_reserve(struct sk_buff *skb, int len); 用來遞增skb->data與skb->tail的值,在塞 資料到暫存區之前,可用此函式保留前頭空 間.
unsigned char *skb_upll(struct sk_buff *skb, int len); 抽出封包前端的資料,所以會扣減skb->len 並增加skb->data.
14.11 自訂的ioctl命令 Sock_ioctl()會直接呼叫特定協定函式 SIOCSIFADDR和SIOCSIFMAP作用在ifreq結構 Socket的ioctl認出命令後,會呼叫介面驅動程式的dev->do_ioctl do_ioctl 收到struct ifreq *指標 int(*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); do_ioctl()返回結構會傳資料到user-space
14.22 統計資訊 Struct net_device_stats裡面重要欄位 unsigned long tx_packets; unsigned long rx_packets; unsigned long tx_bytes; unsigned long rx_bytes; unsigned long tx_errors; unsigned long rx_errors; unsigned long tx_dropped; unsigned long rx_dropped; unsigned long collisions; unsigned long multicast;
14.13 群播(1/2) 傳播方式 群播位置的目的MAC第一個octec的bit0值是1 每一塊Ethernet網卡的MAC此bit是0 unicast broadcast multicast 群播位置的目的MAC第一個octec的bit0值是1 每一塊Ethernet網卡的MAC此bit是0 如何指定正確的目的地硬體位址要靠核心
14.13 群播(2/2) 網路硬體就群播能力分類: 無群播功能介面 能分辨群播封包的介面 介面硬體可自動判別群播位址
無群播功能介面 只能收自己unicast和broadcast封包,or收進每一個封包,不應該設立dev->flags的IFF_MULTICAST旗標
能分辨群播封包的介面 介面可收進每一個群播封包,再接給軟體決定是否要丟棄或是繼續處理
介面硬體可自動判別群播位址 可設定一組“可接收的”群播位址清單,只接收符合條件的群播封包,對核心來說,這種介面是最理想的,因為處理器不必浪費時間應付介面收進來的垃圾封包
核心的群播支援(1/2) void (*dev->set_multicast_list)(struct net_device *dev); dev要接收的群播位址清單有改變,或是dev->flags被修改時,會被核心呼叫 struct dev_mc_list *dev->mc_list; 串列所有dev必須接收的所有群播位址 int dev->mc_count; dev_mc_list所含的群播位址個數
核心的群播支援(2/2) IFF_ALLMULTI IFF_MULTICAST IFF_PROMISC 收進所有群播封包 代表介面是否支援群播 IFF_PROMISC 設立則進入“混雜模式”
典型set_multicast_list範例 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(dev->flags & IFF_ALLMULT || dev->mc_count > FF_TABLE_SIZE) { ff_get_all_multicast_packets(); if (dev->mc_count==0) { ff_get_only_own_packets(); return; } ff_clear_mc_list(); for (mc_ptr =dev->mc_list; mc_ptr ; mc_ptr->next) ff_store_mc_address(mc_ptr->dmi_addr); ff_get_packets_in_multicast_list(); } 如果過濾表容不下核心給的所有群播位址,則收下由軟體處理 不接收群播,只收自己的封包 將所有群播位址填入硬體過濾表