深入理解Linux网络技术内幕-设备注册和初始化(一)


NIC设备在内核中相关联的net_device结构初始化,并添加到内核网络设备数据块中注册之后,用户才能通过用户空间的命令开启设备,使其可用。设备的注册和注销是由内核完成的,而设备的开启和关闭是由用户控制的。

网络设备注册的触发事件:

  1. 加载NIC设备驱动程序:若NIC设备驱动程序编译到内核中,则驱动程序将在系统引导期间初始化;若以模块加载的方式,则会在系统运行期间初始化。每当设备驱动程序初始化时,该驱动程序所控制的所有NIC设备就会被注册到系统设备数据库中。
  2. 插入可插拔网络设备:在设备的驱动程序已加载到系统中后,若用户将可热插拔NIC设备插入,内核会通知其驱动程序,这时NIC设备也会被注册到系统中。

相对应的,NIC设备注销的触发事件:

  1. 卸载NIC驱动程序:只针对以模块方式加载的驱动程序,不适用于编译进内核的驱动程序。当root卸载NIC设备驱动程序时,所有相关联以注册的NIC设备都会被注销。
  2. 拔出支持可热插拔的网络设备:当用户从系统中拔出可热插拔的NIC设备时,该网络设备就会在系统中被注销。

分配net_device数据结构

在系统中,网络设备用net_device数据结构定义,该数据结构由alloc_netdev函数分配,定义在net/core/dev.c源文件中。

#define alloc_netdev(sizeof_priv, name, setup) \ 
    alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)

  1. 其中sizeof_priv是网络设备驱动程序私有数据块的大小,在alloc_netdev_mqs函数中将和net_device数据结构一起分配,但驱动程序也可以设置sizeof_priv为0,不需要私有数据块,或自己分配私有数据块内存。

          若何net_device数据结构一起分配驱动程序的私有数据块,则其私有数据块的内存地址通过函数net_dec_priv获取:

/** 
*    netdev_priv - access network device private data 
*    @dev: network device 

* Get network device private data 
*/ 
static inline void *netdev_priv(const struct net_device *dev) 

    return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN); 
}

可见,驱动程序的私有数据块就跟在net_device数据结构后面,从alloc_netdev_mqs函数中也可以看到。

   2.   name是网络设备的名称,如eth*等等,用户空间的配置工具会引用内核所分派的设备名称。在用户空间,net-tools套件中的nameif可以基于MAC地址分派固定的名称给网络接口。

         实际上,在调用alloc_netdev函数时,传入的设备名称可能是name%d的形式如“eth%d”,内核在注册设备时,检查到%d标识符,会调用函数dev_alloc_name在系统中查找尚未使用的序列号,将eth%d替换为eth6等。

   3.   setup函数的原型是void (*setup)(struct net_device *),由驱动程序实现,在分配net_device结构后对结构中的一些未初始化成员进行初始化操作。

内核为不同的网络设备提供了一层封装函数,更便于使用:

网络设备类型

封装函数

函数定义

以太网设备(Ethernet) alloc_etherdev(sizeof_priv) alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
光纤分布式数据接口(FDDI) alloc_fddidev(int sizeof_priv) alloc_netdev(sizeof_priv, "fddi%d", fddi_setup)
高性能并行接口(HPPI) alloc_hippi_dev(int sizeof_priv) alloc_netdev(sizeof_priv, "hip%d", hippi_setup)
令牌环(Token Ring) alloc_trdev(int sizeof_priv) alloc_netdev(sizeof_priv, "tr%d", tr_setup)
光纤通道(Fibre Channel) alloc_fcdev(int sizeof_priv) alloc_netdev(sizeof_priv, "fc%d", fc_setup)
红外数据接口(InDA) alloc_irdadev(int sizeof_priv) alloc_netdev(sizeof_priv, "irda%d", irda_device_setup)

每一类网络设备都定义了自己的setup函数,如以太网设备的setup函数:

/** 
* ether_setup - setup Ethernet network device 
* @dev: network device 
* Fill in the fields of the device structure with Ethernet-generic values. 
*/ 
void ether_setup(struct net_device *dev) 

    dev->header_ops        = ð_header_ops; 
    dev->type        = ARPHRD_ETHER; 
    dev->hard_header_len     = ETH_HLEN; 
    dev->mtu        = ETH_DATA_LEN; 
    dev->addr_len        = ETH_ALEN; 
    dev->tx_queue_len    = 1000;    /* Ethernet wants good queues */ 
    dev->flags        = IFF_BROADCAST|IFF_MULTICAST;

    memset(dev->broadcast, 0xFF, ETH_ALEN);


