【LwIP源码学习5】网口接收数据处理过程
前言
本文对lwip中TCPIP_MSG_INPKT类型消息的处理过程进行分析。
正文
在 【LwIP源码学习4】主线程tcpip_thread一文中提到。lwip主线程tcpip_thread
会先从邮箱tcpip_mbox
中取出消息,然后调用tcpip_thread_handle_msg
函数根据消息类型对消息进行处理。
static void
tcpip_thread_handle_msg(struct tcpip_msg *msg)
{switch (msg->type) {......case TCPIP_MSG_INPKT:LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));if (msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif) != ERR_OK) {pbuf_free(msg->msg.inp.p);}memp_free(MEMP_TCPIP_MSG_INPKT, msg);break;......}
}
在判断到消息类型msg->type
为TCPIP_MSG_INPKT
时,会执行这个消息对应的msg->msg.inp.input_fn
函数,并传入参数msg->msg.inp.p
和msg->msg.inp.netif
。
TCPIP_MSG_INPKT
类型的消息是在tcpip_inpkt
函数中被创建的。
err_t
tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
{
......msg->type = TCPIP_MSG_INPKT;msg->msg.inp.p = p;//网卡接收到的数据msg->msg.inp.netif = inp;//网卡对应的结构体msg->msg.inp.input_fn = input_fn;//处理本包数据的函数//向tcpip_mbox邮箱中发送这个消息if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK) {memp_free(MEMP_TCPIP_MSG_INPKT, msg);return ERR_MEM;}
......
}
tcpip_inpkt
函数在tcpip_input
函数中被调用
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
#if LWIP_ETHERNET//判断是否支持ARP协议if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {return tcpip_inpkt(p, inp, ethernet_input);} else
#endif /* LWIP_ETHERNET */return tcpip_inpkt(p, inp, ip_input);
}
如果判断相应网卡支持ARP就将ethernet_input
函数设置为这包数据的处理函数。
如果不支持就把ip_input
设置为处理函数,ip_input
就是ip4_input
函数,将这包数据作为IP数据报进行处理。
接下来是ethernet_input
部分源码:
err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{struct eth_hdr *ethhdr;u16_t type;
......//获取网卡接受数据的以太网首部ethhdr = (struct eth_hdr *)p->payload;
......//在以太网首部中获取本包数据的类型type = ethhdr->type;
......switch (type) {......case PP_HTONS(ETHTYPE_IP): //如果本包数据是IP数据报...... ip4_input(p, netif);...break;case PP_HTONS(ETHTYPE_ARP): //如果本包数据是ARP数据报......etharp_input(p, netif);...break;......default:...}return ERR_OK;
}
以上代码中先从数据获取以太网首部,结构体eth_hdr
内容为:
struct eth_hdr {PACK_STRUCT_FLD_S(struct eth_addr dest);PACK_STRUCT_FLD_S(struct eth_addr src);PACK_STRUCT_FIELD(u16_t type);
} PACK_STRUCT_STRUCT;
刚好对应以太网帧结构:
然后获取本包数据类型,并进行相应的处理。
再回到tcpip_input
函数。
在TCPIP_Init
函数中的
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
将tcpip_input
与网卡绑定起来,其中gnetif
是网卡对应结构体,ipaddr
是IP地址,netmask
是子网掩码,gw
是网关,ethernetif_init
是网卡的初始化函数,tcpip_input
是网卡消息处理函数。
在netif_add
函数里通过
netif->input = input;
完成绑定。之后netif->input
就代表了tcpip_input
函数。
在ethernetif_input
函数中取出网卡数据,并调用netif->input
进行处理。
void ethernetif_input(void *pParams) {struct netif *netif;struct pbuf *p = NULL;netif = (struct netif*) pParams;LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));while(1) {//等待信号量s_xSemaphore,有说明网卡有数据需要处理if(xSemaphoreTake( s_xSemaphore, portMAX_DELAY ) == pdTRUE){......p = low_level_input(netif);//取出网卡接受到的数据...if(p != NULL){...//调用tcpip_input函数进行处理if (netif->input(p, netif) != ERR_OK){LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));pbuf_free(p);p = NULL;}...}}}
}
ethernetif_input
函数在low_level_init
函数中被创建成一个任务的执行函数。它的主要任务是等待信号量s_xSemaphore
,有信号量说明网卡接受到了数据,然后取出数据做进一步处理。
而s_xSemaphore
信号量是在以太网接受完数据触发中断后,在回调函数里释放的。
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
以上分析涉及到lwip处理数据的3个任务:
任务 | 功能 |
---|---|
网口接受数据完成中断任务 | 用于释放网卡有数据需要处理的信号。 |
ethernetif_input任务 | 用于等待网卡有消息信号,并从网卡取出数据封装成消息放入邮箱中。 |
tcpip_thread任务 | 用于从邮箱中取出消息,并做进一步协议栈的处理(ARP、IP、TCP、UDP)。 |