Linux素來以其強大的網絡功能著名,同時, 網絡設備也做爲三大設備之一, 成爲Linux驅動學習中必不可少的設備類型, 此外, 因爲歷史緣由, Linux並無強制對網絡設備貫徹其"一切皆文件"的思想, 網絡設備不以/dev下的設備文件爲接口,用戶程序經過socket做爲訪問硬件的接口。本文以Linux3.14.0內核爲例, 討論Linux中的網絡驅動模型
Linux的網絡設備並不使用文件做爲用戶程序訪問網絡設備的接口,因此/sys/dev下和/dev下並無相應的網絡設備文件,在Linux中,用戶程序最終使用套接字來訪問網絡設備。linux
上圖就是經典的OSI 7層模型,Linux的網卡驅動程序處於OSI模型中的數據鏈路層,他的職責就是將上上層的協議棧傳過來的信息經過網卡發送出去, Linux的網絡驅動模型採用4層結構:api
第2 and 第3層是驅動開發主要關心的層次網絡
套接字緩衝區是數據在多層模型中傳輸的載體,其被處理的最終結果就是網絡數據包, Linux巧妙的使用了移動head/tail指針的方式實現了網絡模型中每一層對數據包的加工過程。sk_buff部分定義以下數據結構
427 struct sk_buff { 428 /* These two members must be first. */ 429 struct sk_buff *next; 430 struct sk_buff *prev; 432 ktime_t tstamp; 434 struct sock *sk; 435 struct net_device *dev; 443 char cb[48] __aligned(8); 445 unsigned long _skb_refdst; 449 unsigned int len, 450 data_len; 451 __u16 mac_len, 452 hdr_len; 473 __be16 protocol; 534 __u16 inner_transport_header; 535 __u16 inner_network_header; 536 __u16 inner_mac_header; 537 __u16 transport_header; 538 __u16 network_header; 539 __u16 mac_header; 540 /* These elements must be at the end, see alloc_skb() for details. */ 541 sk_buff_data_t tail; 542 sk_buff_data_t end; 543 unsigned char *head, 544 *data; 545 unsigned int truesize; 546 atomic_t users; 547 };
struct sk_buff
--435-->對應的net_device
--449-->len有效數據長度
--451-->mac_len表示mac頭長度
--473-->protocol協議編號
--537-->transport_header指向傳輸層協議頭
--538-->network_header指向IP頭
--539-->mac_header指向以太網頭
--541-->tail指向當前數據包的尾地址, 隨着各個網絡層的加工而變化
--542-->end 指向數據緩衝的內核尾地址, 不變
--543-->head指向數據緩衝(PackertData)的內核首地址, 不變
--544-->data指向當前數據包的首地址, 隨着各個網路層的加工而變化app
net_device是設備接口層的核心, 也是編寫網絡驅動核心的對象框架
1160 struct net_device { 1167 char name[IFNAMSIZ]; 1179 unsigned long mem_end; /* shared mem end */ 1180 unsigned long mem_start; /* shared mem start */ 1181 unsigned long base_addr; /* device I/O address */ 1182 int irq; /* device IRQ number */ 1189 unsigned long state; 1190 1191 struct list_head dev_list; 1192 struct list_head napi_list; 1193 struct list_head unreg_list; 1194 struct list_head close_list; 1210 netdev_features_t features; 1212 netdev_features_t hw_features; 1214 netdev_features_t wanted_features; 1243 const struct net_device_ops *netdev_ops; 1244 const struct ethtool_ops *ethtool_ops; 1245 const struct forwarding_accel_ops *fwd_ops; 1248 const struct header_ops *header_ops; 1250 unsigned int flags; /* interface flags (a la BSD) */ 1251 unsigned int priv_flags; /* Like 'flags' but invisible to userspace. 1252 * See if.h for definitions. */ 1253 unsigned short gflags; 1254 unsigned short padded; /* How much padding added by alloc_netdev() */ 1256 unsigned char operstate; /* RFC2863 operstate */ 1257 unsigned char link_mode; /* mapping policy to operstate */ 1259 unsigned char if_port; /* Selectable AUI, TP,..*/ 1260 unsigned char dma; /* DMA channel */ 1262 unsigned int mtu; /* interface MTU value */ 1263 unsigned short type; /* interface hardware type */ 1264 unsigned short hard_header_len; /* hardware hdr length */ 1270 unsigned short needed_headroom; 1271 unsigned short needed_tailroom; 1274 unsigned char perm_addr[MAX_ADDR_LEN]; /* permanent hw address */ 1275 unsigned char addr_assign_type; /* hw address assignment type */ 1276 unsigned char addr_len; /* hardware address length */ 1289 struct kset *queues_kset; 1386 int watchdog_timeo; /* used by dev_watchdog() */ 1480 };
struct net_device
--1170-->name是網絡設備的名稱, 網絡設備被載入後會出如今ifconfig中, 好比默認的eth0就是這個
--1179-->mem_start和mem_end存儲了設備所使用的共享內存起始和結束地址
--1180-->base_addr表示網絡設備的IO基地址
--1182-->irq爲設備使用的中斷號
--1210-->用戶層能夠修改的特徵
--1212-->用戶層不能修改的特徵
--1230-->網卡的統計信息
--1243-->netdev_ops即網絡設備的操做方法集
--1244-->ethtool的方法集
--1248-->header_ops表示協議頭操做集
--1250-->用戶層能夠修改的標準
--1251-->用戶層不能修改的標準
--1254-->alloc_netdev()時加入的pad的大小
--1259-->if_port指定多端口設備使用哪個端口
--1260-->dma即分配給該設備的dma通道
--1264-->hard_header_len表示網絡設備的硬件頭長度, 在以太網設備的初始化過程當中, 該成員被賦值爲ETH_HLEN, 即14
--1263-->type是硬件類型
--1262-->mtu即MAX TRANSFER UNIT
--1270-->needed_headroom表示數據包緩衝區中須要的head_room大小
--1271-->數據緩衝區中須要的tailroom的大小
--1274-->mac地址
--1275-->硬件地址類型
--1276-->硬件地址長度
--1289-->設備所屬的kset
--1386-->計數值dom
下面是一些與net_device相關的內核APIsocket
//linux/etherdevice.h /** * 分配及初始化net_device對象() * @sizeof_priv - 私有數據大小(單位:字節數) * 返回值:失敗:NULL, 成功:net_device對象的首地址 */ struct net_device *alloc_etherdev(int sizeof_priv); //linux/netdevice.h /** * 分配及初始化net_device對象 * @int sizeof_priv - 私有數據大小(單位:字節數) * @const char *name - 物理接口名("名稱%d") * @unsigned char name_assign_type - NET_NAME_UNKNOWN * @void (*setup)(struct net_device *) - 初始化函數 * 返回值:失敗:NULL成功:net_device對象的首地址 */ struct net_device *alloc_netdev(int sizeof_priv, const char *name,unsigned char name_assign_type,void (*setup)(struct net_device *)); //釋放 void free_netdev(struct net_device *dev);
經過形參的名字就能夠看出, 這個函數其實不止分配了一個net_device對象的空間, 由於net_device中並無一個存儲私有數據的域(dev->platform_data除外), 關於net_device的私有數據的存儲方式, 咱們能夠經過這個函數的定義中看出, 這也就是要使用內核API來分配一個net_device結構的緣由函數
//include/linux/netdevice.h 2897 #define alloc_netdev(sizeof_priv, name, setup) \ 2898 alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)
//net/core/dev.c 6308 struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, 6309 void (*setup)(struct net_device *), 6310 unsigned int txqs, unsigned int rxqs) 6311{ 6330 alloc_size = sizeof(struct net_device); 6331 if (sizeof_priv) { 6332 /* ensure 32-byte alignment of private area */ 6333 alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); 6334 alloc_size += sizeof_priv; 6335 } 6336 /* ensure 32-byte alignment of whole construct */ 6337 alloc_size += NETDEV_ALIGN - 1; 6339 p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT); 6340 if (!p) 6341 p = vzalloc(alloc_size); 6342 if (!p) 6343 return NULL; 6344 6345 dev = PTR_ALIGN(p, NETDEV_ALIGN); 6346 dev->padded = (char *)dev - (char *)p; 6406 }
alloc_net_dev_wqs
--6330-->用alloc_size保存一個net_device的大小
--6333-->若是有私有數據的要求, 對alloc_size從新賦值, 新的大小爲net_device的32字節對齊大小+請求的私有數據大小
--6337-->確保分配的空間是32字節對齊, 與--6345--配合使用
--6339-->分配空間, 空間的大小=net_device大小+請求的私有數據空間大小+(NETDEV_ALIGN-1)大小 net_device的私有數據是預分配的, 就在net_device對象的下面.
--6345-->6337行的做用在這一行就體現出來了, PTR_ALIGN是向上對齊, 這樣久保證了dev指向的空間是32字節對齊的, 以前分配的多餘的部分做爲padded被添加到net_device域中,因此, 考慮到net_device自己湊巧就是32字節對齊的,最後獲得的內存佈局是"net_device+priv_data+padded"的一塊物理連續空間佈局
//以太網的初始化 void ether_setup(struct net_device *dev);
這個函數也是一個重頭, 在初始化一個以太網設備的時候應該被調用, 它的主要做用就是針對以太網標準對net_device對象進行初始化.
//net/ethernet/eth.c 359 void ether_setup(struct net_device *dev) 360 { 361 dev->header_ops = ð_header_ops; 362 dev->type = ARPHRD_ETHER; 363 dev->hard_header_len = ETH_HLEN; 364 dev->mtu = ETH_DATA_LEN; 365 dev->addr_len = ETH_ALEN; 366 dev->tx_queue_len = 1000; /* Ethernet wants good queues */ 367 dev->flags = IFF_BROADCAST|IFF_MULTICAST; 368 dev->priv_flags |= IFF_TX_SKB_SHARING; 369 370 memset(dev->broadcast, 0xFF, ETH_ALEN); 371 372 }
//註冊 int register_netdev(struct net_device *dev); //註銷 void unregister_netdev(struct net_device *dev);
1002 struct net_device_ops { 1003 int (*ndo_init)(struct net_device *dev); 1004 void (*ndo_uninit)(struct net_device *dev); 1005 int (*ndo_open)(struct net_device *dev); 1006 int (*ndo_stop)(struct net_device *dev); 1007 netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, 1008 struct net_device *dev); 1013 void (*ndo_change_rx_flags)(struct net_device *dev, 1014 int flags); 1015 void (*ndo_set_rx_mode)(struct net_device *dev); 1016 int (*ndo_set_mac_address)(struct net_device *dev, 1017 void *addr); 1018 int (*ndo_validate_addr)(struct net_device *dev); 1019 int (*ndo_do_ioctl)(struct net_device *dev, 1020 struct ifreq *ifr, int cmd); 1021 int (*ndo_set_config)(struct net_device *dev, 1022 struct ifmap *map); 1023 int (*ndo_change_mtu)(struct net_device *dev, 1024 int new_mtu); 1025 int (*ndo_neigh_setup)(struct net_device *dev, 1026 struct neigh_parms *); 1027 void (*ndo_tx_timeout) (struct net_device *dev); 1028 1148 };
struct net_device_ops
--1003-->ndo_init是初始化函數指針, 若是這個指針被設置了, 那麼在設備被註冊的時候會被調用, 用來初始化net_device對象, 至關於C++中的構造函數, 這個指針能夠爲NULL
--1004-->析構函數,回收清理
--1005-->打開設備, ifconfig xxx up 時會回調
--1006-->關閉設備, ifconfig xxx down 時會回調
--1007-->發送數據,會被dev_queue_xmit()回調
--1016-->設置mac
--1018-->檢查mac是否有效
--1019-->對網絡設備的ioctl操做
--1021-->配置接口, 用於配置讀寫參數, 讀狀態, 控制網卡等
--1023-->設置MTU值
--1027超時重發, 超時後會被回調
下面是一個能夠借鑑的ndo_init()的實現
void xxx_init(struct net_device *dev) { /* 設備的私有信息結構體 */ struct xxx_priv *priv; /* 檢查設備是否存在, 以及設備須要的硬件資源 */ xxx_hw_init(); /* 初始化以太網設備的公用成員 */ ether_setup(dev); /* 設置設備的成員函數指針 */ dev->netdev_ops->ndo_open = xxx_open; dev->netdev_ops->ndo_stop = xxx_stop; dev->netdev_ops->ndo_set_config = xxx_set_config; dev->netdev_ops->ndo_start_xmit = xxx_tx; dev->netdev_ops->ndo_do_ioctl = xxx_ioctl; dev->netdev_ops->ndo_get_stats = xxx_stats; dev->netdev_ops->ndo_change_mtu = xxx_change_mtu; dev->netdev_ops->ndo_tx_timeout = xxx_tx_timeout; dev->netdev_ops->ndo_watchdog_timeo = xxx_timeout; dev->rebuild_header = xxx_rebuild_header; dev->hard_header = xxx_header; /* 得到私有數據並將其初始化 */ priv = netdev_priv(dev); /* 初始化priv代碼 */ }
int xxx_open(struct net_device *dev) { /* 申請資源 */ ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev); /* 激活設備的發送隊列 */ netif_start_queue(dev); } init xxx_release(struct net_device *dev) { /* 釋放資源 */ free_irq(dev->irq,dev); /* 關閉設備的發送隊列 */ netif_stop_queue(dev); }
int xxx_tx(struct sk_buff *skb, struct net_device *dev) { int len; char *data, shortpkt[ETH_ZLEN]; if(xxx_send_available(...)){ //發送隊列未滿, 能夠發送 /* 得到有效數據指針和長度 */ data = skb->data; len = skb->len; if(len < ETH_ZLEN){ /* 若是幀長小於以太網幀最小長度,補0 */ memset(shortpkt,0,ETH_ZLEN); memcpy(shortpkt,skb->data,skb->len); len = ETH_ZLEN; data = shortpkt; } dev->trans_start = jiffies; //記錄發送時間戳 /* 設置硬件寄存器讓硬件將數據發出去 */ xxx_hw_tx(data,len,dev); }else{ netif_stop_queue(dev); ... } }
這個函數會在超時的時候被調用, 一般用來實現重發.
void xxx_tx_timeout(struct net_device *dev) { ... netif_wake_queue(dev); //從新啓動設備發送隊列 }
是網絡設備媒介層相設備驅動功能層發送數據的接口, 網卡接收到數據是經過中斷的方式上報的, 因此網絡驅動中的中斷處理函數就是第一時間隊接收到的數據進行處理的地方, 這個函數最終必定要調用netif_rx()將收到的數據上報到協議接口層. 下面是一個簡單的接收數據中斷處理函數模板
static void xxx_rx(struct xxx_device * dev) { ... length = get_rev_len(...); /* 分配新的套接字緩衝區 */ skb = dev_alloc_skb(length +2); skb_researve(skb, 2); //對齊 skb->dev = dev; /* 讀取硬件上接收到的數據 */ insw(ioaddr +RX_FRAME_PORT, skb_put(skb, length), length >>1); if(length &1){ skb ->data[length - 1] = inw(ioaddr + RX_FRAME_PORT); } /* 獲取上層協議類型 */ skb->protocol = eth_type_trans(skb,dev); /* 把數據包交給上層 */ netif_rx(skb); /* 記錄接收時間戳 */ dev->last_rx = jiffies; ... } static void xxx_interrupt(int irq, void *dev_id) { ... switch(status & ISQ_EVENT_MASK){ case ISQ_RECEIVER_EVENT: /* 獲取數據包 */ xxx_rx(dev); break; /* 其餘類型中斷 */ } }
這些API都在"include/linux/netdevice.h"中聲明瞭
獲得私有數據指針 void *netdev_priv(const struct net_device *dev); 設置MAC int eth_mac_addr(struct net_device *dev, void *p); 檢查MAC地址是否有效 int eth_validate_addr(struct net_device *dev); 修改MTU值 int eth_change_mtu(struct net_device *dev, int new_mtu); //隨機生成mac地址 void eth_hw_addr_random(struct net_device *dev) void eth_random_addr(u8 *addr); //開啓發送隊列 void netif_start_queue(struct net_device *dev) //中止發送隊列 void netif_stop_queue(struct net_device *dev) //runing void netif_carrier_on(struct net_device *dev) //not runing void netif_carrier_off(struct net_device *dev)