lwIP 操作系统模拟层

Posted 研究是为了理解

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lwIP 操作系统模拟层相关的知识,希望对你有一定的参考价值。

注1:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。
注2:操作系统使用 FreeRTOS

contrib 2.1.0 给出了一个 lwIP 的操作系统模拟层,使用的操作系统为 FreeRTOS。模拟层位于:\\contrib-2_1_0\\ports\\freertos

LWIP_TCPIP_CORE_LOCKING 定义在 opt.h 中:

/**
 * LWIP_TCPIP_CORE_LOCKING
 * Creates a global mutex that is held during TCPIP thread operations.
 * Can be locked by client code to perform lwIP operations without changing
 * into TCPIP thread using callbacks. See LOCK_TCPIP_CORE() and
 * UNLOCK_TCPIP_CORE().
 * Your system should provide mutexes supporting priority inversion to use this.
 */
#if !defined LWIP_TCPIP_CORE_LOCKING || defined __DOXYGEN__
#define LWIP_TCPIP_CORE_LOCKING         1
#endif

这个宏用于启用协议栈锁定功能,是通过互斥量实现的,要求互斥量具备优先级继承机制。使能后,在 tcpip.c 中定义全局变量:

#if LWIP_TCPIP_CORE_LOCKING
/** The global semaphore to lock the stack. */
sys_mutex_t lock_tcpip_core;
#endif /* LWIP_TCPIP_CORE_LOCKING */

在函数 tcpip_init 中会申请互斥量:

/**
 * @ingroup lwip_os
 * Initialize this module:
 * - initialize all sub modules
 * - start the tcpip_thread
 *
 * @param initfunc a function to call when tcpip_thread is running and finished initializing
 * @param arg argument to pass to initfunc
 */
void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)

  lwip_init();

  tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
  if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) 
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  
#if LWIP_TCPIP_CORE_LOCKING
  if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) 		// <--- 这里
    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  
#endif /* LWIP_TCPIP_CORE_LOCKING */

  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);

使用宏做了一层包装

#if LWIP_TCPIP_CORE_LOCKING
/** The global semaphore to lock the stack. */
extern sys_mutex_t lock_tcpip_core;
#if !defined LOCK_TCPIP_CORE || defined __DOXYGEN__
/** Lock lwIP core mutex (needs @ref LWIP_TCPIP_CORE_LOCKING 1) */
#define LOCK_TCPIP_CORE()     sys_mutex_lock(&lock_tcpip_core)		// <-- 这里
/** Unlock lwIP core mutex (needs @ref LWIP_TCPIP_CORE_LOCKING 1) */
#define UNLOCK_TCPIP_CORE()   sys_mutex_unlock(&lock_tcpip_core)	// <-- 这里
#endif /* LOCK_TCPIP_CORE */
#else /* LWIP_TCPIP_CORE_LOCKING */
#define LOCK_TCPIP_CORE()
#define UNLOCK_TCPIP_CORE()
#endif /* LWIP_TCPIP_CORE_LOCKING */

函数或宏

互斥量

struct _sys_mut 
  void *mut;
;
typedef struct _sys_mut sys_mutex_t;
名称功能
sys_mutex_valid(mutex)宏,判断互斥量是否有效
sys_mutex_set_invalid(mutex)宏,将一个互斥量设置为无效
err_t sys_mutex_new(sys_mutex_t *mutex)函数,创建互斥量
void sys_mutex_lock(sys_mutex_t *mutex)函数,阻塞进程,直到互斥量可用
void sys_mutex_unlock(sys_mutex_t *mutex)函数,释放先前使用 sys_mutex_lock 函数锁定的互斥量
void sys_mutex_free(sys_mutex_t *mutex)函数,释放互斥量资源

信号量

struct _sys_sem 
  void *sem;
;
typedef struct _sys_sem sys_sem_t;
名称功能
sys_sem_valid(sema)宏,判断信号量是否有效
sys_sem_set_invalid(sema)宏,将一个信号量设置为无效
err_t sys_sem_new(sys_sem_t *sem, u8_t initial_count)函数,创建信号量
sem 指向新的信号量
initial_count01 ,指定信号量的初始状态。为 1 表示信号量有效
void sys_sem_signal(sys_sem_t *sem)函数,释放信号量(发信号)
void sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout_ms)函数,阻塞线程直到一个信号量有效。
timeout_ms 用于指定超时时间, = 0:表示无限阻塞,=其它:阻塞指定毫秒
void sys_sem_free(sys_sem_t *sem)函数,释放互斥量资源

