1. 程式人生 > >Linux網路裝置驅動 _驅動模型

Linux網路裝置驅動 _驅動模型

Linux素來以其強大的網路功能著名,同時, 網路裝置也作為三大裝置之一, 成為Linux驅動學習中必不可少的裝置型別, 此外, 由於歷史原因, Linux並沒有強制對網路裝置貫徹其"一切皆檔案"的思想, 網路裝置不以/dev下的裝置檔案為介面,使用者程式通過socket作為訪問硬體的介面。本文以Linux3.14.0核心為例, 討論Linux中的網路驅動模型 Linux的網路裝置並不使用檔案作為使用者程式訪問網路裝置的介面,所以/sys/dev下和/dev下並沒有相應的網路裝置檔案,在Linux中,使用者程式最終使用套接字來訪問網路裝置。

#框架

上圖就是經典的OSI 7層模型,Linux的網絡卡驅動程式處於OSI模型中的資料鏈路層,他的職責就是將上上層的協議棧傳過來的資訊通過網絡卡傳送出去,
Linux的網路驅動模型採用4層結構:

  • 協議介面層 向網路協議提供統一的資料包傳送介面,上層任何形式的協議都通過dev_queue_xmit()傳送,通過netif_rx()接收,都使用sk_buff作為資料的載體
  • 裝置介面層向協議介面層提供統一的用於描述具體網路裝置屬性和操作的結構體net_device,這個結構從整體規劃了具體操作硬體的裝置驅動功能層的結構,是裝置驅動功能層的各個函式的容器,開發網路驅動的主要工作就是編寫驅動功能層的相關函式以填充net_device資料結構的內容並將net_device註冊到核心
  • 驅動功能層的各個函式是網路裝置介面層net_device資料結構的具體成員,是驅動 網路裝置硬體完成相應動作的程式,它通過ndo_start_xmit()函式啟動傳送動作,並通過網路裝置上的中斷觸發接收操作,通過中斷或POLL機制接收
  • 裝置與媒介層 是完成資料收發的物理實體,網絡卡被裝置驅動層中的函式在物理上驅動,對於Linux系統而言,網路裝置和媒介都可以是虛擬的

第2 and 第3層是驅動開發主要關心的層次

#核心類與方法簡述 ####核心類

  • sk_buff是網路驅動框架中資訊的載體, 是網路分層模型中對資料進行層層打包以及層層解包的載體
  • net_device物件描述了一個網路裝置, **其中的struct net_device_ops *netdev_ops是操作方法集, 向上提供介面的同時也向下操作硬體
  • netdev_ops一個網路裝置的操作方法集
  • 私有資料 和其他模型框架一樣, net_device物件也提供了承載私有資料的域, 不過不是使用void *, 參見下文alloc_ethdev

####核心方法

  • **dev_queue_xmit()**是網路協議介面層向下傳送資料的介面, 核心已經實現, 不需要網路裝置驅動實現
  • **ndo_start_xmit()**是網路裝置介面層向下傳送資料的介面, 位於net_device->net_device_ops, 會被dev_queue_xmit()回撥, 需要網路驅動實現
  • **netif_rx()**是網路裝置介面層向上傳送資料的介面, 不需要網路驅動實現
  • 中斷處理函式是網路裝置媒介層收到資料後向上傳送資料的入口, 需要網路驅動實現,最後要呼叫netif_rx()

#核心類與方法詳述 ##sk_buff 套接字緩衝區是資料在多層模型中傳輸的載體,其被處理的最終結果就是網路資料包, 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指向當前資料包的首地址, 隨著各個網路層的加工而變化

##net_device 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-->計數值

下面是一些與net_device相關的核心API

####分配/釋放

//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         = &eth_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);

##netdevice_ops

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()模板 下面是一個可以借鑑的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程式碼 */
}

####ndo_open()/ndo_release()模板

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);
}

####ndo_start_xmit()模板

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);
                ...
        }
}

####timeout()模板 這個函式會在超時的時候被呼叫, 通常用來實現重發.

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 這些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