前面的笔记分析了net_device的分配和注册流程,在可以进行数据收发之前,还需要打开net_device。对应的net_device不再工作时需要关闭net_device,这篇笔记来分析下这两个流程是如何实现的。
打开net_device
当执行“ip link set dev {DEVICE} up”时,将会打开net_device。该命令对应到设备接口层,将由dev_open()函数完成具体的打开流程。
/**
* dev_open - prepare an interface for use.
* @dev: device to open
*
* Takes a device from down to up state. The device's private open
* function is invoked and then the multicast lists are loaded. Finally
* the device is moved into the up state and a %NETDEV_UP message is
* sent to the netdev notifier chain.
*
* Calling this function on an active interface is a nop. On a failure
* a negative errno code is returned.
*/
int dev_open(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
int ret = 0;
ASSERT_RTNL(); // 一定是在持有RTNL锁的情况下调用
// 如果设备已经打开,直接返回打开成功
if (dev->flags & IFF_UP)
return 0;
// 设备必须已经注册或者未挂起,即外部可见
if (!netif_device_present(dev))
return -ENODEV;
// 设置START状态,表示设备已经启动
set_bit(__LINK_STATE_START, &dev->state);
// 调用驱动的地址校验回调函数
if (ops->ndo_validate_addr)
ret = ops->ndo_validate_addr(dev);
// 调用驱动的nod_open()回调,驱动程序一般应该在这里清除设备的XOFF标记以开启发送能力
if (!ret && ops->ndo_open)
ret = ops->ndo_open(dev);
if (ret)
clear_bit(__LINK_STATE_START, &dev->state); // 操作失败,清除START标记
else {
// 设置UP标记
dev->flags |= IFF_UP;
// Enable NET_DMA
net_dmaengine_get();
// Initialize multicasting status
dev_set_rx_mode(dev);
// 激活发送队列,主要是配置流量控制相关的队列
dev_activate(dev);
// 向内核其它模块发送NETDEV_UP通知事件,表示设备已经打开
call_netdevice_notifiers(NETDEV_UP, dev);
}
return ret;
}
关闭net_device
当执行“ip link set dev DEVICE down”时,设备会被关闭,最后由设备接口层的dev_close()函数负责执行具体的关闭操作。
/**
* dev_close - shutdown an interface.
* @dev: device to shutdown
*
* This function moves an active device into down state. A
* %NETDEV_GOING_DOWN is sent to the netdev notifier chain. The device
* is then deactivated and finally a %NETDEV_DOWN is sent to the notifier
* chain.
*/
int dev_close(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
ASSERT_RTNL();
might_sleep();
// 如果设备本来就是关闭的,那么直接返回成功
if (!(dev->flags & IFF_UP))
return 0;
// 向内核其它模块发送设备即将关闭通知事件
call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);
// 清除START状态
clear_bit(__LINK_STATE_START, &dev->state);
/* Synchronize to scheduled poll. We cannot touch poll list,
* it can be even on different cpu. So just clear netif_running().
*
* dev->stop() will invoke napi_disable() on all of it's
* napi_struct instances on this device.
*/
// 同步其它CPU的状态,让他们能够退出收发状态
smp_mb__after_clear_bit(); /* Commit netif_running(). */
// 流量控制相关的清除工作,这会关闭发送队列
dev_deactivate(dev);
/*
* Call the device specific close. This cannot fail.
* Only if device is UP
*
* We allow it to be called even after a DETACH hot-plug
* event.
*/
// 调用驱动程序的ndo_stop()回调
if (ops->ndo_stop)
ops->ndo_stop(dev);
// 清除UP标记
dev->flags &= ~IFF_UP;
// 向内核其它模块发送DOWN通知事件,表示设备已经关闭
call_netdevice_notifiers(NETDEV_DOWN, dev);
// Shutdown NET_DMA
net_dmaengine_put();
return 0;
}
小结
从上面设备打开与关闭过程可以总结如下几个关键点:
- 设备打开与否,可以通过net_device.flags中的IFF_UP标记或者net_device.state中的__LINK_STATE_START标记判断,flags标记主要是给内核其它模块或者用户态暴露的;state字段是设备接口层内部自己使用的;
- 和net_devcie的注册&注销过程类似,打开与关闭过程中也会向其它模块发送事件通知;
- 设备打开与关闭过程中,还有一个非常重要的操作是流量控制机制的启用与停用,这部分见默认网络设备流量控制。