1. 程式人生 > >裝置介面層之裝置狀態管理

裝置介面層之裝置狀態管理

從前面的資料收發過程中也看到了,在收發流程中很多檢查裝置狀態的操作,這篇筆記來完整的看下在設備註冊成功後,到底有哪些狀態,它們是如何控制收發流程的。

1. 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_XOFF=0, __LINK_STATE_START, __LINK_STATE_PRESENT, __LINK_STATE_SCHED, __LINK_STATE_NOCARRIER, __LINK_STATE_LINKWATCH_PENDING, __LINK_STATE_DORMANT, __LINK_STATE_QDISC_RUNNING, };

1.1 __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); }

1.2 __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);
}

1.3 __LINK_STATE_XOFF

表示裝置的傳送佇列是否可用。該標誌位的設定與清除一般由驅動程式負責,因為只有驅動程式才知道硬體當前是否能夠傳送資料,驅動程式可能會裝置沒有可用快取、記憶體不足等等因素而臨時將該標誌位清除,進而停止流量控制機制傳送資料。

可以使用如下三個介面操作和檢查該標誌位:

/**
 *	netif_start_queue - allow transmit
 *	@dev: network device
 *
 *	Allow upper layers to call the device hard_start_xmit routine.
 */
static inline void netif_start_queue(struct net_device *dev)
{
	clear_bit(__LINK_STATE_XOFF, &dev->state);
}

/**
 *	netif_wake_queue - restart transmit
 *	@dev: network device
 *
 *	Allow upper layers to call the device hard_start_xmit routine.
 *	Used for flow control when transmit resources are available.
 */
static inline void netif_wake_queue(struct net_device *dev)
{
#ifdef CONFIG_NETPOLL_TRAP
	if (netpoll_trap()) {
		clear_bit(__LINK_STATE_XOFF, &dev->state);
		return;
	}
#endif
	//和netif_stop_queue()不同,這裡還多了排程裝置使其可以傳送的操作
	//關於排程見筆記《裝置介面層之資料包傳送》
	if (test_and_clear_bit(__LINK_STATE_XOFF, &dev->state))
		__netif_schedule(dev);
}

/**
 *	netif_stop_queue - stop transmitted packets
 *	@dev: network device
 *
 *	Stop upper layers calling the device hard_start_xmit routine.
 *	Used for flow control when transmit resources are unavailable.
 */
static inline void netif_stop_queue(struct net_device *dev)
{
	set_bit(__LINK_STATE_XOFF, &dev->state);
}

/**
 *	netif_queue_stopped - test if transmit queue is flowblocked
 *	@dev: network device
 *
 *	Test if transmit queue on device is currently unable to send.
 */
static inline int netif_queue_stopped(const struct net_device *dev)
{
	return test_bit(__LINK_STATE_XOFF, &dev->state);
}

1.4 __LINK_STATE_SCHED

表示裝置是否已經在傳送輪詢佇列中,即CPU的收發佇列softnet_data.output_queue中,一個裝置同時只能在一個CPU的傳送輪詢佇列中,在流量控制傳送過程qdisc_run()過程中會檢查和設定該標誌位。

static inline void netif_schedule(struct net_device *dev)
{
	if (!test_bit(__LINK_STATE_XOFF, &dev->state))
		__netif_schedule(dev);
}

void __netif_schedule(struct net_device *dev)
{
	//如果沒有就設定__LINK_STATE_SCHED標誌
	if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state)) {
		unsigned long flags;
		struct softnet_data *sd;

		//將裝置接入當前CPU的傳送輪詢佇列中
		local_irq_save(flags);
		sd = &__get_cpu_var(softnet_data);
		dev->next_sched = sd->output_queue;
		sd->output_queue = dev;
		//啟用傳送輪詢軟中斷
		raise_softirq_irqoff(NET_TX_SOFTIRQ);
		local_irq_restore(flags);
	}
}
EXPORT_SYMBOL(__netif_schedule);

這裡需要解釋下接收過程為什麼沒有這樣一個類似的標誌位。其實是有的,只是該標誌位不在dev->state中,而是在napi_struct.state中,具體可參考裝置介面層之資料包傳送.

1.5 __LINK_STATE_QDISC_RUNNING

表示流量控制過程是否正在執行,即是否正在qdisc_run()函式執行過程中。相關程式碼如下:

static inline void qdisc_run(struct net_device *dev)
{
	//1. 裝置的傳送佇列開啟(__LINK_STATE_XOFF標誌位沒有設定)
	//2. 裝置沒有被其它CPU排程傳送(__LINK_STATE_QDISC_RUNNING標誌位沒有置位)
	//如果滿足上述兩個條件,那麼設定排程標記,然後呼叫__qdisc_run()
	if (!netif_queue_stopped(dev) &&
	    !test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state))
		__qdisc_run(dev);
}

void __qdisc_run(struct net_device *dev)
{
...
	//退出該函式時清除__LINK_STATE_QDISC_RUNNING標誌位
	clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state);
}

1.6 __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);
}

1.7 __LINK_STATE_LINKWATCH_PENDING

如果裝置已經產生了LINKWATCH事件並且正在排程過程中,設定該標誌位。這是為了防止同一個裝置同時被多次排程。

1.8 __LINK_STATE_DORMANT

