[文/告別年代 Email:byeyear@hotmail.com]
重大修訂記錄
-----------------------------------------
2016.11.03
感謝@ wayne88 指出(見文后討論帖),對本文所述的NETIF_FLAG_UP標記和有效IP地址之間的關系,lwip代碼作者其實是存有疑慮的。見以下鏈接:
http://savannah.nongnu.org/bugs/?func=detailitem&item_id=37068
所幸該鏈接不會影響本文內容。對1.4.1版代碼,代碼作者仍然采用了如本文所述的、他認為可能是“unclear”的做法。
注意
-----------------------------------------
本文基於lwip-1.4.1,若文章與代碼對不上,請確認代碼版本。
基本流程
-----------------------------------------
A. link up -> link down:
關閉MAC和DMA;
調用netif_set_link_down
B. link down -> link up:
打開MAC和DMA;
調用netif_set_link_up
C. 注意:當使用RAW API時,所有使用RAW API的代碼(包括處理鏈路狀態的代碼)必須運行於同一線程環境。
-----------------------------------------
代碼分析
-----------------------------------------
有四個函數和兩個標志位和鏈路狀態改變有關:
A. netif_set_up
該函數設置NETIF_FLAG_UP標記,並在鏈路已up(NETIF_FLAG_LINK_UP有效)的情況下發送arp探測。
/* 刪除了部分預編譯條件和無關代碼 */ void netif_set_up(struct netif *netif) { if (!(netif->flags & NETIF_FLAG_UP)) { netif->flags |= NETIF_FLAG_UP; /* 鏈路有效情況下發送ARP探測 */ if (netif->flags & NETIF_FLAG_LINK_UP) { if (netif->flags & (NETIF_FLAG_ETHARP)) { etharp_gratuitous(netif); } } } }
如果你的網絡使用靜態IP,那么在lwip初始化時調用該函數;
如果你的網絡使用DHCP,那么DHCP成功后lwip會幫你調用netif_set_up。
B. netif_set_down
除非你需要關閉網絡,否則一般不需要主動調用該函數。
C. netif_set_link_up
該函數設置NETIF_FLAG_LINK_UP標記,啟動DHCP和AutoIP,並在NETIF_FLAG_UP標記有效的情況下發送arp探測。
void netif_set_link_up(struct netif *netif ) { if (!(netif->flags & NETIF_FLAG_LINK_UP)) { netif->flags |= NETIF_FLAG_LINK_UP; if (netif->dhcp) { dhcp_network_changed(netif); } /* ...其它代碼(autoip相關)... */ if (netif->flags & NETIF_FLAG_UP) { if (netif->flags & NETIF_FLAG_ETHARP) { etharp_gratuitous(netif); } } NETIF_LINK_CALLBACK(netif); } }
當驅動程序檢測到鏈路從down變化為up時,重新初始化MAC和DMA,然后調用該函數。
注意:初始化時如果鏈路有效,low_level_init直接設置NETIF_FLAG_LINK_UP,而不調用netif_set_link_up函數,避免在lwip沒有完全初始化好時啟動DHCP。
D. netif_set_link_down
當驅動程序檢測到鏈路從up變為down時調用該函數,並關閉MAC和DMA。
E. NETIF_FLAG_UP和NETIF_FLAG_LINK_UP
前者表示lwip協議棧已經就緒(已得到合法IP地址,且協議棧已准備好收發數據包);后者表示鏈路層有效。
或者說,一個是軟件(協議棧)就緒標志,一個是硬件(鏈路層)就緒標志。
F. 參考lwip代碼中的netif.h文件對這兩個標記的詳細說明。
網上很多port代碼不管是否使用DHCP都在lwip初始化時設置NETIF_FLAG_UP,這是*不*正確的。
NETIF_FLAG_UP必須在獲得有效IP地址后才能置位,以保證順利發送ARP探測。
所以,如果使用了DHCP,那么初始化時就不能設置NETIF_FLAG_UP。
LwIP會在鏈路有效情況下發送DHCP請求,並在拿到有效IP地址后為你設置NETIF_FLAG_UP。
情景分析
--------------------------------------------------------------
A. 靜態IP,初始化時鏈路有效
這是最簡情況。驅動層會看到Link有效,並直接設置NETIF_FLAG_LINK_UP。
隨后lwip初始化過程中調用netif_set_up,該函數設置NETIF_FLAG_UP,並發送arp探測。
B. 靜態IP,初始化時鏈路無效
驅動層不會設置NETIF_FLAG_LINK_UP。
lwip初始化過程會調用netif_set_up,該函數看到沒有link,除了設置NETIF_FLAG_UP標記外不會做任何事。
當鏈路變為有效后,驅動層調用netif_set_link_up,發送arp探測。
C. 動態IP,初始化時鏈路有效
驅動層直接設置NETIF_FLAG_LINK_UP。
lwip初始化過程中調用dhcp_start,啟動DHCP過程。
但lwip初始化過程不會調用netif_set_up,因為還沒有獲得有效IP地址。
獲得有效IP后,lwip會幫我們調用netif_set_up,發送arp探測。
D. 動態IP,初始化時鏈路無效
驅動層不會設置NETIF_FLAG_LINK_UP。
lwip初始化過程中仍然會調用dhcp_start。對dhcp_start的調用是必須的,因為dhcp過程需要的資源將在該函數中分配。
等到鏈路有效后驅動層調用netif_set_link_up,該函數會啟動DHCP過程。
獲得有效IP后,lwip會幫我們調用netif_set_up,發送arp探測。
E. 靜態IP,鏈路斷開后重建
鏈路斷開時netif_set_link_down,重建后netif_set_link_up發送arp探測。
NETIF_FLAG_UP在設置后就一直有效。
F. 動態IP,鏈路斷開后重建
鏈路重建后netif_set_link_up重新啟動DHCP過程,該過程會清除NETIF_FLAG_UP標記。
DHCP完成后lwip自動調用netif_set_up重新置位該標記並發起arp探測。
總結
-------------------------------------------------------------------------------------
1. 使用靜態IP
low_level_init根據當前鏈路狀態設置或不設置NETIF_FLAG_LINK_UP;
lwip初始化時調用netif_set_up;
鏈路狀態改變時調用netif_set_link_up/netif_set_link_down。
2. 使用動態IP
low_level_init根據當前鏈路狀態設置或不設置NETIF_FLAG_LINK_UP;
lwip初始化時調用dhcp_start;
鏈路狀態改變時調用netif_set_link_up/netif_set_link_down;
不要在使用動態IP時直接調用netif_set_up,而是由lwip協議棧代碼在成功獲得IP后為你調用這個函數。
[文/告別年代 Email:byeyear@hotmail.com]