数据收发过程中的网络设备状态

本文详细介绍了网络设备在数据收发过程中的状态,包括net_device->state的链路状态,Qdisc->state的排队规则状态,以及netdev_queue->state的收发队列状态。内容涵盖设备的挂起和恢复、链路状态侦测以及事件处理机制,为理解网络设备的运行提供了深入见解。
摘要由CSDN通过智能技术生成


从前面的数据包收发过程中可以看到,过程中有很多的设备状态检查和设置的操作,这篇笔记来单独分析下涉及的状态以及它们的含义。

收发过程中,实际上涉及如下三个数据结构对象的状态检查和设置。

网络设备状态: net_device->state

dev->state字段描述了设备和设备队列的状态,当前定义有如下值:

/* These flag bits are private to the generic network queueing
 * layer, they may not be explicitly referenced by any other
 * code.
 */
enum netdev_state_t
{
	__LINK_STATE_START,
	__LINK_STATE_PRESENT,
	__LINK_STATE_NOCARRIER,
	__LINK_STATE_LINKWATCH_PENDING,
	__LINK_STATE_DORMANT,
};

__LINK_STATE_START

表示设备是否已经被打开。在dev_open()的时候设置该标记位,在dev_close()清除该标记位。

可以使用netif_running()检测是否设置了该标志位:

/**
 *	netif_running - test if up
 *	@dev: network device
 *
 *	Test if the device has been brought up.
 */
static inline int netif_running(const struct net_device *dev)
{
	return test_bit(__LINK_STATE_START, &dev->state);
}

__LINK_STATE_PRESENT

表示网络设备对象是否存在。网络设备对象注册过程会将该标记置位。此外,在电源管理过程中,设备被挂起时会清除该标记使得设备暂时不可用,在恢复时重新置位使设备恢复,具体看下面的“设备挂起”与“设备恢复”。

可以使用netif_device_present()检测是否设置了该标志位:

/**
 *	netif_device_present - is device available or removed
 *	@dev: network device
 *
 * Check if device has not been removed from system.
 */
static inline int netif_device_present(struct net_device *dev)
{
	return test_bit(__LINK_STATE_PRESENT, &dev->state);
}

__LINK_STATE_NOCARRIER

当驱动程序感知到设备的载波变化时,要使用netif_carrier_on/off()通知内核,使得内核可以在这些情况下合理的关闭设备的收发能力,具体见下文。

可以使用netif_carrier_ok()检查是否设置了该标志位:

/**
 *	netif_carrier_ok - test if carrier present
 *	@dev: network device
 *
 * Check if carrier is present on device
 */
static inline int netif_carrier_ok(const struct net_device *dev)
{
	return !test_bit(__LINK_STATE_NOCARRIER, &dev->state);
}

__LINK_STATE_LINKWATCH_PENDING

如果设备已经产生了LINKWATCH事件并且正在调度过程中,设置该标志位。这是为了防止同一个设备同时被多次调度。

__LINK_STATE_DORMANT

和__LINK_STATE_NOCARRIER类似,也是表示链路层状态,设置了该标记后,设备无法收发数据包。驱动程序可使用netif_dormant_on/off()操作该标志位。

排队规则状态: Qdisc->state

enum qdisc_state_t
{
	__QDISC_STATE_RUNNING,
	__QDISC_STATE_SCHED,
	__QDISC_STATE_DEACTIVATED,
};

__QDISC_STATE_RUNNING

表示qdisc_run()函数是否正在被调用,可以防止一个排队规则同时被多个CPU同时调度。该标记在进入qdisc_run()之前设置,退出时清除。

__QDISC_STATE_SCHED

表示排队规则是否正在被发送软中断调度。

__QDISC_STATE_DEACTIVATED

设置该标记位表示该排队规则被禁用,禁用的排队规则是无法用来发送数据包的。设备被关闭时会关闭发送队列,此时对应的排队规则就会被设置该标记。

收发队列状态: netdev_queue->state

enum netdev_queue_state_t
{
	__QUEUE_STATE_XOFF,
	__QUEUE_STATE_FROZEN,
};

