裝置介面層之裝置狀態管理
從前面的資料收發過程中也看到了,在收發流程中很多檢查裝置狀態的操作,這篇筆記來完整的看下在設備註冊成功後,到底有哪些狀態,它們是如何控制收發流程的。
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()告訴框架裝置即將休眠。
在掛起時,需要幹兩件事:
- 清除__LINK_STATE_PRESENT標誌位,使得裝置暫時不可用;
- 如果裝置已經開啟(呼叫過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()告訴框架裝置即將恢復。
恢復裝置時,幹兩件事:
- 重新社長是__LINK_STATE_PRESENT標誌位;
- 如果裝置已經開啟,那麼開啟發送佇列並且重新排程裝置使其可以傳送
/**
* 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()的呼叫。