NIC注册和注销的通用架构
Linux系统中NIC网络设备驱动程序利用网络代码进行注册和注销有其通用的架构,这里以PCI Ethernet NIC为例,其他设备类型只是所以函数名称和调用方式不同,主要依据于设备总线提供的接口。
其中(a)为设备注册的大致流程图,而(b)为设备注销的流程图。
在PCI Ethernet NIC设备驱动程序的探测函数(热插拔设备)或模块初始化函数中,首先要为设备分配一个net_device数据结构,并对其中的成员进行必要的初始化,对其中与设备类型密切相关的特殊成员利用驱动程序自己实现的setup函数进行初始化;Ethernet NIC设备驱动程序还需要调用netdev_boot_setup_check检查是否在系统启动参数中对网络设备进行了设置;然后调用register_netdev完成设备的注册。
在分配net_device数据结构时,驱动程序一般不直接调用alloc_netdev函数,而是调用为其类型封装后的函数,如Ethernet NIC设备直接调用alloc_etherdev函数,使用更加方便简单。
而Ethernet NIC设备的注销则是相反的过程,首先调用unregister_netdev在系统中注销设备,然后将分配的net_device数据结构释放。
在释放net_device数据结构时,设备也可能不直接调用free_netdev函数中,而是调用net_device数据结构中的成员函数:
/* Called from unregister, can be used to call free_netdev */
void (*destructor)(struct net_device *dev);
虚拟设备驱动程序一般采用这种方式,实现自己的destructor函数来释放net_device数据结构。
网络设备注册过程
网络设备在系统中注册后,内核在处理数据包时才能调用设备接口实现的处理函数。网络设备的注册是通过register_netdev函数完成的:
/**
* register_netdev - register a network device
* @dev: device to register
*
* Take a completed network device structure and add it to the kernel
* interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
* chain. 0 is returned on success. A negative errno code is returned
* on a failure to set up the device, or if the name is a duplicate.
*
* This is a wrapper around register_netdevice that takes the rtnl semaphore
* and expands the device name if you passed a format string to
* alloc_netdev.
*/
int register_netdev(struct net_device *dev)
{
int err;
rtnl_lock();
/*
* If the name is a format string the caller wants us to do a
* name allocation.
*/
if (strchr(dev->name, '%')) {
err = dev_alloc_name(dev, dev->name);
if (err < 0)
goto out;
}
err = register_netdevice(dev);
out:
rtnl_unlock();
return err;
}
EXPORT_SYMBOL(register_netdev);
其中rtnl_lock是内核保护运行时的net_device数据结构的互斥手段,一般在修改net_device中flag字段,表示有事件发生需要改变设备的状态;或者用户通过ifconfig、route等命令修改接口的配置时,通过ioctl和netlink接口告诉内核操作设备的net_device结构,都需要调用这个锁来进行互斥。
dev_alloc_name(dev, dev->name)函数会在系统中找到这种类型的网络设备中第一个没有使用的序列号来替换设备名称中的%d,生成如eth2的设备名称。
int dev_alloc_name(struct net_device *dev, const char *name)
{
char buf[IFNAMSIZ];
struct net *net;
int ret;
BUG_ON(!dev_net(dev));
net = dev_net(dev);
ret = __dev_alloc_name(net, name, buf);
if (ret >= 0)
strlcpy(dev->name, buf, IFNAMSIZ); //将返回的设备名称复制到net_device的name字段
return ret;
}
static int __dev_alloc_name(struct net *net, const char *name, char *buf)
{
int i = 0;
const char *p;
const int max_netdevices = 8*PAGE_SIZE;
unsigned long *inuse;
struct net_device *d;
/*检查设备名称中是否有%d,或其他不合法字符*/
p = strnchr(name, IFNAMSIZ-1, '%');
if (p) {
if (p[1] != 'd' || strchr(p + 2, '%'))
return -EINVAL;
/*分配一个物理页面作为位图,来对系统中该类型设备已用序列号进行标记*/
inuse = (unsigned long *) get_zeroed_page(GFP_ATOMIC);
if (!inuse)
return -ENOMEM;
/*变量网络命名空间中的所有设备,即net_device结构*/
for_each_netdev(net, d) {
if (!sscanf(d->name, name, &i)) //获取同类型网络设备的其序列号,这里极为巧妙
continue;
if (i < 0 || i >= max_netdevices) //判断序列号的范围
continue;
snprintf(buf, IFNAMSIZ, name, i);
if (!strncmp(buf, d->name, IFNAMSIZ)) /*验证解析的序列号是否正确*/
set_bit(i, inuse); //在位图中将该位标记
}
i = find_first_zero_bit(inuse, max_netdevices); //找到第一个为0的序列号
free_page((unsigned long) inuse);
}
if (buf != name)
snprintf(buf, IFNAMSIZ, name, i); //根据找到的序列号,输出完整的设备名
if (!__dev_get_by_name(net, buf)) //在name_list链表中查找是否有同名的设备
return i;
/* It is possible to run out of possible slots
* when the name is long and there isn't enough space left
* for the digits, or if all bits are used.
*/
return -ENFILE;
}
在这里就为设备完成了完整设备名的组合,内核在这里位图的使用非常巧妙,以后可以在处理位图时,可以直接使用内核实现的set_bit和find_first_zero_bit、clear_bit等函数。
register_netdevice才是网络设备注册的最重要步骤:
int register_netdevice(struct net_device *dev)
{
int ret;
struct net *net = dev_net(dev); //设备的网络空间
BUG_ON(dev_boot_phase);
ASSERT_RTNL();
might_sleep();
/* When net_device's are persistent, this will be fatal. */
BUG_ON(dev->reg_state != NETREG_UNINITIALIZED); //alloc_netdev时不需要设置这个成员,因为其为0
BUG_ON(!net);
/*初始化net_device中的一些成员锁*/
spin_lock_init(&dev->addr_list_lock);
netdev_set_addr_lockdep_class(dev);
dev->iflink = -1;
/* Init, if this function is available */
if (dev->netdev_ops->ndo_init) { //调用设备驱动程序操作中实现的初始化函数
ret = dev->netdev_ops->ndo_init(dev);
if (ret) {
if (ret > 0)
ret = -EIO;
goto out;
}
}
ret = dev_get_valid_name(dev, dev->name, 0); //检查设备名称的有效性
if (ret)
goto err_uninit;
dev->ifindex = dev_new_index(net); //为设备分配一个唯一的索引号
if (dev->iflink == -1)
dev->iflink = dev->ifindex;
/* Transfer changeable features to wanted_features and enable
* software offloads (GSO and GRO).
*/
/*设置设备的一些特性*/
dev->hw_features |= NETIF_F_SOFT_FEATURES;
dev->features |= NETIF_F_SOFT_FEATURES;
dev->wanted_features = dev->features & dev->hw_features;
/* Enable GRO and NETIF_F_HIGHDMA for vlans by default,
* vlan_dev_init() will do the dev->features check, so these features
* are enabled only if supported by underlying device.
*/
dev->vlan_features |= (NETIF_F_GRO | NETIF_F_HIGHDMA);
ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev); //调用通知链,发出事件通知
ret = notifier_to_errno(ret);
if (ret)
goto err_uninit;
ret = netdev_register_kobject(dev); //设备注册的核心函数,主要是调用device_add函数,将设备添加到内核的设备管理器中
if (ret)
goto err_uninit;
dev->reg_state = NETREG_REGISTERED; //设置net_device的状态
netdev_update_features(dev);
/*
* Default initial state at registry is that the
* device is present.
*/
set_bit(__LINK_STATE_PRESENT, &dev->state);
dev_init_scheduler(dev); //在这里会设置设备的看门狗定时器
dev_hold(dev); //增加设备的引用计数
list_netdevice(dev); //将设备加入系统的indexlist、namelist和devlist中
/* Notify protocols, that a new device appeared. */
ret = call_netdevice_notifiers(NETDEV_REGISTER, dev); //通过通知链发出设备注册通知
ret = notifier_to_errno(ret);
if (ret) {
rollback_registered(dev);
dev->reg_state = NETREG_UNREGISTERED;
}
/*
* Prevent userspace races by waiting until the network
* device is fully setup before sending notifications.
*/
if (!dev->rtnl_link_ops ||
dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
out:
return ret;
err_uninit:
if (dev->netdev_ops->ndo_uninit)
dev->netdev_ops->ndo_uninit(dev);
goto out;
}
EXPORT_SYMBOL(register_netdevice);
由上可知注册的主要过程是netdev_register_kobject函数中的device_add过程,和list_netdevice(dev)将设备加入到系统的几个hash链表中,便于系统处理数据包时查找对应的设备。