__QUEUE_STATE_XOFF

标记队列是否被关闭。关闭后的队列是无法用来收发数据的。驱动程序可以通过操作该标记位影响设备接口层的发送逻辑,进而可以临时性的开启和关闭指定收发队列。

__QUEUE_STATE_FROZEN

标记队列是否被冻结,当一个队列被冻结时,该队列无法收发数据。

设备的挂起和恢复

当电源管理模块通知设备系统即将进入休眠态时,驱动程序必须要执行休眠准备工作,在该过程中,驱动需要调用netif_device_detach()告诉框架设备即将休眠。类似的,设备退出休眠态时,驱动程序必须要执行恢复工作,在该过程中,驱动需要调用netif_device_attach()告诉框架设备即将恢复。

挂起: netif_device_detach()

在挂起时,需要干两件事:

  1. 清除__LINK_STATE_PRESENT标志位,使得设备暂时不可用;
  2. 如果设备已经打开(调用过dev_open(),可以正常收发数据了),还需要关闭设备的发送队列。
/**
 * netif_device_detach - mark device as removed
 * @dev: network device
 *
 * Mark device as removed from system and therefore no longer available.
 */
void netif_device_detach(struct net_device *dev)
{
	// cond1:之前设备的PRESET标记存在,即没有休眠
	// cond2:设备的START标记存在,即设备是打开的
	if (test_and_clear_bit(__LINK_STATE_PRESENT, &dev->state) &&
	    netif_running(dev)) {
		// 关闭设备的发送队列
		netif_stop_queue(dev);
	}
}

恢复: netif_device_attach()

恢复设备时,干两件事:

  1. 重新社长是__LINK_STATE_PRESENT标志位;
  2. 如果设备已经打开,那么开启发送队列并且重新调度设备使其可以发送
/**
 * netif_device_attach - mark device as attached
 * @dev: network device
 *
 * Mark device as attached from system and restart if needed.
 */
void netif_device_attach(struct net_device *dev)
{
	// cond1:之前设备的PRESET标记不在,即休眠了
	// cond2:设备的START标记存在,即设备是打开的
	if (!test_and_set_bit(__LINK_STATE_PRESENT, &dev->state) &&
	    netif_running(dev)) {
		// 开启发送队列;将设别加入发送轮询队列;激活发送软中断
		netif_wake_queue(dev);
		// 启动WatchDog,其实是延迟回调驱动提供的tx_timeout()接口,
		// 不知道这种设计有什么目的
		__netdev_watchdog_up(dev);
	}
}

链路状态侦测

驱动程序可以感知到网络设备硬件是否处于可用状态的变化,比如网线是否掉了等事件,这些事件可以简单的划分为可用和不可用两种状态。当这两个事件发生时,驱动程序应该将这种状态传递给设备接口层,这是通过netif_carrier_on()和netif_carrier_off()来实现的。

/**
 *	netif_carrier_on - set carrier
 *	@dev: network device
 *
 * Device has detected that carrier.
 */
void netif_carrier_on(struct net_device *dev)
{
	// 清除设备的__LINK_STATE_NOCARRIER标志
	if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		// 产生一个链路状态变化事件
		linkwatch_fire_event(dev);
		// 如果设备已经打开,启动发送异常检测WatchDog定时器
		if (netif_running(dev))
			__netdev_watchdog_up(dev);
	}
}

/**
 *	netif_carrier_off - clear carrier
 *	@dev: network device
 *
 * Device has detected loss of carrier.
 */
void netif_carrier_off(struct net_device *dev)
{
    // 设置__LINK_STATE_NOCARRIER标志位
	if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		// 产生一个链路状态变化事件
		linkwatch_fire_event(dev);
	}
}

报告链路状态变化事件

如上,链路状态变化的事件最先由驱动程序感知到,驱动通过netif_carrier_on/off()、netif_dormant_on/off()通知设备接口层,设备接口层会调用linkwatch_fire_event()触发链路状态变化事件的产生。