EXPORT_SYMBOL(ether_setup);

alloc_netdev函数的实现:

/** 
*    alloc_netdev_mqs - allocate network device 
*    @sizeof_priv:    size of private data to allocate space for 
*    @name:        device name format string 
*    @setup:        callback to initialize device 
*    @txqs:        the number of TX subqueues to allocate 
*    @rxqs:        the number of RX subqueues to allocate 

*    Allocates a struct net_device with private data area for driver use 
*    and performs basic initialization.  Also allocates subquue structs 
*    for each queue on the device. 
*/ 
struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, 
        void (*setup)(struct net_device *), 
        unsigned int txqs, unsigned int rxqs) 

    struct net_device *dev; 
    size_t alloc_size; 
    struct net_device *p;

    BUG_ON(strlen(name) >= sizeof(dev->name)); //net_device数据结构中设备名称的最大长度是16个字节

    /*将net_device数据结构的大小按32字节对齐后,和sizeof_priv私有数据大小相加,产生分配的总内存字节大小*/

    alloc_size = sizeof(struct net_device); 
    if (sizeof_priv) { 
        /* ensure 32-byte alignment of private area */ 
        alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); 
        alloc_size += sizeof_priv; 
    }

     /*在这里增加31个字节,是为后面将分配后net_device数据结构的地址调整到32字节边界对齐,预留空间*/ 
    /* ensure 32-byte alignment of whole construct */ 
    alloc_size += NETDEV_ALIGN - 1;

   

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

  /*将net_device数据结构的地址对齐到32字节边界,并记录下调整后的地址和实际分配的地址之间的长度,便于释放空间时使用分配的实际起始地址*/

   dev = PTR_ALIGN(p, NETDEV_ALIGN); 
    dev->padded = (char *)dev - (char *)p;

/*分配一个per_cpu变量,记录该结构的引用计数*/ 

  dev->pcpu_refcnt = alloc_percpu(int); 
    if (!dev->pcpu_refcnt) 
        goto free_p;

    /*初始化设备的硬件地址列表,并分配一个硬件地址成员*/

    if (dev_addr_init(dev)) 
        goto free_pcpu;

    /*初始化多播和单播硬件地址列表*/

    dev_mc_init(dev); 
    dev_uc_init(dev);

   /*设置设备的网络空间*/

    dev_net_set(dev, &init_net);

    dev->gso_max_size = GSO_MAX_SIZE;

    INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list); 
    dev->ethtool_ntuple_list.count = 0; 
    INIT_LIST_HEAD(&dev->napi_list); 
    INIT_LIST_HEAD(&dev->unreg_list); 
    INIT_LIST_HEAD(&dev->link_watch_list); 
    dev->priv_flags = IFF_XMIT_DST_RELEASE;

   /*调用setup函数,初始化net_device结构中与设备类型密切相关的成员*/ 
    setup(dev);

   /*分配接收队列和发送队列*/ 

  dev->num_tx_queues = txqs; 
    dev->real_num_tx_queues = txqs; 
    if (netif_alloc_netdev_queues(dev)) 
        goto free_all;

#ifdef CONFIG_RPS 
    dev->num_rx_queues = rxqs; 
    dev->real_num_rx_queues = rxqs; 
    if (netif_alloc_rx_queues(dev)) 
        goto free_all; 
#endif

    /*设置网络设备名称*/

    strcpy(dev->name, name); 
    dev->group = INIT_NETDEV_GROUP; 
    return dev;

free_all: 
    free_netdev(dev); 
    return NULL;

free_pcpu: 
    free_percpu(dev->pcpu_refcnt); 
    kfree(dev->_tx); 
#ifdef CONFIG_RPS 
    kfree(dev->_rx); 
#endif

free_p: 
    kfree(p); 
    return NULL; 

EXPORT_SYMBOL(alloc_netdev_mqs);

猜你喜欢

转载自blog.csdn.net/wlf_go/article/details/80284367