邮箱
FreeRTOS 并没有邮箱机制,模拟层使用 队列 来模拟邮箱。

struct _sys_mbox 
  void *mbx;
;
typedef struct _sys_mbox sys_mbox_t;
名称功能
sys_mbox_valid(mbox)宏,判断邮箱是否有效
sys_mbox_set_invalid(mbox)宏,将一个邮箱设置为无效
err_t sys_mbox_new(sys_mbox_t *mbox, int size)函数,创建一个空的邮箱,最大可存放 size 个元素
存储到邮箱的元素应为指针类型
你应该在 lwipopts.h 中定义 _MBOX_SIZE 来确定邮箱的个数
void sys_mbox_post(sys_mbox_t *mbox, void *msg)函数,向邮箱投递消息,如果邮箱满则无限阻塞,只能用于任务中。决不能在中断中调用。
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)函数,尝试向邮箱投递信息,如果邮箱满则返回错误 ERR_MEM
err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)函数,尝试向邮箱投递信息,如果邮箱满则返回错误 ERR_MEM,用于中断
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout_ms)函数,阻塞线程直到一个消息有效。
timeout_ms 用于指定超时时间, = 0:表示无限阻塞,=其它:阻塞指定毫秒
msg 可以为 NULL ,表示丢弃掉这个消息
返回 SYS_ARCH_TIMEOUT 表示发生超时
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)函数,接收一个消息,如果当前邮箱为空,则立即返回 SYS_MBOX_EMPTY 。成功返回 0
void sys_mbox_free(sys_mbox_t *mbox)函数,释放邮箱资源。
如果释放邮箱资源时,检测到邮箱内仍有消息,啧表明有了编程错误,应该设法通知开发者。

在 lwIP 中,上层 API 与协议栈内核之间的数据交互都是通过邮箱实现的。
在使用上层 API 时,API 需要调用内核相关的函数,信号量和互斥量为上层 API 与内核函数的执行提供了同步和互斥支持。
比如应用层调用数据发送 API 函数时,API 函数会阻塞在一个信号量上,同时请求内核进程 tcpip_thread 调用对应的内核数据发送函数,当内核函数执行完成后,释放信号量,这样原来阻塞的 API 函数得以继续执行。

lwIP 进程
在操作系统中,lwIP 被封装到一个名为 tcpip_thread 的进程中。在函数 tcpip_init 中,会创建这个进程:

sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);

TCPIP_THREAD_STACKSIZETCPIP_THREAD_PRIO 分别表示进程的堆栈空间和优先级,需要在 lwipopts.h 中定义。

tcpip_thread 进程会阻塞在一个邮箱上,等待消息,处理消息。这个邮箱内的消息可能来自于底层硬件驱动接收到的数据包,也可能来自上层应用程序的 API 调用。

/**
 * The main lwIP thread. This thread has exclusive access to lwIP core functions
 * (unless access to them is not locked). Other threads communicate with this
 * thread using message boxes.
 *
 * It also starts all the timers to make sure they are running in the right
 * thread context.
 *
 * @param arg unused argument
 */
static void
tcpip_thread(void *arg)

  struct tcpip_msg *msg;
  LWIP_MARK_TCPIP_THREAD();
  LOCK_TCPIP_CORE();
  if (tcpip_init_done != NULL) 
    tcpip_init_done(tcpip_init_done_arg);
  
  while (1)                           /* MAIN Loop */
    LWIP_TCPIP_THREAD_ALIVE();
    /* wait for a message, timeouts are processed while waiting */
    TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);
    if (msg == NULL) 
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      continue;
    
    tcpip_thread_handle_msg(msg);
  

头文件 lwipopts.h

#define NO_SYS					0			
#define LWIP_SOCKET				1
#define LWIP_NETCONN			1
#define TCPIP_MBOX_SIZE
#define TCPIP_THREAD_STACKSIZE 	
#define TCPIP_THREAD_PRIO 		

以上是关于lwIP 操作系统模拟层的主要内容,如果未能解决你的问题,请参考以下文章

lwIP 操作系统模拟层

lwIP 操作系统模拟层

二LWIP学习笔记之网络接口管理

《嵌入式 - Lwip开发指南》第2章 LWIP开发环境简介

《嵌入式 - Lwip开发指南》第2章 LWIP开发环境简介

八LWIP学习笔记之用户编程接口