Linux网络协议栈——网络设备

网络设备(network device)是内核对网络适配器(硬件)的抽象与封装,并为各个协议实例提供统一的接口,它是硬件与内核的接口,它有两个特征:
(1)    作为基于硬件的网络适配器与基于软件的协议之间的接口;
(2)    内核协议栈异步输入输出点。
记住:网络设备软件对硬件的抽象
网络设备与协议和网络适配器的关系如下:

1、    net_device接口(net_device Interface)
     网络设备是内核中除了字符设备、块设备之外第三类主要设备,它的主要特征之一就是在设备文件系统/dev/没有相应的表示,即不存在/dev/eth0等,这就意味着不能通过简单的读写操作来访问它们。
    net_device结构保存与网络设备相关的所有信息。每一个网络设备都对应一个这样的结构,包括真实设备(例如以太网卡)和虚拟设备(比如 bonding 或 VLAN)。
所有设备的 net_device 结构都放在一个全局链表中,链表的头指针是 dev_base。net_device结构的定义在include/linux/netdevice.h中。与 sk_buff 类似,net_device 结构比较大,而且包含了很多特性相关的参数,这些参数在不同的协议层中使用。出于这个原因,net_device 结构的组织会有一些改变,用于优化协议栈的性能。 网络设备可以分为不同的类型,比如以太网卡和令牌环网卡。net_device 结构中的某些变量对同一类型的设备来说, 取值是相同的; 而某些变量在同一设备的不同工作模式下,取值必须不同。因此,对几乎所有类型的设备,linux内核提供了一个通用的函数用于初始化那些在所有模式下取值相同的变量。每一个设备驱动在调用这个函数的同时,还初始化那些在当前模式下取值不同的变量。设备驱动同样可以覆盖那些由内核初始化的变量(例如,在优化设备性能时)。
  net_device的定义:

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//include/linux/netdevice.h
/*

 *    The DEVICE structure.
 *    Actually, this whole structure is a big mistake.  It mixes I/O
 *    data with strictly "high-level" data, and it has to know about
 *    almost every data structure used in the INET module.
 *
 *    FIXME: cleanup struct net_device such that network protocol info
 *    moves out.
 
*/

struct net_device
{

    
/*
     * This is the first field of the "visible" part of this structure
     * (i.e. as seen by users in the "Space.c" file).  It is the name
     * the interface.
     
*/
    
char            name[IFNAMSIZ];//网络设备名称

    
/*
     *    I/O specific fields
     *    FIXME: Merge these and struct ifmap into one
     
*/
    unsigned 
long        mem_end;    /* shared mem end    */
    unsigned 
long        mem_start;    /* shared mem start    */
    unsigned 
long        base_addr;    /* device I/O address    */
    unsigned 
int        irq;        /* device IRQ number    */

    
/*
     *    Some hardware also needs these fields, but they are not
     *    part of the usual set specified in Space.c.
     
*/

    unsigned 
char        if_port;    /* Selectable AUI, TP,..*/
    unsigned 
char        dma;        /* DMA channel        */

    unsigned 
long        state;

    
struct net_device    *next;
    
    
/* The device initialization function. Called only once. */
    
int            (*init)(struct net_device *dev);

    
/* ------- Fields preinitialized in Space.c finish here ------- */

    
struct net_device    *next_sched;

    
/* Interface index. Unique device identifier    */
    
int            ifindex;
    
int            iflink;


    
struct net_device_stats* (*get_stats)(struct net_device *dev);
    
struct iw_statistics*    (*get_wireless_stats)(struct net_device *dev);

    
/* List of functions to handle Wireless Extensions (instead of ioctl).
     * See <net/iw_handler.h> for details. Jean II 
*/
    
const struct iw_handler_def *    wireless_handlers;
    
/* Instance data managed by the core of Wireless Extensions. */
    
struct iw_public_data *    wireless_data;

    
struct ethtool_ops *ethtool_ops;

    
/*
     * This marks the end of the "visible" part of the structure. All
     * fields hereafter are internal to the system, and may change at
     * will (read: may be cleaned up at will).
     
*/

    
/* These may be needed for future network-power-down code. */
    unsigned 
long        trans_start;    /* Time (in jiffies) of last Tx    */
    unsigned 
long        last_rx;    /* Time of last Rx    */

