操作系统环境下, 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地址