现在有必要来看看前面一直提到的内核协议栈进程是什么样子的。这个函数叫tcpip_thread,其源码如下,其中去掉了不相关的编译选项和非重点讨论部分。
static void tcpip_thread(void *arg)
{
structtcpip_msg *msg;
sys_timeout(IP_TMR_INTERVAL,ip_reass_timer, NULL);//创建IP分片重装超时事件
sys_timeout(ARP_TMR_INTERVAL, arp_timer, NULL);//创建ARP超时事件
while (1){// 进程循环
sys_mbox_fetch(mbox,(void*)&msg);// 阻塞在邮箱上接收要处理的消息
switch(msg->type) { // 判断消息类型
caseTCPIP_MSG_API: // 若是API消息,调用消息内部的function函数
msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
break;
caseTCPIP_MSG_INPKT:// 若是接收到IP层递交的数据包
if(msg->msg.inp.netif->flags & NETIF_FLAG_ETHARP) //支持ARP
ethernet_input(msg->msg.inp.p,msg->msg.inp.netif);//先进行ARP处
//理,再判断是否递交IP层处理
else //否则直接递交给IP层
ip_input(msg->msg.inp.p, msg->msg.inp.netif);
memp_free(MEMP_TCPIP_MSG_INPKT,msg);
break;
。。。。。。
default:break;
}//switch
} //while
}//
邮箱mbox是协议栈初始化时建立的用于tcpip_thread接收消息的邮箱,该函数能够识别的消息类型是tcpip_msg结构的,所以不管是API部分还是IP数据包输入部分,都必须将自己的信息封装成tcpip_msg结构。
struct tcpip_msg {
enum tcpip_msg_type type;// 枚举结构,消息类型
sys_sem_t*sem; //信号量指针,该字段似乎没怎么被用到
union {// 共用体,不同消息类型使用不同的结构
structapi_msg *apimsg; // API消息指针
structnetifapi_msg *netifapimsg; // 不讨论
struct{// 接收到IP层数据包相关指针
struct pbuf *p;
struct netif *netif;
} inp;
struct{ // 不讨论
void (*f)(void *ctx);
void *ctx;
} cb;
struct{ // 不讨论
u32_t msecs;
sys_timeout_handler h;
void *arg;
}tmo;
} msg; //枚举类型的名字
};
这个结构和上节讨论的api_msg_msg有着很相似,api_msg_msg里面也包含了一个叫做的msg的结构体。枚举型tcpip_msg_type的内部成员就是在函数tcpip_thread中看到的TCPIP_MSG_API和TCPIP_MSG_INPKT等,这里只讨论这两种类型。
API函数内部调用来向内核进程发送消息的函数叫tcpip_apimsg,该函数填充一个TCPIP_MSG_API类型的tcpip_msg结构,并把该结构投递到协议栈阻塞的邮箱mbox:
err_t tcpip_apimsg(struct api_msg *apimsg)
{
struct tcpip_msgmsg;// 定义一个消息变量
if (mbox != SYS_MBOX_NULL) {
msg.type= TCPIP_MSG_API; // 消息类型
msg.msg.apimsg = apimsg; //使用tcpip_msg中的msg.apimsg字段记录相关信息
sys_mbox_post(mbox, &msg); // 投递消息
sys_arch_sem_wait(apimsg->msg.conn->op_completed, 0);//阻塞,等待内核处理完毕
returnERR_OK;
}
return ERR_VAL;
}
底层向内核进程递交接收到的IP数据包是通过调用网络接口结构netif中指针input指向的函数来实现的(参见前面netif描述的部分)。通常这个函数是tcpip_input。
err_t tcpip_input(struct pbuf *p, struct netif *inp)
{
struct tcpip_msg *msg; // 定义了一个消息指针
if (mbox != SYS_MBOX_NULL) {
msg =memp_malloc(MEMP_TCPIP_MSG_INPKT); //为新的消息申请空间
if (msg== NULL) {
return ERR_MEM;
}
msg->type = TCPIP_MSG_INPKT; // 消息类型
msg->msg.inp.p = p; //使用tcpip_msg中的msg.inp字段记录相关信息
msg->msg.inp.netif = inp;
if(sys_mbox_trypost(mbox, msg) != ERR_OK) { // 投递一次消息
memp_free(MEMP_TCPIP_MSG_INPKT, msg);//投递不成功则删除消息
return ERR_MEM;
}
returnERR_OK;
}
return ERR_VAL;
}
哎,这个过程是十分的复杂纠结啊,下面这个图可能更容易理解。这里函数tcpip_thread还是只处理TCPIP_MSG_API和TCPIP_MSG_INPKT这两种类型的消息。
从上面的图中可以清晰的看出两部分API函数之间的交互过程,以及应用程序和内核函数之间的交互过程。API函数netconn_xxx在文件api_lib.c中,而API实现的另一部分函数do_xxx在api_msg.c中。
接下来的一节我们将精力集中在api_lib.c中的各个函数,而不去过多关心api_msg.c中的各个do_xxx是怎样与内核函数交互完成相关工作的。netconn_xxx函数在LwIP说明文档16小节也有相关的描述,这里打算再重复的描述下各个函数的功能以及它们的实现过程。