    unsigned 
short        flags;    /* interface flags (a la BSD)    */
    unsigned 
short        gflags;
        unsigned 
short          priv_flags; /* Like 'flags' but invisible to userspace. */
        unsigned 
short          unused_alignment_fixer; /* Because we need priv_flags,
                                                         * and we want to be 32-bit aligned.
                                                         
*/

    unsigned        mtu;    
/* interface MTU value        */
    unsigned 
short        type;    /* interface hardware type    */
    unsigned 
short        hard_header_len;    /* hardware hdr length    */
    
void            *priv;    /* pointer to private data    */

    
struct net_device    *master; /* Pointer to master device of a group,
                      * which this device is member of.
                      
*/

    
/* Interface address info. */
    unsigned 
char        broadcast[MAX_ADDR_LEN];    /* hw bcast add    */
    unsigned 
char        dev_addr[MAX_ADDR_LEN];    /* hw address    */
    unsigned 
char        addr_len;    /* hardware address length    */
    unsigned 
short          dev_id;        /* for shared network cards */

    
struct dev_mc_list    *mc_list;    /* Multicast mac addresses    */
    
int            mc_count;    /* Number of installed mcasts    */
    
int            promiscuity;
    
int            allmulti;

    
int            watchdog_timeo;
    
struct timer_list    watchdog_timer;

    
/* Protocol specific pointers */
    
    
void             *atalk_ptr;    /* AppleTalk link     */
    
void            *ip_ptr;    /* IPv4 specific data    */  
    
void                    *dn_ptr;        /* DECnet specific data */
    
void                    *ip6_ptr;       /* IPv6 specific data */
    
void            *ec_ptr;    /* Econet specific data    */
    
void            *ax25_ptr;    /* AX.25 specific data */

    
struct list_head    poll_list;    /* Link to poll list    */
    
int            quota;
    
int            weight;

    
struct Qdisc        *qdisc;
    
struct Qdisc        *qdisc_sleeping;
    
struct Qdisc        *qdisc_ingress;
    
struct list_head    qdisc_list;
    unsigned 
long        tx_queue_len;    /* Max frames per queue allowed */

    
/* ingress path synchronizer */
    spinlock_t        ingress_lock;
    
/* hard_start_xmit synchronizer */
    spinlock_t        xmit_lock;
    
/* cpu id of processor entered to hard_start_xmit or -1,
       if nobody entered there.
     
*/
    
int            xmit_lock_owner;
    
/* device queue lock */
    spinlock_t        queue_lock;
    
/* Number of references to this device */
    atomic_t        refcnt;
    
/* delayed register/unregister */
    
struct list_head    todo_list;
    
/* device name hash chain */
    
struct hlist_node    name_hlist;
    
/* device index hash chain */
    
struct hlist_node    index_hlist;

    
/* register/unregister state machine */
    
enum { NETREG_UNINITIALIZED=0,
           NETREG_REGISTERING,    
/* called register_netdevice */
           NETREG_REGISTERED,    
/* completed register todo */
           NETREG_UNREGISTERING,    
/* called unregister_netdevice */
           NETREG_UNREGISTERED,    
/* completed unregister todo */
           NETREG_RELEASED,        
/* called free_netdev */
    } reg_state;

    
/* Net device features */
    
int            features;
#define NETIF_F_SG        1    /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM        2    /* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM        4    /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM        8    /* Can checksum all the packets. */
#define NETIF_F_HIGHDMA        32    /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST    64    /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX    128    /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX    256    /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER    512    /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED    1024    /* Device cannot handle VLAN packets */
#define NETIF_F_TSO        2048    /* Can offload TCP/IP segmentation */
#define NETIF_F_LLTX        4096    /* LockLess TX */

    
/* Called after device is detached from network. */
    
void            (*uninit)(struct net_device *dev);
    
/* Called after last user reference disappears. */
    
void            (*destructor)(struct net_device *dev);

    
/* Pointers to interface service routines.    */
    
int            (*open)(struct net_device *dev);
    
int            (*stop)(struct net_device *dev);
    
int            (*hard_start_xmit) (struct sk_buff *skb,
                            
struct net_device *dev);
#define HAVE_NETDEV_POLL
    
int            (*poll) (struct net_device *dev, int *quota);
    
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);
#define HAVE_MULTICAST             
    
