启动流程图:
启动时序:
启动程序会创建2个线程:tcpip_thread负责LWIP的绝大部分工作(主要是协议栈的解析和系统运行),ethernetif_thread负责从网口接收数据包再交付给tcpip_thread线程进行处理。
路径:lwip-2.1.2\src\api\tcpip.c
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
函数内容如下(各个初始化需要打开相应的宏定义
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; // 用于存储接收到的消息
LWIP_UNUSED_ARG(arg); // 表示参数 arg 未被使用
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_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
LWIP_ASSERT("tcpip_thread: invalid message", 0);
continue;
}
// 处理消息
tcpip_thread_handle_msg(msg);
}
}
带操作系统时是用 tcpip_timeouts_mbox_fetch(mbox, msg)函数
/**
* Wait (forever) for a message to arrive in an mbox.
* While waiting, timeouts are processed.
*
* @param mbox the mbox to fetch the message from
* @param msg the place to store the message
*/
/**
* 从指定的邮箱中获取消息,并处理超时情况
*
* @param mbox 要操作的邮箱
* @param msg 用于存储获取到的消息的指针
*/
static void tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg)
{
u32_t sleeptime, res;
again:
/* 计算下一个到期的定时器的时间和当前时间的差 */
sleeptime = sys_timeouts_sleeptime();
if (sleeptime == SYS_TIMEOUTS_SLEEPTIME_INFINITE) {
/* 系统中没有的定时器 */
UNLOCK_TCPIP_CORE();
/* 阻塞等待mbox的消息:若阻塞的时候有新的定时器创建,
* 是否也会有一个mbox消息,否则一直阻塞?(todo:后续阅读相关代码) */
sys_arch_mbox_fetch(mbox, msg, 0);
LOCK_TCPIP_CORE();
return;
} else if (sleeptime == 0) {
/* 有定时器已经到期 */
sys_check_timeouts();
/* 回去重新检查是否有多个定时器同时到期 */
goto again;
}
/* 没有定时器到期:这段时间(sleeptime)来检查mbox有没有消息 */
UNLOCK_TCPIP_CORE();
res = sys_arch_mbox_fetch(mbox, msg, sleeptime);
LOCK_TCPIP_CORE();
if (res == SYS_ARCH_TIMEOUT) {
/* 等待sleeptime后还是没有mbox到期,此时肯定有定时器到期,去检查 */
sys_check_timeouts();
/* 再回去检查mbox中是否有数据 */
goto again;
}
}
tcpip_timeouts_mbox_fetch 的作用检查系统timeout的定时器,因为下一个定时器到期的时间是确定的,所以在这个间隔内可以等待mbox的消息。
由于tcpip_thread中需要tcpip_timeouts_mbox_fetch中返回一个msg去处理,所以该函数的退出时机是等到了mbox中的消息。
对于这边的互斥锁,tcpip_thread一上来就调用LOCK_TCPIP_CORE上锁,在TCPIP_MBOX_FETCH中等待消息队列阻塞时释放这个锁,等待完这个队列,无论有没有消息到来,sys_arch_mbox_fetch返回后就立即上锁,因为在此期间tcpip_thread已经阻塞死等,此时用户可以调用部分LwIP内核函数。
整体框架图: