操作系統環境下, LwIP移植的核心就是編寫與操作系統相關的接口文件sys\_arch.c和sys\_arch.h,這兩個文件可以自己創建也可以從contrib包中獲取,路徑分別為“contrib-2.1.0\\ports\\freertos”與“contrib-2.1.0\\ports\\freertos\\include\\arch”,用戶在移植的時候必須根據操作系統的功能為協議棧提供相應的接口,如郵箱(因為本次移植以FreeRTOS為例子,FreeRTOS中沒有郵箱這種概念,但是可以使用消息隊列替代,為了迎合LwIP中的命名,下文統一采用郵箱表示)、信號量、互斥量等,這些IPC通信機制是保證內核與上層API接口通信的基本保障,也是內核實現管理的繼承,同時在sys.h文件中聲明了用戶需要實現的所有函數框架,這些函數具體見表格 8?2。
| 名稱 | 屬性 | 功能 | 所在文件 |
| --- | --- | --- | --- |
| sys\_sem\_t | 數據類型 | 指針類型,指向系統信號量 | sys\_arch.h |
| sys\_mutex\_t | 數據類型 | 指針類型,指向系統互斥量 | sys\_arch.h |
| sys\_mbox\_t | 數據類型 | 指針類型,指向系統郵箱 | sys\_arch.h |
| sys\_thread\_t | 數據類型 | 指針類型,指向系統任務 | sys\_arch.h |
| SYS\_MBOX\_NULL | 宏定義 | 系統郵箱的空值 | sys\_arch.h |
| SYS\_SEM\_NULL | 宏定義 | 系統信號量的空值 | sys\_arch.h |
| SYS\_MRTEX\_NULL | 宏定義 | 系統互斥量的空值 | sys\_arch.h |
| sys\_now | 函數 | 內核時鐘 | sys\_arch.c |
| sys\_init | 函數 | 初始化系統 | sys\_arch.c |
| sys\_arch\_protect | 函數 | 進入臨界段 | sys\_arch.c |
| sys\_arch\_unprotect | 函數 | 退出臨界段 | sys\_arch.c |
| sys\_sem\_new | 函數 | 創建一個信號量 | sys\_arch.c |
| sys\_sem\_free | 函數 | 刪除一個信號量 | sys\_arch.c |
| sys\_sem\_valid | 函數 | 判斷信號量是否有效 | sys\_arch.c |
| sys\_sem\_set\_invalid | 函數 | 將信號量設置無效狀態 | sys\_arch.c |
| sys\_arch\_sem\_wait | 函數 | 等待一個信號量 | sys\_arch.c |
| sys\_sem\_signal | 函數 | 釋放一個信號量 | sys\_arch.c |
| sys\_mutex\_new | 函數 | 創建一個互斥量 | sys\_arch.c |
| sys\_mutex\_free | 函數 | 刪除一個互斥量 | sys\_arch.c |
| sys\_mutex\_set\_invalid | 函數 | 設置互斥量為無效狀態 | sys\_arch.c |
| sys\_mutex\_lock | 函數 | 獲取一個互斥量 | sys\_arch.c |
| sys\_mutex\_unlock | 函數 | 釋放一個互斥量 | sys\_arch.c |
| sys\_mbox\_new | 函數 | 創建一個郵箱 | sys\_arch.c |
| sys\_mbox\_free | 函數 | 刪除一個郵箱 | sys\_arch.c |
| sys\_mbox\_valid | 函數 | 判斷郵箱是否有效 | sys\_arch.c |
| sys\_mbox\_set\_invalid | 函數 | 設置郵箱為無效狀態 | sys\_arch.c |
| sys\_mbox\_post | 函數 | 向郵箱發送消息,一直阻塞 | sys\_arch.c |
| sys\_mbox\_trypost | 函數 | 向郵箱發送消息,非阻塞 | sys\_arch.c |
| sys\_mbox\_trypost\_fromisr | 函數 | 在中斷中向郵箱發送消息 | sys\_arch.c |
| sys\_arch\_mbox\_fetch | 函數 | 從郵箱中獲取消息,阻塞 | sys\_arch.c |
| sys\_arch\_mbox\_tryfetch | 函數 | 從郵箱中獲取消息,非阻塞 | sys\_arch.c |
| sys\_thread\_new | 函數 | 創建一個線程 | sys\_arch.c |
看到那么多函數,是不是頭都大了,其實這些函數的實現都是很簡單的,首先講解一下郵箱函數的實現。在LwIP中,用戶代碼與協議棧內部之間是通過郵箱進行數據的交互的,郵箱本質上就是一個指向數據的指針,API將指針傳遞給內核,內核通過這個指針訪問數據,然后去處理,反之內核將數據傳遞給用戶代碼也是通過郵箱將一個指針進行傳遞。
在操作系統環境下,LwIP會作為一個線程運行,線程的名字叫tcpip\_thread,在初始化LwIP的時候,內核就會自動創建這個線程,并且在線程運行的時候阻塞在郵箱上,等待數據進行處理,這個郵箱數據的來源可能在底層網卡接收到的數據或者上層應用程序的數據,總之,tcpip\_thread線程在獲取到郵箱中的數據時候,就會退出阻塞態,去處理數據,在處理完畢數據后又進入阻塞態中等待數據的到來,如此反復。
信號量與互斥量的實現為內核提供同步與互斥的機制,比如當用戶想要發送一個數據的時候,就會調用上層API接口,API接口就會去先發送一個數據給內核去處理,然后嘗試獲取一個信號量,因為此時是沒有信號量的,所以就會阻塞用戶線程;內核在知道用戶想要發送數據后,就會調用對應的網卡去發送數據,當數據發送完成后就釋放一個信號量告知用戶線程發送完成,這樣子用戶線程就得以繼續執行。
所以這些函數的接口都必須由用戶實現,下面具體看看這些函數的實現,具體見代碼清單 8?3。
```
1 #include "debug.h"
2
3 #include <lwip/opt.h>
4 #include <lwip/arch.h>
5
6 #include "tcpip.h"
7 #include "lwip/init.h"
8 #include "lwip/netif.h"
9 #include "lwip/sio.h"
10 #include "ethernetif.h"
11
12 #if !NO_SYS
13 #include "sys_arch.h"
14 #endif
15 #include <lwip/stats.h>
16 #include <lwip/debug.h>
17 #include <lwip/sys.h>
18
19 #include <string.h>
20
21 int errno;
22
23
24 u32_t lwip_sys_now;
25
26 struct sys_timeouts
27 {
28 struct sys_timeo *next;
29 };
30
31 struct timeoutlist
32 {
33 struct sys_timeouts timeouts;
34 xTaskHandle pid;
35 };
36
37 #define SYS_THREAD_MAX 4
38
39 static struct timeoutlist s_timeoutlist[SYS_THREAD_MAX];
40
41 static u16_t s_nextthread = 0;
42
43 u32_t
44 sys_jiffies(void)
45 {
46 lwip_sys_now = xTaskGetTickCount();
47 return lwip_sys_now;
48 }
49
50 u32_t
51 sys_now(void)
52 {
53 lwip_sys_now = xTaskGetTickCount();
54 return lwip_sys_now;
55 }
56
57 void
58 sys_init(void)
59 {
60 int i;
61 // Initialize the the per-thread sys_timeouts structures
62 // make sure there are no valid pids in the list
63 for (i = 0; i < SYS_THREAD_MAX; i++)
64 {
65 s_timeoutlist[i].pid = 0;
66 s_timeoutlist[i].timeouts.next = NULL;
67 }
68 // keep track of how many threads have been created
69 s_nextthread = 0;
70 }
71
72 struct sys_timeouts *sys_arch_timeouts(void)
73 {
74 int i;
75 xTaskHandle pid;
76 struct timeoutlist *tl;
77 pid = xTaskGetCurrentTaskHandle( );
78 for (i = 0; i < s_nextthread; i++)
79 {
80 tl = &(s_timeoutlist[i]);
81 if (tl->pid == pid)
82 {
83 return &(tl->timeouts);
84 }
85 }
86 return NULL;
87 }
88
89 sys_prot_t sys_arch_protect(void)
90 {
91 vPortEnterCritical(); //進入臨界段
92 return 1;
93 }
94
95 void sys_arch_unprotect(sys_prot_t pval)
96 {
97 ( void ) pval;
98 vPortExitCritical(); //退出臨界段
99 }
100
101 #if !NO_SYS
102
103 err_t
104 sys_sem_new(sys_sem_t *sem, u8_t count)
105 {
106 /* 創建 sem */
107 if (count <= 1)
108 {
109 *sem = xSemaphoreCreateBinary(); //創建二值信號量
110 if (count == 1)
111 {
112 sys_sem_signal(*sem); //新創建的信號量是無效的,需要釋放一個信號量
113 }
114 }
115 else
116 *sem = xSemaphoreCreateCounting(count,count); //創建計數信號量
117
118 #if SYS_STATS
119 ++lwip_stats.sys.sem.used;
120 if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used)
121 {
122 lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
123 }
124 #endif /* SYS_STATS */
125
126 if (*sem != SYS_SEM_NULL)
127 return ERR_OK; //創建成功返回ERR_OK
128 else
129 {
130 #if SYS_STATS
131 ++lwip_stats.sys.sem.err;
132 #endif /* SYS_STATS */
133 printf("[sys_arch]:new sem fail!\n");
134 return ERR_MEM;
135 }
136 }
137
138 void
139 sys_sem_free(sys_sem_t *sem)
140 {
141 #if SYS_STATS
142 --lwip_stats.sys.sem.used;
143 #endif /* SYS_STATS */
144 /* 刪除 sem */
145 vSemaphoreDelete(*sem); //刪除一個信號量
146 *sem = SYS_SEM_NULL; //刪除之后置空
147 }
148
149
150 int sys_sem_valid(sys_sem_t *sem)
151 {
152 return (*sem != SYS_SEM_NULL); //返回信號量是否有效
153 }
154
155
156 void
157 sys_sem_set_invalid(sys_sem_t *sem)
158 {
159 *sem = SYS_SEM_NULL; //信號量設置為無效
160 }
161
162 /*
163 如果timeout參數不為零,則返回值為
164 等待信號量所花費的毫秒數。如果
165 信號量未在指定時間內發出信號,返回值為
166 SYS_ARCH_TIMEOUT。如果線程不必等待信號量
167 該函數返回零。 */
168 u32_t
169 sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
170 {
171 u32_t wait_tick = 0;
172 u32_t start_tick = 0 ;
173
174 //看看信號量是否有效
175 if (*sem == SYS_SEM_NULL)
176 return SYS_ARCH_TIMEOUT;
177
178 //首先獲取開始等待信號量的時鐘節拍
179 start_tick = xTaskGetTickCount();
180
181 //timeout != 0,需要將ms換成系統的時鐘節拍
182 if (timeout != 0)
183 {
184 //將ms轉換成時鐘節拍
185 wait_tick = timeout / portTICK_PERIOD_MS;
186 if (wait_tick == 0)
187 wait_tick = 1;
188 }
189 else
190 wait_tick = portMAX_DELAY; //一直阻塞
191
192 //等待成功,計算等待的時間,否則就表示等待超時
193 if (xSemaphoreTake(*sem, wait_tick) == pdTRUE)
194 return ((xTaskGetTickCount()-start_tick)*portTICK_RATE_MS);
195 else
196 return SYS_ARCH_TIMEOUT;
197 }
198
199 void
200 sys_sem_signal(sys_sem_t *sem)
201 {
202 if (xSemaphoreGive( *sem ) != pdTRUE) //釋放信號量
203 printf("[sys_arch]:sem signal fail!\n");
204 }
205
206 err_t
207 sys_mutex_new(sys_mutex_t *mutex)
208 {
209 /* 創建 sem */
210 *mutex = xSemaphoreCreateMutex(); //創建互斥量
211 if (*mutex != SYS_MRTEX_NULL)
212 return ERR_OK; //創建成功返回ERR_OK
213 else
214 {
215 printf("[sys_arch]:new mutex fail!\n");
216 return ERR_MEM;
217 }
218 }
219
220 void
221 sys_mutex_free(sys_mutex_t *mutex)
222 {
223 vSemaphoreDelete(*mutex); //刪除互斥量
224 }
225
226 void
227 sys_mutex_set_invalid(sys_mutex_t *mutex)
228 {
229 *mutex = SYS_MRTEX_NULL; //設置互斥量為無效
230 }
231
232 void
233 sys_mutex_lock(sys_mutex_t *mutex)
234 {
235 xSemaphoreTake(*mutex,/* 互斥量句柄 */
236 portMAX_DELAY); /* 等待時間 */
237 }
238
239 void
240 sys_mutex_unlock(sys_mutex_t *mutex)
241 {
242 xSemaphoreGive( *mutex );//給出互斥量
243 }
244
245 sys_thread_t
246 sys_thread_new(const char *name, lwip_thread_fn function,
247 void *arg, int stacksize, int prio)
248 {
249 sys_thread_t handle = NULL;
250 BaseType_t xReturn = pdPASS;
251 /* 創建一個線程 */
252 xReturn = xTaskCreate((TaskFunction_t )function, /* 線程入口函數 */
253 (const char* )name,/* 線程名字 */
254 (uint16_t )stacksize, /* 線程棧大小 */
255 (void* )arg,/* 線程入口函數參數 */
256 (UBaseType_t )prio, /* 線程的優先級 */
257 (TaskHandle_t* )&handle);/* 線程控制塊指針 */
258 if (xReturn != pdPASS)
259 {
260 printf("[sys_arch]:create task fail!err:%#lx\n",xReturn);
261 return NULL;
262 }
263 return handle;
264 }
265
266 err_t
267 sys_mbox_new(sys_mbox_t *mbox, int size)
268 {
269 /* 創建一個郵箱 */
270 *mbox = xQueueCreate((UBaseType_t ) size,/* 郵箱的長度 */
271 (UBaseType_t ) sizeof(void *));/* 消息的大小 */
272 #if SYS_STATS
273 ++lwip_stats.sys.mbox.used;
274 if (lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used)
275 {
276 lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
277 }
278 #endif /* SYS_STATS */
279 if (NULL == *mbox)
280 return ERR_MEM; // 創建成功返回ERR_OK
281
282 return ERR_OK;
283 }
284
285 void
286 sys_mbox_free(sys_mbox_t *mbox)
287 {
288 if ( uxQueueMessagesWaiting( *mbox ) )
289 {
290 /* Line for breakpoint. Should never break here! */
291 portNOP();
292 #if SYS_STATS
293 lwip_stats.sys.mbox.err++;
294 #endif /* SYS_STATS */
295 }
296
297 vQueueDelete(*mbox); //刪除一個郵箱
298
299 #if SYS_STATS
300 --lwip_stats.sys.mbox.used;
301 #endif /* SYS_STATS */
302 }
303
304 int sys_mbox_valid(sys_mbox_t *mbox)
305 {
306 if (*mbox == SYS_MBOX_NULL) //判斷郵箱是否有效
307 return 0;
308 else
309 return 1;
310 }
311
312 void
313 sys_mbox_set_invalid(sys_mbox_t *mbox)
314 {
315 *mbox = SYS_MBOX_NULL; //設置有效為無效狀態
316 }
317
318 void
319 sys_mbox_post(sys_mbox_t *q, void *msg)
320 {
321 while (xQueueSend( *q, /* 郵箱的句柄 */
322 &msg,/* 發送的消息內容 */
323 portMAX_DELAY) != pdTRUE); /* 等待時間 */
324 }
325
326 err_t
327 sys_mbox_trypost(sys_mbox_t *q, void *msg)
328 {
329 if (xQueueSend(*q,&msg,0) == pdPASS) //嘗試發送一個消息,非阻塞發送
330 return ERR_OK;
331 else
332 return ERR_MEM;
333 }
334
335 err_t
336 sys_mbox_trypost_fromisr(sys_mbox_t *q, void *msg)
337 {
338 uint32_t ulReturn;
339 err_t err = ERR_MEM;
340 BaseType_t pxHigherPriorityTaskWoken;
341
342 /* 進入臨界段,臨界段可以嵌套 */
343 ulReturn = taskENTER_CRITICAL_FROM_ISR();
344
345 if (xQueueSendFromISR(*q,&msg,&pxHigherPriorityTaskWoken)==pdPASS)
346 {
347 err = ERR_OK;
348 }
349 //如果需要的話進行一次線程切換
350 portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
351
352 /* 退出臨界段 */
353 taskEXIT_CRITICAL_FROM_ISR( ulReturn );
354
355 return err;
356 }
357
358 u32_t
359 sys_arch_mbox_fetch(sys_mbox_t *q, void **msg, u32_t timeout)
360 {
361 void *dummyptr;
362 u32_t wait_tick = 0;
363 u32_t start_tick = 0 ;
364
365 if ( msg == NULL ) //看看存儲消息的地方是否有效
366 msg = &dummyptr;
367
368 //首先獲取開始等待信號量的時鐘節拍
369 start_tick = sys_now();
370
371 //timeout != 0,需要將ms換成系統的時鐘節拍
372 if (timeout != 0)
373 {
374 //將ms轉換成時鐘節拍
375 wait_tick = timeout / portTICK_PERIOD_MS;
376 if (wait_tick == 0)
377 wait_tick = 1;
378 }
379 //一直阻塞
380 else
381 wait_tick = portMAX_DELAY;
382
383 //等待成功,計算等待的時間,否則就表示等待超時
384 if (xQueueReceive(*q,&(*msg), wait_tick) == pdTRUE)
385 return ((sys_now() - start_tick)*portTICK_PERIOD_MS);
386 else
387 {
388 *msg = NULL;
389 return SYS_ARCH_TIMEOUT;
390 }
391 }
392
393 u32_t
394 sys_arch_mbox_tryfetch(sys_mbox_t *q, void **msg)
395 {
396 void *dummyptr;
397 if ( msg == NULL )
398 msg = &dummyptr;
399
400 //等待成功,計算等待的時間
401 if (xQueueReceive(*q,&(*msg), 0) == pdTRUE)
402 return ERR_OK;
403 else
404 return SYS_MBOX_EMPTY;
405 }
406
407 #if LWIP_NETCONN_SEM_PER_THREAD
408 #error LWIP_NETCONN_SEM_PER_THREAD==1 not supported
409 #endif /* LWIP_NETCONN_SEM_PER_THREAD */
410
411 #endif /* !NO_SYS */
```
這些函數都是對操作系統的IPC通信機制進行簡單的封裝,在這里用戶只需要稍微注意一下sys\_arch\_sem\_wait()函數與sys\_arch\_mbox\_fetch()函數,因為LwIP中使用的時間是以毫秒(ms)為單位的,而操作系統中則以時鐘節拍(tick)為單位,那么在返回等待信號量或者郵箱所使用的時間就是要轉換成ms,而操作系統并未提供等待這些信息的時間,那么我們可以使用一個折中的方法,在獲取的時候開始記錄時間戳,在獲取結束后再次記錄一次時間戳,兩次時間戳相減就得到等待的時間,但是需要將這些時間(tick)轉換為毫秒,這種做法當然是不精確的,但是對LwIP來說影響不大。
- 說明
- 第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地址