void            (*set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR           
    
int            (*set_mac_address)(struct net_device *dev,
                           
void *addr);
#define HAVE_PRIVATE_IOCTL
    
int            (*do_ioctl)(struct net_device *dev,
                        
struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
    
int            (*set_config)(struct net_device *dev,
                          
struct ifmap *map);
#define HAVE_HEADER_CACHE
    
int            (*hard_header_cache)(struct neighbour *neigh,
                             
struct hh_cache *hh);
    
void            (*header_cache_update)(struct hh_cache *hh,
                               
struct net_device *dev,
                               unsigned 
char *  haddr);
#define HAVE_CHANGE_MTU
    
int            (*change_mtu)(struct net_device *dev, int new_mtu);

#define HAVE_TX_TIMEOUT
    
void            (*tx_timeout) (struct net_device *dev);

    
void            (*vlan_rx_register)(struct net_device *dev,
                            
struct vlan_group *grp);
    
void            (*vlan_rx_add_vid)(struct net_device *dev,
                           unsigned 
short vid);
    
void            (*vlan_rx_kill_vid)(struct net_device *dev,
                            unsigned 
short vid);

    
int            (*hard_header_parse)(struct sk_buff *skb,
                             unsigned 
char *haddr);
    
int            (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
    
int            (*accept_fastpath)(struct net_device *struct dst_entry*);
#ifdef CONFIG_NETPOLL
    
int            netpoll_rx;
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
    
void                    (*poll_controller)(struct net_device *dev);
#endif

    
/* bridge stuff */
    
struct net_bridge_port    *br_port;

#ifdef CONFIG_NET_DIVERT
    
/* this will get initialized at each interface type init routine */
    
struct divert_blk    *divert;
#endif /* CONFIG_NET_DIVERT */

    
/* class/net/name entry */
    
struct class_device    class_dev;
    
/* how much padding had been added by alloc_netdev() */
    
int padded;
};

    net_device结构主要分为以下几部分:
1.1、    通用字段
name:
网络适配器的名称,比如eth0。在注册网络设备时可以为设备分配一个名称,便必须唯一。
next:
所有的网络设备组成一个由dev_base开头的链表。
int ifindex :
全局唯一的设备ID。在每个设备注册时,调用dev_new_index 生成。
int iflink:
这个变量主要被(虚拟)隧道设备使用,用于标识隧道的真实设备。
state:
它包含一组被网络队列子系统使用的标记。这些标记的值是枚举类型netdev_state_t中的索引值,这个类型的定义在 include/linux/netdevice.h 中,每个标记都是诸如__LINK_STATE_XOFF 这样的常量。每一位都可以通过函数 set_bit 和 clear_bit 设置或清除,但通常情况下,都会有一个包装函数来隐藏标记位的信息。例如,在网络队列子系统停止一个设备队列时,它调用函数 netif_stop_queue,这个函数的定义如下:
  static inline void netif_stop_queue(struct net_device *dev)
{
        ...
        set_bit(_ _LINK_STATE_XOFF, &dev->state);
}
trans_start:
最后一个帧开始发送的时间(用jiffies度量)。设备驱动在发送之前设置这个变量。这个变量用来检测网卡是否在给定的时间内把帧发送了出去。 太长的发送时间意味
着有错误发生,在这种情况下,设备驱动会重置网卡。
last_rx :
接收到最后一个包的时间(用jiffies度量)。
xmit_lock 和xmit_lock_owner :
xmit_lock 用来序列化对设备驱动函数hard_start_xmit的调用。这意味着,每个cpu每次只能调用设备完成一次发送。xmit_lock_owner 是拥有锁的 CPU 的 ID。在单cpu 系统上,这个值是 0;在多 cpu 系统中,如果锁没有被占用,这个值是-1。内核同样允许不加锁的发送,前提条件是设备驱动必须支持这个功能。
struct hlist_node name_hlist
struct hlist_node index_hlist
  把net_device结构链接到两个哈希表中。
1.2、    硬件相关
unsigned int irq
  设备中断号。它可以被多个设备共享。设备驱动调用request_irq来分配这个值,并
调用free_irq来释放它。
unsigned char if_port
  接口的端口类型。有些设备可以支持多种接口(最常见的组合是 BNC+RJ45),用户可以根据需要来选择使用哪种接口。这个变量用来设置设备的接口类型。如果配置命令没有指定设备的接口类型,设备驱动就使用缺省的类型。在某些情况下,一个设备驱动可以处理多种接口类型;在这种情况下,设备驱动可以按一定的顺序来测试每个接口的类型。下面的代码片断展示了一个设备驱动如何根据配置来设置接口的类型:
  switch (dev->if_port) {
                case    IF_PORT_10BASE2:
                       writeb((readb(addr) & 0xf8) | 1, addr);
                        break;
                case    IF_PORT_10BASET:
                       writeb((readb(addr) & 0xf8), addr);
                        break;
                }
unsigned char dma
  设备所使用的 DMA 通道。为获取和释放一个 DMA 通道,内核在 kernel/dma.c 中定义了两个函数request_dma和free_dma。为了在获取dma通道后,启用或者停止dma通道,内核定义了两个函数enable_dma 和disable_dma。这两个函数的实现与
体系结构相关,所以在 include/asm-architecture 下有相关的文件(例如include/asm-i386)。这些函数被 ISA 设备使用;PCI 设备不使用这些函数,它们使
用其他函数。并不是所有的设备都可以使用dma,因为有些总线不支持dma。
 unsigned long mem_start
unsigned long mem_end
  这两个变量描述设备与内核通信所用到的内存边界。它们由设备驱动初始化,并且只能被设备驱动访问;高层协议不需要关心这块内存。
unsigned long base_addr
  映射到设备内存空间中I/O 内存起始地址。
1.3、    物理层相关
unsigned mtu
  MTU 的意思是最大传输单元,它表示设备可以处理帧的最大长度。不同设备的MTU值:

unsigned short type
    设备类型(以太网,帧中继等)。在include/linux/if_arp.h 中有完整的类型列表。
unsigned short hard_header_len
  以字节为单位的帧头部长度。例如,以太网帧的头是 14 字节。某种设备所支持帧的头部长度在相应的设备头文件中定义。对以太网来说,ETH_HLEN 在
<include/linux/if_ether.h>中定义。
unsigned char broadcast[MAX_ADDR_LEN]
  链路层广播地址。
unsigned char dev_addr[MAX_ADDR_LEN]
unsigned char addr_len
  dev_addr是设备的链路层地址,不要把它和IP 地址或者L3 地址混淆了。链路层地址的长度是 addr_len,以字节为单位。addr_len 的大小与设备类型有关。以太网地址的长度是8。
int promiscuity
promiscuity 计数器来标识设备是否工作在混杂模式。之所以使用计数器而不是一个标志位的原因是:可能有多个用户程序设置设备工作在混杂模式下。因此,每次进入混杂模式,计数器加一;退出混杂模式,计数器减一。只有计数器为0 时,设备才退出混杂模式。这个变量通常调用函数dev_set_promiscuity 来设置。
struct dev_mc_list *mc_list
  指向dev_mc_list结构
int mc_count
  设备多播地址的数量,它同样表示mc_list所指向链表的长度。
int allmulti
  如果是非零值,那么设备将监听所有的多播地址。和 promiscuity 一样,这个变量是一个计数器而不仅仅是一个布尔值。这是因为多个设备(比如VLAN和bonding
设备)可能独立地要求监听所有地址。如果这个变量的值从0变为非零,内核会调用函数dev_set_allmulti通知设备监听所有的多播地址。如果这个值变为0,则停止监听所有的多播地址。
1.4、    协议相关
void *atalk_ptr
void *ip_ptr
void *dn_ptr
void *ip6_ptr
void *ec_ptr
void *ax25_ptr
这六个变量指向特定协议的数据结构,每个数据结构都包含协议私有的参数。例如,ip_ptr 指向一个 in_device 类型的结构(尽管 ip_ptr 的类型是 void*),它包含 IPv4相关的参数,其中包括设备的 IP 地址列表等。
1.5、    流量管理
Linux 流量控制子系统的功能已经非常强大,并且已经成为 Linux 内核中的一个重要组件。相关的内核选项是 “Device drivers ->Networking support ->Networking options ->QoS and/or fair queueing”。net_device中的相关变量包括:
struct net_device *next_sched
  被内核软中断使用。
struct Qdisc *qdisc
struct Qdisc *qdisc_sleeping
struct Qdisc *qdisc_ingress
struct list_head qdisc_list
  这些变量管理设备的接收,发送队列,并且可以被不同的cpu访问。
spinlock_t queue_lock
spinlock_t ingress_lock
  流量控制子系统为每个网络设备定义了一个私有的发送队列。 queue_lock用于避免并发的访问(参见第11章)。ingress_lock 用于保护接收队列。
unsigned long tx_queue_len
  设备发送队列的长度。如果内核中包含了流量控制子系统,这个变量可能没有什么用(只有几个排队策略会使用它)。常见设备的 tx_queue_len 值(这个值可以通过sysfs文件系统修改(在/sys/class/net/device_name/目录下)):

1.6、    设备驱动程序相关
int (*init)(...)
void (*uninit)(...)
void (*destructor)(...)
int (*open)(...)
int (*stop)(...)
用于初始化,清除,销毁,启用和停止一个设备。这些函数并不是每个设备都会用到。
int (*hard_start_xmit)(...)
 发送一个帧。
int (*hard_header)(...)
 根据源和目标的第2层地址创建第2层报文头。
int (*rebuild_header)(...)
 负责在传送包之前重建第2导报文头。
int (*set_mac_address)(...)
  修改设备的 MAC 地址。如果设备不提供这个功能(比如网桥设备),可以把这个指针设置为NULL。
int (*change_mtu)(...)
  修改设备的MTU,修改mtu 不会对设备驱动有任何影响,它只是让协议栈软件可以根据新的mtu 正确地处理分片。



2.1、网络设备的注册与注销
注册网络设备发生在下列情形:
(1)加载网卡驱动程序
  网卡驱动程序如果被编译进内核,则它在启动时被初始化,在运行时被作为模块加载。无论初始化是否发生,所以由驱动程序控制的网卡都被注册。
(2)插入可热拔插网络设备
  当用户插入一块热拔插网卡,内核通知其对应的驱动程序以注册设备。(为了简单化,我们假定设备驱动程序已经被加载)。

两个主要的情形会导致设备注销:
(1)卸载网卡驱动程序
  这只适用与驱动程序作为模块被加载的情形,当然不适于编译进内核的情况。当管理员卸载网卡设备驱动程序时,所有相关网卡的驱动程序都被注销。
(2)移除热拔插网络设备
  当用户从正在运行且内核支持热拔插功能的系统中移除热拔插网卡时,网络设备被注销。

2.1.1、分配 net_device结构空间
在内核中,网络设备由结构net_device表示,在注册网络设备之前,必须为之分配内存空间,这一任务由net/core/dev.c中定义的alloc_netdev函数来完成:

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//net/core/dev.c
/*
分配内存空间
 ** sizeof_priv:私有数据结构大小
 ** name:网络设备的名称
 ** setup:配置函数
*
*/
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
        
void (*setup)(struct net_device *))
{
    
void *p;
    
struct net_device *dev;
    
int alloc_size;

    
/* ensure 32-byte alignment of both the device and private area */
    alloc_size 
= (sizeof(*dev) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;
    alloc_size 
+= sizeof_priv + NETDEV_ALIGN_CONST;

    p 
= kmalloc(alloc_size, GFP_KERNEL);//分配内存
    if (!p) {
        printk(KERN_ERR 
"alloc_dev: Unable to allocate device./n");
        
return NULL;
    }
    memset(p, 
0, alloc_size);

    dev 
= (struct net_device *)
        (((
long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
    dev
->padded = (char *)dev - (char *)p;

    
if (sizeof_priv)
        dev
->priv = netdev_priv(dev);//设备私有数据块

    setup(dev);
//调用初始化函数
    strcpy(dev->name, name); //设置网络设备名称
    return dev;
}

内核也提供了一些封装alloc_netdev功能的函数,如下:

alloc_etherdev函数用于以太网设备,所以它创建以字符串eth后跟唯一数字形式的设备名;第二点,它指派ether_setup作为配置函数,对于所有以太网卡来说,配置函数均把net_device结构的部分域初始化为公用值。

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//net/ethernet/eth.c
struct net_device *alloc_etherdev(int sizeof_priv)
{
    
return alloc_netdev(sizeof_priv, "eth%d", ether_setup);
}

//以太网配置函数
void ether_setup(struct net_device *dev)
{
    dev
->change_mtu        = eth_change_mtu;
    dev
->hard_header    = eth_header;
    dev
->rebuild_header     = eth_rebuild_header;
    dev
->set_mac_address     = eth_mac_addr;
    dev
->hard_header_cache    = eth_header_cache;
    dev
->header_cache_update= eth_header_cache_update;
    dev
->hard_header_parse    = eth_header_parse;

    dev
->type        = ARPHRD_ETHER; //设备类类型(==1)
    dev->hard_header_len     = ETH_HLEN;  //以字节为单位的帧头部长度,14个字节
    dev->mtu        = 1500/* eth_mtu */
    dev
->addr_len        = ETH_ALEN;//链路层地址的长度,6个字节
    dev->tx_queue_len    = 1000;    /* Ethernet wants good queues */    
    dev
->flags        = IFF_BROADCAST|IFF_MULTICAST; //支持广播和多播
    
    memset(dev
->broadcast,0xFF, ETH_ALEN);
}

2.1.2、注册网络设备
网络设备注册(a)与注销模型(b):

为了简单,来看看回环设备的注册:

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//drivers/net/loopback.c
int __init loopback_init(void)
{
    
struct net_device_stats *stats; 

    
/* Can survive without statistics */
    stats 
= kmalloc(sizeof(struct net_device_stats), GFP_KERNEL);
    
if (stats) { //分配内存成功
        memset(stats, 0sizeof(struct net_device_stats));
        loopback_dev.priv 
= stats;  //私有数据块
        loopback_dev.get_stats = &get_stats;
    }
    
    
return register_netdev(&loopback_dev); //注册回环网络设备
};

EXPORT_SYMBOL(loopback_dev);
//回环网络设备
struct net_device loopback_dev = {
    .name             
= "lo",
    .mtu            
= (16 * 1024+ 20 + 20 + 12,
    .hard_start_xmit    
= loopback_xmit,
    .hard_header        
= eth_header,
    .hard_header_cache    
= eth_header_cache,
    .header_cache_update    
= eth_header_cache_update,
    .hard_header_len    
= ETH_HLEN,    /* 14    */
    .addr_len        
= ETH_ALEN,    /* 6    */
    .tx_queue_len        
= 0,
    .type            
= ARPHRD_LOOPBACK,    /* 0x0001*/
    .rebuild_header        
= eth_rebuild_header,
    .flags            
= IFF_LOOPBACK,
    .features         
= NETIF_F_SG|NETIF_F_FRAGLIST
                  
|NETIF_F_NO_CSUM|NETIF_F_HIGHDMA
                  
|NETIF_F_LLTX,
    .ethtool_ops        
= &loopback_ethtool_ops,
};

注:在这里,设备驱动初始化函数loopback_init并没有调用alloc_netdev来分配内存,而是直接调用kmalloc,实际上alloc_netdev只是对kmalloc的封装而已。

2.2、网络设备的方法
2.2.1、打开与关闭设备
打开网络设备
一旦设备注册即可使用,但它必须在用户(或用户空间应用程序)使能后才可以收发数据。dev_open 处理设备使能的请求,它定义在net/core/dev.c,定义如下:

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//打开设备
int dev_open(struct net_device *dev)
{
    
int ret = 0;

    
/*
     *    Is it already up?
     
*/

    
if (dev->flags & IFF_UP)
        
return 0;

    
/*
     *    Is it even present?
     
*/
    
if (!netif_device_present(dev))
        
return -ENODEV;

    
/*
     *    Call device private open method
     
*/
    set_bit(__LINK_STATE_START, 
&dev->state);
    
if (dev->open) { //如果网络设备驱动程序定义了open方法,则调用它
        ret = dev->open(dev);
        
if (ret)
            clear_bit(__LINK_STATE_START, 
&dev->state);
    }

     
/*
     *    If it went open OK then:
     
*/

    
if (!ret) {
        
/*
         *    Set the flags.
         
*/
        dev
->flags |= IFF_UP;//标记设备启动

        
/*
         *    Initialize multicasting status
         
*/
        dev_mc_upload(dev);

        
/*
         *    Wakeup transmit queue engine
         
*/
        dev_activate(dev);

        
/*
         *     and announce new interface.
         
*/
        notifier_call_chain(
&netdev_chain, NETDEV_UP, dev);
    }
    
return ret;
}

打开设备由以下几部组成:
(1)如果 dev->open 被定义则调用它。并非所有的驱动程序都初始化这个函数。
(2)设置 dev->state 的__LINK_STATE_START 标志位,标记设备打开并在运行。
(3)设置 dev->flags 的 IFF_UP 标志位标记设备启动。
(4)调用dev_activate所指函数初始化流量控制用的排队规则,并启动监视定时器。如
果用户没有配置流量控制,则指定缺省的先进先出(FIFO)队列。
(5)发送 NETDEV_UP 通知给 netdev_chain 通知链以通知对设备使能有兴趣的内核组件。

设备驱动的open方法
来看看3com网卡的驱动drivers/net/3c59x.c中的vortex_open,它是在vortex_init()中(即驱动程序初始化的过程中)赋给dev->open函数指针:

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//打开网络设备,
static int
vortex_open(
struct net_device *dev)
{
  
//
vortex_up(dev);
}

static void
vortex_up(
struct net_device *dev)
{
//
//用于清除__LINK_STATE_XOFF标志,表示可以向网络适配器传达室递套接字缓冲区了
    netif_start_queue (dev);
}
//include/linux/netdevice.h
static inline void netif_start_queue(struct net_device *dev)
{
    clear_bit(__LINK_STATE_XOFF, 
&dev->state);
}


关闭网络设备

Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Linux网络协议栈——网络设备 - wdqfirst - Dengqiang Wang  Blog Code
//关闭设备(net/core/dev.c)
int dev_close(struct net_device *dev)
{
    
if (!(dev->flags & IFF_UP))
        
return 0;

    
/*
     *    Tell people we are going down, so that they can
     *    prepare to death, when device is still operating.
     
*/
    notifier_call_chain(
&netdev_chain, NETDEV_GOING_DOWN, dev);

    dev_deactivate(dev);

    clear_bit(__LINK_STATE_START, 
&dev->state);

    
/* Synchronize to scheduled poll. We cannot touch poll list,
     * it can be even on different cpu. So just clear netif_running(),
     * and wait when poll really will happen. Actually, the best place
     * for this is inside dev->stop() after device stopped its irq
     * engine, but this requires more changes in devices. 
*/

    smp_mb__after_clear_bit(); 
/* Commit netif_running(). */
    
while (test_bit(__LINK_STATE_RX_SCHED, &dev->state)) {
        
/* No hurry. */
        current
->state = TASK_INTERRUPTIBLE;
        schedule_timeout(
1);
    }

    
/*
     *    Call the device specific close. This cannot fail.
     *    Only if device is UP
     *
     *    We allow it to be called even after a DETACH hot-plug
     *    event.
     
*/
    
if (dev->stop)
        dev
->stop(dev); //调用设备驱动的close方法

    
/*
     *    Device is now down.
     
*/

    dev
->flags &= ~IFF_UP;

    
/*
     * Tell people we are down
     
*/
    notifier_call_chain(
&netdev_chain, NETDEV_DOWN, dev);

    
return 0;
}

它由以下几步组成:
(1)发送 NETDEV_GOING_DOWN 通知到 netdev_chain 通知链以通知对设备禁止有兴趣的内核组件。
(2)调用 dev_deactivate 函数禁止出口队列规则,这样确保设备不再用于传输,并停止
不再需要的监控定时器。
(3)清除 dev->state 标志的__LINK_STATE_START 标志位,标记设备卸载。
(4)如果轮询动作被调度在读设备入队列数据包,则等待此动作完成。这是由于__LINK_STATE_START 标志位被清除,不再接受其它轮询在设备上调度,但在标志被清除前已有一个轮询正被调度。
(5)如果 dev->stop 指针不空则调用它,并非所有的设备驱动都初始化此函数指针。
(6)清除 dev->flags 的 IFF_UP 标志位标识设备关闭。
(7)发送 NETDEV_DOWN 通知给 netdev_chain 通知链,通知对设备禁止感兴趣的内核组件。

2.2.2、传输数据与接收数据
传输数据
网络子系统中,数据最后由链路层协议调用dev_queue_xmit(),位于net/core/dev.c,完成传输,而dev_queue_xmit 又会调用具体网络适配器的驱动程序方法dev->hard_start_xmit(),从而驱动网络适配器最终完成数据传输,参见 vortex_start_xmit()。

接收数据
当网络适配器接收一个数据帧时,就会触发一个中断,在中断处理程序(位于设备驱动)中,会分配sk_buff数据结构保存数据帧,最后会调用netif_rx(),将套接字缓冲区放入网络设备的输入队列。对于3c39x.c,其过程如下:
vortex_interrupt( )---> vortex_rx( )--->netif_rx( )。

来看看回环设备的发送与接收过程:
//derivers/net/loopback.c
/*
首先调用skb_orphan把skb孤立,使它跟发送socket和协议栈不再有任何联系,也即对本机来说,
**这个skb的数据内容已经发送出去了,而skb相当于已经被释放掉了。对于环回设备接口来说,
**数据的发送工作至此已经全部完成,接下来,只要把这个实际上还未被释放的skb传回给协议栈
**的接收函数即可。
*/
static int loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{
    
struct net_device_stats *lb_stats;
    
/相当于发送过程////
    skb_orphan(skb);

    
//以下相当于接收过程////
    skb->protocol=eth_type_trans(skb,dev);
    skb
->dev=dev;
#ifndef LOOPBACK_MUST_CHECKSUM
    skb
->ip_summed = CHECKSUM_UNNECESSARY;
#endif

    
if (skb_shinfo(skb)->tso_size) {
        BUG_ON(skb
->protocol != htons(ETH_P_IP));
        BUG_ON(skb
->nh.iph->protocol != IPPROTO_TCP);

        emulate_large_send_offload(skb);
        
return 0;
    }

    dev
->last_rx = jiffies; //接收到数据的时间
    
    
//统计信息
    lb_stats = &per_cpu(loopback_stats, get_cpu());
    lb_stats
->rx_bytes += skb->len;
    lb_stats
->tx_bytes += skb->len;
    lb_stats
->rx_packets++;
    lb_stats
->tx_packets++;
    put_cpu();

    netif_rx(skb);

    
return(0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值