此標誌位在當前的收發流程中還沒有見到怎麼使用,暫不關注。

2. 開啟裝置dev_open()

裝置被註冊後還無法收發資料,這時網路裝置的狀態為DOWN,必須UP後才能收發資料,在使用者空間,可以通過ifconifg {dev} up來使能裝置,對應核心,該過程由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)
{
	int ret = 0;

	//如果該網路裝置已經UP,直接返回成功
	if (dev->flags & IFF_UP)
		return 0;

	//裝置不能處於掛起狀態,已經註冊並且沒有被掛起的裝置會設定__LINK_STATE_PRESENT
	if (!netif_device_present(dev))
		return -ENODEV;

	//設定網路裝置處於啟用狀態
	set_bit(__LINK_STATE_START, &dev->state);
	//如果裝置驅動提供了校驗地址的介面,則呼叫它
	if (dev->validate_addr)
		ret = dev->validate_addr(dev);
	//如果裝置驅動提供了open回撥,則呼叫它
	if (!ret && dev->open)
		ret = dev->open(dev);

	if (ret)
		//校驗地址或者驅動的open返回失敗,清除__LINK_STATE_START標誌位
		clear_bit(__LINK_STATE_START, &dev->state);
	else {
		//設定UP標記
		dev->flags |= IFF_UP;

		//回撥set_rx_mode()和set_multicast_list(),這操作與裝置強相關,
		//沒寫過網絡卡驅動,不知道要幹什麼
		dev_set_rx_mode(dev);
		/*
		 *	Wakeup transmit queue engine
		 */
		//設定流量控制的排隊規則,具體見相關筆記
		dev_activate(dev);

		//向其他模組傳送NETDEV_UP通知事件
		call_netdevice_notifiers(NETDEV_UP, dev);
	}

	return ret;
}

3. 裝置的關閉dev_close()

有開啟就有關閉,使用者空間通過ifconfig {dev} 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)
{
	might_sleep();
	//如果裝置已經關閉,直接返回成功
	if (!(dev->flags & IFF_UP))
		return 0;

	/*
	 *	Tell people we are going down, so that they can
	 *	prepare to death, when device is still operating.
	 */
	//向其他模組傳送NETDEV_GOING_DOWN通知,表示裝置即將被關閉
	call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);
	//清除__LINK_STATE_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.
	 */
	//等待其它裝置也感知到該標記的變化,這是一同同步機制,不是很瞭解
	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.
	 */
	//呼叫驅動程式的stop()完成硬體相關的關閉操作
	dev->stop(dev);

	//網路裝置設定為DOWN
	dev->flags &= ~IFF_UP;

	/*
	 * Tell people we are down
	 */
	//關閉結束,向其它模組傳送NETDEV_DOWN事件通知
	call_netdevice_notifiers(NETDEV_DOWN, dev);

	return 0;
}

4. 裝置的掛起

當電源管理模組通知裝置系統即將進入休眠態時,驅動程式必須要執行休眠準備工作,在該過程中,驅動需要呼叫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);
	}
}

5. 裝置的恢復

同上,裝置退出休眠態時,驅動程式必須要執行恢復工作,在該過程中,驅動需要呼叫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);
	}
}

6. 連線狀態偵測

驅動程式可以感知到網路裝置是否處於可用狀態的變化,比如網線是否掉了等事件,這些事件可以簡單的劃分為可用和不可用兩種狀態。當這兩個事件發生時,驅動程式應該將這種狀態傳遞給網路裝置介面層,這是通過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))
    	//也產生一個鏈路狀態變化事件
		linkwatch_fire_event(dev);
}

6.1 鏈路變化狀態事件

6.1.1 事件的產生

如上,鏈路狀態變化的事件最先由驅動程式感知到,並最後呼叫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);
}

6.1.2 事件列表

系統中會有多個網路裝置,這些裝置都有可能會有LINKWATCH事件產生,這些來自不同裝置的LINKWATCH需要組織起來。核心是使用一個簡單的連結串列組織的。

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

6.1.3 事件的排程

一旦核心的事件佇列不為空,那麼就應該觸發排程,使得這些事件能夠被及時的處理,這是通過linkwatch_schedule_work()完成的。這裡涉及到延遲任務的執行原理,不是我們關注的重點,我們直接看處理事件的函式實現:

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
		 */
		//清除LINKWATCH_PENDING標記
		clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);

		rfc2863_policy(dev);
		if (dev->flags & IFF_UP) {
			//裝置在開啟的情況下,根據當前鏈路狀態是否ok分別開啟和關閉傳送佇列
			if (netif_carrier_ok(dev)) {
				WARN_ON(dev->qdisc_sleeping == &noop_qdisc);
				dev_activate(dev);
			} else
				dev_deactivate(dev);
			//裝置狀態發生了變化,通過RT_NETLINK通知使用者空間
			netdev_state_change(dev);
		}

		dev_put(dev);
	}
	//如果LINKWATCH佇列非空,觸發下一次延遲任務的執行
	if (lweventlist)
		linkwatch_schedule_work(0);
}

注意:__linkwatch_run_queue()中涉及到了延遲任務執行機制的一些內容,可以忽略,重點把握對dev_activate()和dev_deactivate()的呼叫。