從前面的章節我們知道,網絡接口(如以太網接口)是硬件接口,(提示:網絡接口又可以稱之為網卡,為了統一,下文均采用網卡表示網絡接口),LwIP是軟件,那么怎么讓硬件與軟件無縫連接起來呢?而且,網卡又有多種多樣,怎么能讓LwIP使用同樣的軟件能兼容不同的硬件呢?原來LwIP使用一個數據結構——netif來描述一個網卡,但是由于網卡是直接與硬件打交道的,硬件不同則處理基本是不同的,所以必須由用戶提供最底層接口函數,LwIP提供統一的接口,但是底層的實現需要用戶自己去完成,比如網卡的初始化,網卡的收發數據,當LwIP底層得到了網絡的數據之后,才會傳入內核中去處理;同理,LwIP內核需要發送一個數據包的時候,也需要調用網卡的發送函數,這樣子才能把數據從硬件接口到軟件內核無縫連接起來。LwIP中的 ethernetif.c文件即為底層接口的驅動的模版,用戶為自己的網絡設備實現驅動時應參照此模塊做修改。ethernetif.c文件中的函數通常為與硬件打交道的底層函數,當有數據需要通過網卡接收或者發送數據的時候就會被調用,經過LwIP協議棧內部進行處理后,從應用層就能得到數據或者可以發送數據。
簡單來說,netif是LwIP抽象出來的網卡,LwIP協議棧可以使用多個不同的接口,而ethernetif.c文件則提供了netif訪問各種不同的網卡,每個網卡有不同的實現方式,用戶只需要修改ethernetif.c文件即可。
在單網卡中,這個netif結構體只有一個,可能還有人會問,那么一個設備中有多個網卡怎么辦,很簡單,LwIP會將每個用netif描述的網卡連接成一個鏈表(單向鏈表),該鏈表就記錄每個網卡的netif。屏蔽硬件接口的差異,完成了對不同網卡的抽象,因此了解netif結構體是移植LwIP的關鍵。
我們可以理解將整個網絡的數據傳輸理解為物流,那么網卡就是不同的運輸工具,我們可以選擇汽車、飛機、輪船等運輸工具,不同的運輸工具速度是不一樣的,但是對于一個物流公司而言,可能同時存在很多種運輸的工具,這就需要物流公司去記錄這些運輸工具,當有一個包裹需要通過飛機運輸出去,那么物流公司就會將這個包裹通過飛機發送出去,這就好比我們的網卡,需要哪個網卡發送或者接收網絡數據的時候,就會讓對應的網卡去工作。
下面一起來看看netif數據結構是怎么樣的,具體見代碼清單 4?1。
```
1 struct netif
2 {
3 #if !LWIP_SINGLE_NETIF
4 /* 指向netif鏈表中的下一個 */
5 struct netif *next; (1)
6 #endif
7
8 #if LWIP_IPV4
9 /* 網絡字節中的IP地址、子網掩碼、默認網關配置 */
10 ip_addr_t ip_addr;
11 ip_addr_t netmask;
12 ip_addr_t gw; (2)
13 #endif /* LWIP_IPV4 */
14
15 /* 此函數由網絡設備驅動程序調用,將數據包傳遞到TCP/IP協議棧。
16 * 對于以太網物理層,這通常是ethernet_input()*/
17 netif_input_fn input; (3)
18
19 #if LWIP_IPV4
20
21 /* 此函數由IP層調用,在接口上發送數據包。通常這個功能,
22 * 首先解析硬件地址,然后發送數據包。
23 * 對于以太網物理層,這通常是etharp_output() */
24 netif_output_fn output; (4)
25
26 #endif /* LWIP_IPV4 */
27 /* 此函數由ethernet_output()調用,當需要在網卡上發送一個數據包時。
28 * 底層硬件輸出數據函數,一般是調用自定義函數low_level_output*/
29 netif_linkoutput_fn linkoutput; (5)
30
31 #if LWIP_NETIF_STATUS_CALLBACK
32 /* 當netif狀態設置為up或down時調用此函數 */
33 netif_status_callback_fn status_callback; (6)
34 #endif /* LWIP_NETIF_STATUS_CALLBACK */
35
36 #if LWIP_NETIF_LINK_CALLBACK
37 /* 當netif鏈接設置為up或down時,將調用此函數 */
38 netif_status_callback_fn link_callback; (7)
39 #endif /* LWIP_NETIF_LINK_CALLBACK */
40
41 #if LWIP_NETIF_REMOVE_CALLBACK
42 /* 當netif被刪除時調用此函數 */
43 netif_status_callback_fn remove_callback; (8)
44 #endif /* LWIP_NETIF_REMOVE_CALLBACK */
45
46 /* 此字段可由設備驅動程序設置并指向設備的狀態信息。
47 * 主要是將網卡的某些私有數據傳遞給上層,用戶可以自由發揮,也可以不用。*/
48 void *state; (9)
49
50 #ifdef netif_get_client_data
51 void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
52 #endif
53 #if LWIP_NETIF_HOSTNAME
54 /* 這個netif的主機名,NULL也是一個有效值 */
55 const char* hostname;
56 #endif /* LWIP_NETIF_HOSTNAME */
57
58 #if LWIP_CHECKSUM_CTRL_PER_NETIF
59 u16_t chksum_flags;
60 #endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
61
62 /** 最大傳輸單位(以字節為單位),對于以太網一般設為 1500 */
63 u16_t mtu; (10)
64
65 /** 此網卡的鏈路層硬件地址 */
66 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; (11)
67
68 /** 硬件地址長度,對于以太網就是 MAC 地址長度,為6字節 */
69 u8_t hwaddr_len; (12)
70
71 /* 網卡狀態信息標志位,是很重要的控制字段,
72 * 它包括網卡功能使能、廣播使能、 ARP 使能等等重要控制位。 */
73 u8_t flags; (13)
74
75 /* 字段用于保存每一個網卡的名字。用兩個字符的名字來標識網絡接
76 * 口使用的設備驅動的種類,名字由設備驅動來設置并且應該反映通過網卡
77 * 表示的硬件的種類。比如藍牙設備( bluetooth)的網卡名字可以是 bt,
78 * 而 IEEE 802.11b WLAN 設備的名字就可以是wl,當然設置什么名字用戶是可
79 * 以自由發揮的,這并不影響用戶對網卡的使用。當然,如果兩個網卡
80 * 具有相同的網絡名字,我們就用 num 字段來區分相同類別的不同網卡*/
81 char name[2]; (14)
82
83 /* 用來標示使用同種驅動類型的不同網卡 */
84 u8_t num; (15)
85
86 #if MIB2_STATS
87 /* 連接類型 */
88 u8_t link_type;
89 /* 連接速度 */
90 u32_t link_speed;
91 /* 最后一次更改的時間戳 */
92 u32_t ts;
93 /** counters */
94 struct stats_mib2_netif_ctrs mib2_counters;
95 #endif /* MIB2_STATS */
96
97 #if LWIP_IPV4 && LWIP_IGMP
98 /** 可以調用此函數來添加或刪除多播中的條目
99 以太網MAC的過濾表。*/
100 netif_igmp_mac_filter_fn igmp_mac_filter;
101 #endif /* LWIP_IPV4 && LWIP_IGMP */
102
103 #if LWIP_NETIF_USE_HINTS
104 struct netif_hint *hints;
105 #endif /* LWIP_NETIF_USE_HINTS */
106
107 #if ENABLE_LOOPBACK
108 /* List of packets to be queued for ourselves. */
109 struct pbuf *loop_first;
110 struct pbuf *loop_last;
111
112 #if LWIP_LOOPBACK_MAX_PBUFS
113 u16_t loop_cnt_current;
114 #endif /* LWIP_LOOPBACK_MAX_PBUFS */
115
116 #endif /* ENABLE_LOOPBACK */
117 };
```
我們挑一些比較重要的netif字段進行講解:
* (1):LwIP使用鏈表來管理同一設備的多個網卡。在netif.c文件中定義兩個全局指針:struct netif *netif_list和struct netif *netif_default,其中netif_list就是網卡鏈表指針,指向網卡鏈表的首節點(第一個網卡),后者表示默認情況下(有多網口時)使用哪個網卡。next字段指向下一個netif結構體指針,在一個設備中有多個網卡時,才使用該字段。
* (2):ip_addr字段記錄的是網絡中的IP地址,netmask字段記錄的是子網掩碼,gw記錄的是網關地址,這些字段是用于描述網卡的網絡地址屬性。
IP地址必須與網卡對應,即設備擁有多少個網卡那就必須有多少個IP地址;子網掩碼可以用來判斷某個IP地址與當前網卡是否處于同一個子網中,IP在發送數據包的時候會選擇與目標IP地址處于同一子網的網卡來發送;網關地址在數據包的發送、轉發過程非常重要,如果要向不屬于同一子網的主機(主機目標IP地址與網卡不屬于同一子網)發送一個數據包,那么LwIP就會將數據包發送到網關中,網關設備會對該數據包進行正確的轉發,除此之外,網關還提供很多高級功能,如DNS,DHCP等。
* (3):input是一個函數指針,指向一個函數,該函數由網絡設備驅動程序調用,將數據包傳遞到TCP/IP協議棧(IP層)。對于以太網物理層,這通常是ethernet_input(),參數為pbuf和netif類型,其中pbuf為接收到的數據包。
* (4):output也是一個函數指針,指向一個函數,此函數由IP層調用,在接口上發送數據包。用戶需要編寫該函數并使output指向它,通這個函數的處理步驟是首先解析硬件地址,然后發送數據包。對于以太網物理層,該函數通常是etharp_output(),參數為pbuf、netif和ip_addr類型,其中,ipaddr代表要將該數據包發送到的地址,但不一定是數據包最終到到達的IP地址,比如,要發送IP數據報到一個并不在本網絡的主機上,該數據包要被發送到一個路由器上,這里的ipaddr就是路由器IP地址。
* (5):linkoutput字段和output類似,也需要用戶自己實現一個函數,但只有兩個參數,它是由ARP模塊調用的,一般是自定義函數low_level_output()。當需要在網卡上發送一個數據包時,該函數會被ethernet_output()函數調用。
* (6):當netif狀態設置為up或down時,將調用此函數。
* (7):當netif連接設置為up或down時,將調用此函數。
* (8):當netif被刪除時調用此函數。
* (9):此字段可由設備驅動程序設置并指向設備的狀態信息。主要是將網卡的某些私有數據傳遞給上層,用戶可以自由發揮,也可以不用。
* (10):最大傳輸單位(以字節為單位),對于以太網一般設為 1500,在IP層發送數據的時候,LwIP會使用該字段決定是否需要對數據包進行分片處理,為什么是在IP層進行分片處理?因為鏈路層不提供任何的差錯處理機制,如果在網卡中接收的數據包不滿足網卡自身的屬性,那么網卡可能就會直接丟棄該數據包,也可能在底層進行分包發送,但是這種分包在IP層看來是不可接受的,因為它打亂了數據的結構,所以只能由IP層進行分片處理。
* (11):此網卡的鏈路層硬件地址。
* (12):硬件地址長度,對于以太網就是 MAC 地址長度,為6字節
* (13):網卡狀態信息標志位,是很重要的控制字段,它包括網卡功能使能、廣播使能、 ARP 使能等等重要控制位。
* (14):name字段用于保存每一個網卡的名字。用兩個字符的名字來標識網卡使用的設備驅動的種類,名字由設備驅動來設置并且應該反映通過網卡表示的硬件的種類。比如藍牙設備(bluetooth)的網卡名字可以是 bt,而 IEEE 802.11b WLAN設備的名字就可以是wl,當然設置什么名字用戶是可以自由發揮的,這并不影響用戶對網卡的使用。當然,如果兩個網卡具有相同的網絡名字,我們就用 num字段來區分相同類別的不同網卡。
* (15):用來標識使用同種驅動類型的不同網卡。
- 說明
- 第1章:網絡協議簡介
- 1.1:常用網絡協議
- 1.2:網絡協議的分層模型
- 1.3:協議層報文間的封裝與拆封
- 第2章:LwIP簡介
- 2.1:LwIP的優缺點
- 2.2:LwIP的文件說明
- 2.2.1:如何獲取LwIP源碼文件
- 2.2.2:LwIP文件說明
- 2.3:查看LwIP的說明文檔
- 2.4:使用vscode查看源碼
- 2.4.1:查看文件中的符號列表(函數列表)
- 2.4.2:函數定義跳轉
- 2.5:LwIP源碼里的example
- 2.6:LwIP的三種編程接口
- 2.6.1:RAW/Callback API
- 2.6.2:NETCONN API
- 2.6.3:SOCKET API
- 第3章:開發平臺介紹
- 3.1:以太網簡介
- 3.1.1:PHY層
- 3.1.2:MAC子層
- 3.2:STM32的ETH外設
- 3.3:MII 和 RMII 接口
- 3.4:PHY:LAN8720A
- 3.5:硬件設計
- 3.6:軟件設計
- 3.6.1:獲取STM32的裸機工程模板
- 3.6.2:添加bsp_eth.c與bsp_eth.h
- 3.6.3:修改stm32f4xx_hal_conf.h文件
- 第4章:LwIP的網絡接口管理
- 4.1:netif結構體
- 4.2:netif使用
- 4.3:與netif相關的底層函數
- 4.4:ethernetif.c文件內容
- 4.4.1:ethernetif數據結構
- 4.4.2:ethernetif_init()
- 4.4.3:low_level_init()
- 第5章:LwIP的內存管理
- 5.1:幾種內存分配策略
- 5.1.1:固定大小的內存塊
- 5.1.2:可變長度分配
- 5.2:動態內存池(POOL)
- 5.2.1:內存池的預處理
- 5.2.2:內存池的初始化
- 5.2.3:內存分配
- 5.2.4:內存釋放
- 5.3:動態內存堆
- 5.3.1:內存堆的組織結構
- 5.3.2:內存堆初始化
- 5.3.3:內存分配
- 5.3.4:內存釋放
- 5.4:使用C庫的malloc和free來管理內存
- 5.5:LwIP中的配置
- 第6章:網絡數據包
- 6.1:TCP/IP協議的分層思想
- 6.2:LwIP的線程模型
- 6.3:pbuf結構體說明
- 6.4:pbuf的類型
- 6.4.1:PBUF_RAM類型的pbuf
- 6.4.2:PBUF_POOL類型的pbuf
- 6.4.3:PBUF_ROM和PBUF_REF類型pbuf
- 6.5:pbuf_alloc()
- 6.6:pbuf_free()
- 6.7:其它pbuf操作函數
- 6.7.1:pbuf_realloc()
- 6.7.2:pbuf_header()
- 6.7.3:pbuf_take()
- 6.8:網卡中使用的pbuf
- 6.8.1:low_level_output()
- 6.8.2:low_level_input()
- 6.8.3:ethernetif_input()
- 第7章:無操作系統移植LwIP
- 7.1:將LwIP添加到裸機工程
- 7.2:移植頭文件
- 7.3:移植網卡驅動
- 7.4:LwIP時基
- 7.5:協議棧初始化
- 7.6:獲取數據包
- 7.6.1:查詢方式
- 7.6.2:ping命令詳解
- 7.6.3:中斷方式
- 第8章:有操作系統移植LwIP
- 8.1:LwIP中添加操作系統
- 8.1.1:拷貝FreeRTOS源碼到工程文件夾
- 8.1.2:添加FreeRTOS源碼到工程組文件夾
- 8.1.3:指定FreeRTOS頭文件的路徑
- 8.1.4:修改stm32f10x_it.c
- 8.2:lwipopts.h文件需要加入的配置
- 8.3:sys_arch.c/h文件的編寫
- 8.4:網卡底層的編寫
- 8.5:協議棧初始化
- 8.6:移植后使用ping測試基本響應
- 第9章:LwIP一探究竟
- 9.1:網卡接收數據的流程
- 9.2:內核超時處理
- 9.2.1:sys_timeo結構體與超時鏈表
- 9.2.2:注冊超時事件
- 9.2.3:超時檢查
- 9.3:tcpip_thread線程
- 9.4:LwIP中的消息
- 9.4.1:消息結構
- 9.4.2:數據包消息
- 9.4.3:API消息
- 9.5:揭開LwIP神秘的面紗
- 第10章:ARP協議
- 10.1:鏈路層概述
- 10.2:MAC地址的基本概念
- 10.3:初識ARP
- 10.4:以太網幀結構
- 10.5:IP地址映射為物理地址
- 10.6:ARP緩存表
- 10.7:ARP緩存表的超時處理
- 10.8:ARP報文
- 10.9:發送ARP請求包
- 10.10:數據包接收流程
- 10.10.1:以太網之數據包接收
- 10.10.2:ARP數據包處理
- 10.10.3:更新ARP緩存表
- 10.11:數據包發送流程
- 10.11.1:etharp_output()函數
- 10.11.2:etharp_output_to_arp_index()函數
- 10.11.3:etharp_query()函數
- 第11章:IP協議
- 11.1:IP地址.md
- 11.1.1:概述
- 11.1.2:IP地址編址
- 11.1.3:特殊IP地址