// 何为紧急事件
static int linkwatch_urgent_event(struct net_device *dev)
{
	// 设备打开 && 链路状态由off变为on && 排队规则有变更
	return netif_running(dev) && netif_carrier_ok(dev) &&
	       dev->qdisc != dev->qdisc_sleeping;
}

void linkwatch_fire_event(struct net_device *dev)
{
	// 判断此次是否为紧急事件
	int urgent = linkwatch_urgent_event(dev);

	// 设置LINKWATCH_PENDING标记,表示该设备的LINKWATCH事件正在被调度中,同一时间段内同一个设备只能有
	// 一个LINKWATCH事件被调度,因为调度的多了也是浪费资源,完全没有必要
	if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
		// 设备没有LINKWATCH事件被调度,所以增加引用计数并将该设备加入到系统的LINKWATCH事件链表中
		dev_hold(dev);
		linkwatch_add_event(dev);
	} else if (!urgent)
		// 非紧急事件,并且该设备的LINKWATCH事件已经在调度中了,不再重复定都
		return;

	// 紧急事件、或者发生了首次调度
	linkwatch_schedule_work(urgent);
}

linkwatch事件列表

系统中会有多个网络设备,这些设备都有可能会有LINKWATCH事件产生,这些来自不同设备的LINKWATCH需要组织起来。内核是使用一个简单的链表组织的。

static struct net_device *lweventlist;
static DEFINE_SPINLOCK(lweventlist_lock);

// 将网络设备对象加入linkwatch表中
static void linkwatch_add_event(struct net_device *dev)
{
	unsigned long flags;

	spin_lock_irqsave(&lweventlist_lock, flags);
	dev->link_watch_next = lweventlist;
	lweventlist = dev;
	spin_unlock_irqrestore(&lweventlist_lock, flags);
}

事件的处理

通过linkwatch_schedule_work()调度事件,这里涉及到延迟任务的执行原理,不是我们关注的重点,直接看处理事件的处理。

static unsigned long linkwatch_nextevent;

static void __linkwatch_run_queue(int urgent_only)
{
	struct net_device *next;

	/*
	 * Limit the number of linkwatch events to one
	 * per second so that a runaway driver does not
	 * cause a storm of messages on the netlink
	 * socket.  This limit does not apply to up events
	 * while the device qdisc is down.
	 */
	// 对于非紧急事件,下次延迟任务的执行最少在1s以后
	if (!urgent_only)
		linkwatch_nextevent = jiffies + HZ;
	/* Limit wrap-around effect on delay. */
	else if (time_after(linkwatch_nextevent, jiffies + HZ))
		linkwatch_nextevent = jiffies;
    // 正在处理,清除LW_URGENT标志
	clear_bit(LW_URGENT, &linkwatch_flags);

    // 处理LINKWATCH事件队列
	spin_lock_irq(&lweventlist_lock);
	next = lweventlist;
	lweventlist = NULL;
	spin_unlock_irq(&lweventlist_lock);
	while (next) {
		struct net_device *dev = next;

		next = dev->link_watch_next;
        // 只处理紧急事件,但当前事件不是紧急事件,那么等待正常调度到后执行
		if (urgent_only && !linkwatch_urgent_event(dev)) {
			linkwatch_add_event(dev);
			continue;
		}
		/*
		 * Make sure the above read is complete since it can be
		 * rewritten as soon as we clear the bit below.
		 */
		smp_mb__before_clear_bit();

		/* We are about to handle this device,
		 * so new events can be accepted
		 */
		// 清除PENDING标记
		clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);

		rfc2863_policy(dev);
		if (dev->flags & IFF_UP) {
		    // 根据状态使能或者禁用网络设备
			if (netif_carrier_ok(dev))
				dev_activate(dev);
			else
				dev_deactivate(dev);
            // 设备状态发生了变化,通过RT_NETLINK通知用户空间
			netdev_state_change(dev);
		}

		dev_put(dev);
	}
    // 如果LINKWATCH队列非空,触发下一次延迟任务的执行(上述处理过程中,外部可能又设置了该队列)
	if (lweventlist)
		linkwatch_schedule_work(0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值