net_device的分配与释放

这篇博客详细探讨了Linux环境中net_device的分配、初始化和释放过程。通过alloc_netdev_mq()分配net_device对象,并对其进行初始化,然后通过register_netdev()注册到系统。在去注册时,unregister_netdev()先执行,接着调用free_netdev()释放资源。alloc_netdev_mq()内部处理了对齐和私有数据区域,ether_setup()用于初始化以太网设备。释放时,引用计数管理net_device的真正释放。
摘要由CSDN通过智能技术生成


网络设备驱动程序需要三个步骤完成net_device的注册:

  1. 通过alloc_netdev_mq()分配一个net_device对象;
  2. 对net_device对象的部分字段进行初始化;
  3. 通过register_netdev()将net_device对象注册到系统,此时在系统中就可以看到该网络设备了。

类似的,net_device的去注册需要两个步骤:

  1. 通过unregister_device()将net_device对象从系统去注册,此时在系统中就看不到该网络设备了;
  2. 通过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		= &eth_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;
...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值