分类: LINUX
2014-01-03 14:06:52
原文地址: 深入理解Linux网络技术内幕-设备注册和初始化(一) 作者:visualfan
NIC设备在内核中相关联的net_device结构初始化,并添加到内核网络设备数据块中注册之后,用户才能通过用户空间的命令开启设备,使其可用。设备的注册和注销是由内核完成的,而设备的开启和关闭是由用户控制的。
网络设备注册的触发事件:
- 加载NIC设备驱动程序:若NIC设备驱动程序编译到内核中,则驱动程序将在系统引导期间初始化;若以模块加载的方式,则会在系统运行期间初始化。每当设备驱动程序初始化时,该驱动程序所控制的所有NIC设备就会被注册到系统设备数据库中。
- 插入可插拔网络设备:在设备的驱动程序已加载到系统中后,若用户将可热插拔NIC设备插入,内核会通知其驱动程序,这时NIC设备也会被注册到系统中。
相对应的,NIC设备注销的触发事件:
- 卸载NIC驱动程序:只针对以模块方式加载的驱动程序,不适用于编译进内核的驱动程序。当root卸载NIC设备驱动程序时,所有相关联以注册的NIC设备都会被注销。
- 拔出支持可热插拔的网络设备:当用户从系统中拔出可热插拔的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)
- 其中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);
#endiffree_p:
kfree(p);
return NULL;
}
EXPORT_SYMBOL(alloc_netdev_mqs);