💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
操作系统环境下, 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来说影响不大。