- 环境:FreeRTOS & LwIP 2.2.0
- 文章中的所有参数检测的断言代码都删除以使代码更清晰
LwIP通过调用tcpip_init
来初始化TCPIP协议栈,函数如下所示,函数中代码的含义见注释:
static tcpip_init_done_fn tcpip_init_done;
static void *tcpip_init_done_arg;
static sys_mbox_t tcpip_mbox;
sys_mutex_t lock_tcpip_core;
void tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
/* 初始化LwIP */
lwip_init();
/* 初始化完毕后调用的回调函数,可传NULL,在tcpip_thread开头调用 */
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
/* 创建message box,实际上是FreeRTOS的Queue
* 每个项目大小为一个指针的大小(4B),队列长度为TCPIP_MBOX_SIZE */
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
/* 创建互斥锁:用户可以通过这个锁在代码中实现LwIP的一些操作,而不需要在tcpip_thread的callback中实现 */
if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT("failed to create lock_tcpip_core", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
/* 创建FreeRTOS任务tcpip_thread */
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
其中lwip_init
函数内容如下(各个初始化需要打开相应的宏定义,这里略去#ifdef...#endif
的宏定义以方便阅读代码):
void lwip_init(void)
{
/* 统计模块初始化,这里只是初始化lwip_stats.mem.name为MEM */
stats_init();
/* 带操作系统时的初始化:函数中暂时没有内容 */
sys_init();
/* 初始化内存堆的起始地址、结束地址以及空闲列表 */
mem_init();
/* 初始化LwIP内存池 */
memp_init();
/* 初始化pbuf:函数中暂时没有内容 */
pbuf_init();
/* 初始化netif:主要是环回的ip,网关,子网掩码以及netif的添加和配置 */
netif_init();
/* 兼容老版本:函数中暂时没有内容 */
ip_init();
/* 兼容老版本:函数中暂时没有内容 */
etharp_init();
/* 兼容老版本:函数中暂时没有内容 */
raw_init();
/* 初始化UDP端口号(随机分配),范围:0xc000~0xffff */
udp_init();
/* 初始化TCP端口号(随机分配),范围:0xc000~0xffff */
tcp_init();
/* 初始化组播IP */
igmp_init();
/* 初始化DNS解析:设置UDP PCB并配置默认服务器 */
dns_init();
/* 初始化PPP:根据配置分配相关的结构体内存和初始化魔术字(PPP连接需要用到) */
ppp_init();
/* 初始化软件定时器 */
sys_timeouts_init();
}
最后,可以看到tcpip_init
最终调用sys_thread_new
创建了一个tcpip_thread
任务,看看它做了什么:
static void tcpip_thread(void *arg)
{
struct tcpip_msg *msg;
/* 上锁 */
LOCK_TCPIP_CORE();
/* 调用前面设置的回调函数 */
if (tcpip_init_done != NULL) {
tcpip_init_done(tcpip_init_done_arg);
}
while (1) {
/* wait for a message, timeouts are processed while waiting */
TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);
if (msg == NULL) {
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
LWIP_ASSERT("tcpip_thread: invalid message", 0);
continue;
}
tcpip_thread_handle_msg(msg);
}
}
可见,最后是调用TCPIP_MBOX_FETCH
来等待消息,这个函数中还有定时器的检查,具体解释参考:LwIP源码分析(1):软件定时器。
static void
tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
{
u32_t sleeptime, res;
again:
LWIP_ASSERT_CORE_LOCKED();
sleeptime = sys_timeouts_sleeptime();
if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
UNLOCK_TCPIP_CORE();
sys_arch_mbox_fetch(mbox, msg, 0);
LOCK_TCPIP_CORE();
return;
} else if (sleeptime == 0) {
sys_check_timeouts();
/* We try again to fetch a message from the mbox. */
goto again;
}
UNLOCK_TCPIP_CORE();
res = sys_arch_mbox_fetch(mbox, msg, sleeptime);
LOCK_TCPIP_CORE();
if (res == SYS_ARCH_TIMEOUT) {
/* If a SYS_ARCH_TIMEOUT value is returned, a timeout occurred
before a message could be fetched. */
sys_check_timeouts();
/* We try again to fetch a message from the mbox. */
goto again;
}
}
对于这边的互斥锁,tcpip_thread
一上来就调用LOCK_TCPIP_CORE
上锁,在TCPIP_MBOX_FETCH
中等待消息队列阻塞时释放这个锁,等待完这个队列,无论有没有消息到来,sys_arch_mbox_fetch
返回后就立即上锁,因为在此期间tcpip_thread
已经阻塞死等,此时用户可以调用部分LwIP内核函数。
对于消息处理函数tcpip_thread_handle_msg(msg)
相关的结构体和实现需要在了解TCP/IP原理之后进行分析,这样知道在哪里在何时会往这个mbox发送消息,我们才能知道每个消息的定义,不同的消息需要执行什么操作。后续循序渐进地分析完LwIP相关代码后,我会写一个博客来分析,这里暂不讨论。