目录
通信过程中,本质上是数据的交互,数据传递,理解LwIP的框架,就可以更清晰的理解数据传递过程、原理。
注:以下皆为有操作系统环境下,网卡数据传递到内核的运作流程
1. 网卡数据接收流程
ETH(网卡)接收到数据后,产生中断,然后,释放一个信号量通知网卡接收线程处理这些接收的数据,然后,将数据封装成消息,投递到tcpip_mbox邮箱中,LwIP内核获取到该消息,对消息进行解析;根据消息中数据包类型进行处理,实际上是调用ethernet_input()函数决定是否递交到IP 层,如果是ARP 包,内核就不会递交给IP 层,而是更新ARP 缓存表,对于IP 数据包则递交给IP 层去处理,这就是一个数据从网卡到内核的过程
有图亦可知,用户程序与内核是完全独立的,只是通操作系统的IPC 通信机制进行数据交互。
2. 内核超时处理
在LwIP 中很多时候都要用到超时处理,例如ARP 缓存表项的时间管理、IP 分片数据报的重装等待超时、TCP 中的建立连接超时、重传超时机制等,因此超时处理的实现是TCP/IP 协议栈中一个重要部分,LwIP 为每个与外界网络连接的任务都有设定了 timeout 属性,即等待超时时间,超时处理的相关代码实现在timeouts.c 与timeouts.h 中。
LwIP 采用软件定时器对这些超时进行处理,因为软件定时器很容易维护,并且与平台无关,只需要用户提供一个较为准确的时基即可。
2.1 sys_timeo 结构体与超时链表
LwIP 通过一个sys_timeo 类型的数据结构管理与超时链表相关的所有超时事件。LwIP使用这个结构体记录下内核中所有被注册的超时事件,这些结构体会以链表的形式一个个连接在超时链表中,而内核中只有一条超时链表。
LwIP定义了一个sys_timeo 类型的指针next_timeout,并且将next_timeout 指向当前内核中链表头部,所有被注册的超时事件都会按照被处理的先后顺序排列在超时链表上。
struct sys_timeo {
struct sys_timeo *next; // 指向下一个超时事件的指针,用于超时链表的连接
u32_t time; // 当前超时事件的等待时间
sys_timeout_handler h; // 指向超时的回调函数
void *arg; // 向回调函数传入参数
#if LWIP_DEBUG_TIMERNAMES
const char* handler_name;
#endif /* LWIP_DEBUG_TIMERNAMES */
};
/** The one and only timeout list */
static struct sys_timeo *next_timeout; // 指向超时链表第一个超时事件
2.2 注册超时事件
LwIP 虽然使用超时链表进行管理所有的超时事件,那么它首先需要知道有哪些超时事件才能去管理,而这些超时事件就是通过注册的方式被挂载在链表上.
超时事件要在内核中登记一下,内核才会去处理,LwIP 中注册超时事件的函数是sys_timeout(),但是实际上是调用sys_timeout_abs()函数.
sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg)
{
u32_t next_timeout_time;
LWIP_ASSERT_CORE_LOCKED();
LWIP_ASSERT("Timeout time too long, max is LWIP_UINT32_MAX/4 msecs", msecs <= (LWIP_UINT32_MAX / 4));
/* 根据当前时间计算超时时间 */
next_timeout_time = (u32_t)(sys_now() + msecs); /* overflow handled by TIME_LESS_THAN macro */
/* 当前事件插入超时链表 */
sys_timeout_abs(next_timeout_time, handler, arg);
}
sys_timeout_abs(u32_t abs_time, sys_timeout_handler handler, void *arg)
{
struct sys_timeo *timeout, *t;
/* 内存池中申请,用以保存超时事件相关信息 */
timeout = (struct sys_timeo *)memp_malloc(MEMP_SYS_TIMEOUT);
if (timeout == NULL) {
LWIP_ASSERT("sys_timeout: timeout != NULL, pool MEMP_SYS_TIMEOUT is empty", timeout != NULL);
return;
}
/* 填写对应的超时事件信息,超时回调函数、函数参数、超时的 时间 */
timeout->next = NULL;
timeout->h = handler;
timeout->arg = arg;
timeout->time = abs_time;
/* 如果超时链表中没有超时事件,那么新添加的事件就是链表的第一个 */
if (next_timeout == NULL) {
next_timeout = timeout;
return;
}
/* 若新插入的超时事件比链表上第一个事件的时间短,则将新插入的超时事件设置成链表的第一个 */
if (TIME_LESS_THAN(timeout->time, next_timeout->time)) {
timeout->next = next_timeout;
next_timeout = timeout;
} else {
for (t = next_timeout; t != NULL; t = t->next) {
if ((t->next == NULL) || TIME_LESS_THAN(timeout->time, t->next->time)) {
/* 遍历链表,寻找合适的插入节点,超时链表根据超时事件的时间升序排列。 */
timeout->next = t->next;
t->next = timeout;
break;
}
}
}
}
在timeouts.c 中,有一个名字为lwip_cyclic_timer 的结构,LwIP 使用该结构存放了其内部使用的循环超时事件。这些超时事件在LwIP 初始化时通过函数sys_timeouts_init()调用定时器注册函数sys_timeout()注册进入超时链表中。
lwip_cyclic_timers 数组中存放了每个周期性的超时事件回调函数及超时时间,在LwIP初始化的时候就将这些事件一个个插入超时链表中。
/** Initialize this module */
void sys_timeouts_init(void)
{
size_t i;
/* tcp_tmr() at index 0 is started on demand */
for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {
/* we have to cast via size_t to get rid of const warning
(t