网络设备驱动程序需要三个步骤完成net_device的注册:
- 通过alloc_netdev_mq()分配一个net_device对象;
- 对net_device对象的部分字段进行初始化;
- 通过register_netdev()将net_device对象注册到系统,此时在系统中就可以看到该网络设备了。
类似的,net_device的去注册需要两个步骤:
- 通过unregister_device()将net_device对象从系统去注册,此时在系统中就看不到该网络设备了;
- 通过free_netdev()释放net_dev对象,net_device对象真正的释放是由引用计数控制的;
这篇笔记分析了注册过程的步骤一、步骤二,以及去注册过程的步骤二,其他部分的分析见net_device的注册与注销。
分配net_device
常用的net_device分配接口有下面几个:
- alloc_netdev_mq()
分配一个支持多队列的net_device对象并进行一定的初始化。alloc_netdev_mq()本身会对一些字段进行初始化,调用者可以通过setup()参数或调用完毕后进一步进行初始化。
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
void (*setup)(struct net_device *), unsigned int queue_count)
- alloc_netdev()
该接口就是alloc_netdev_mq()的包裹函数,当只有一个tx队列时使用。
#define alloc_netdev(sizeof_priv, name, setup) \
alloc_netdev_mq(sizeof_priv, name, setup, 1)
- alloc_etherdev_alloc()
特别的,Ethernet提供了该包裹函数方便调用者分配以太网类型的net_device对象。
struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)
{
return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
}
上述接口最后都是通过alloc_netdev_mq()完成net_device对象的分配的,下面来分析该函数的实现。
alloc_netdev_mq()
/**
* alloc_netdev_mq - allocate network device
* @sizeof_priv: size of private data to allocate space for
* @name: device name format string
* @setup: callback to initialize device
* @queue_count: the number of 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 at the end of the netdevice.
*/
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
void (*setup)(struct net_device *), unsigned int queue_count)
{
struct netdev_queue *tx;
struct net_device *dev;
size_t alloc_size;
void *p;
// 网卡名字长度不能超过IFNAMSIZ字节
BUG_ON(strlen(name) >= sizeof(dev->name));
// 实际分配的内存大小:struct net_device + 私有空间大小
alloc_size = sizeof(struct net_device);
if (sizeof_priv) {
/* ensure 32-byte alignment of private area */
alloc_size = (alloc_size + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;
alloc_size += sizeof_priv;
}
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN_CONST;
p = kzalloc(alloc_size, GFP_KERNEL);
if (!p) {
printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
return NULL;
}
// 分配发送队列
tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL);
if (!tx) {
printk(KERN_ERR "alloc_netdev: Unable to allocate "
"tx qdiscs.\n");
kfree(p);
return NULL;
}
// 调整偏移量,使得net_device对象的第一个成员地址是32字节对齐的
dev = (struct net_device *)
(((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
// 计算padding
dev->padded = (char *)dev - (char *)p;
// 初始分配的net_device都放在initns中,后面有需要可以调整
dev_net_set(dev, &init_net);
// 设置发送队列
dev->_tx = tx;
dev->num_tx_queues = queue_count;
dev->real_num_tx_queues = queue_count;
dev->gso_max_size = GSO_MAX_SIZE;
// 初始化发送队列
netdev_init_queues(dev);
// 初始化NAPI对象列表
INIT_LIST_HEAD(&dev->napi_list);
// 执行分配对象后的setup()回调
setup(dev);
// 拷贝网卡名字到name字段中,后面任意步骤均可以继续修改名字
strcpy(dev->name, name);
return dev;
}
该函数执行后,分配的net_device对象的内存布局如下图所示:
在分配net_device对象时,会分配两块内存:1)net_device对象和私有数据区域(由调用者指定大小);2)TX队列。驱动程序可以利用私有数据区域保存任意的数据,内核提供了函数netdev_priv()来通过net_device指针获取指向私有数据区域的指针。
为了提高内存访问效率,要求net_device对象的第一个字段要32字节边界对齐,所以在开头可能会有填充,具体填充的字节数记录在net_device.padded中。此外私有数据区域的起始位置也是32字节对齐的,所以在net_device和priv_data之间也可能存在填充。
初始化net_device
在调用alloc_netdev_mq()时必须传入setup回调,调用者可以通过setup回调对net_device对象进行初始化。此外还可以在alloc_netdev_mq()返回后直接设置其字段。下面我们以Ethernet网络设备的setup回调为例分析看其对哪些字段进行了初始化。
ether_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; // 邻居子系统使用这组回调构造L2首部
#ifdef CONFIG_COMPAT_NET_DEV_OPS
dev->change_mtu = eth_change_mtu;
dev->set_mac_address = eth_mac_addr;
dev->validate_addr = eth_validate_addr;
#endif
dev->type = ARPHRD_ETHER; // Ethernet类型设备
dev->hard_header_len = ETH_HLEN; // L2首部长度为14字节
dev->mtu = ETH_DATA_LEN; // 1500字节的MTU
dev->addr_len = ETH_ALEN; // L2地址长度为6字节
dev->tx_queue_len = 1000; /* Ethernet wants good queues */
dev->flags = IFF_BROADCAST|IFF_MULTICAST; // 支持L2多播和广播
memset(dev->broadcast, 0xFF, ETH_ALEN); // 设置广播地址为全1
}
释放net_device
只要alloc_netdev_mq()成功,那么在需要释放net_device对象的时候都应该调用free_netdev(),至于net_device对象的内存最终何时回收,是由引用计数管理的,调用者不用关心。
free_netdev()
/**
* free_netdev - free network device
* @dev: device
*
* This function does the last stage of destroying an allocated device
* interface. The reference to the device object is released.
* If this is the last reference then it will be freed.
*/
void free_netdev(struct net_device *dev)
{
struct napi_struct *p, *n;
// 删除namespace绑定
release_net(dev_net(dev));
// 释放发送队列
kfree(dev->_tx);
// 删除NAPI对象列表
list_for_each_entry_safe(p, n, &dev->napi_list, dev_list)
netif_napi_del(p);
/* Compatibility with error handling in drivers */
// net_device尚未注册,可以直接kfree()
if (dev->reg_state == NETREG_UNINITIALIZED) {
kfree((char *)dev - dev->padded);
return;
}
// 设置为RELEASE状态,然后递减引用计数进行释放
BUG_ON(dev->reg_state != NETREG_UNREGISTERED);
dev->reg_state = NETREG_RELEASED;
/* will free via device release */
put_device(&dev->dev);
}
void put_device(struct device *dev)
{
// 递减统一设备模型中的引用计数
if (dev)
kobject_put(&dev->kobj);
}
上面并没有看到free()操作,但是有设备模型相关的引用递减。在net_device对象注册过程中会调用netdev_kobject_init()将网络设备和设备模型关联起来的,当最后一个引用计数递减时,触发设备模型的析构函数netdev_release(),该函数会会后net_device对象占用的内存。相关代码如下:
/*
* netdev_release -- destroy and free a dead device.
* Called when last reference to device kobject is gone.
*/
static void netdev_release(struct device *d)
{
struct net_device *dev = to_net_dev(d);
// 必须是RELEASED状态
BUG_ON(dev->reg_state != NETREG_RELEASED);
kfree(dev->ifalias);
// 释放net_device占用内存,注意进行了偏移处理
kfree((char *)dev - dev->padded);
}
static struct class net_class = {
.name = "net",
// 设备模型的析构函数
.dev_release = netdev_release,
...
};
// 如上,该函数在设备注册过程中被调用
int netdev_register_kobject(struct net_device *net)
{
struct device *dev = &(net->dev);
//设备类为net_class
dev->class = &net_class;
...
}