lwip系列一之数据的收发
lwip宏观的
经过一段时间的反复折磨,也看了许多资料,做一下学习总结,同时希望通过向他人表述来加深对内容的理解。驱动程序是参照野火的,但是我觉得这里面有点小小的疑问没有解决。
我不知道大家曾经是否有和我一样的疑问,学完计算机网络后,对计算机网络的各个层次的原理有所了解,但是有个疑问就是如何将这个协议用起来,为了能更好的说明数据收发过程,我们暂时将这个协议当成一个黑盒子,观察其如何在计算机中立起来。
参考下面的图
将lwip看成是看成是黑盒子,他能接收数据,然后对其进行处理,怎么处理暂时不管,然后递交给应用端;
反之,应用端发送数据,经过协议处理后,递交给外设,然后发送出去。
所以说这个lwip可以认为是一个消息处理器,对接收的消息进行处理,对发送的消息进行处理。
所以说可以直接将这个协议栈打包成一个高优先级的线程,干什么呢,不断的去读取邮箱中的需要处理的数据(包含发送和接收)谁先来,谁先处理。这样就将协议“立”起来了。
实际中就有一个线程,叫做tcpip_thread ,有个邮箱tcpip_mbox 来存放等待处理的消息,这个线程在干什么事呢?
不断的尝试从邮箱中取数据,取到呢,就进行消息处理
取不到呢?判断一下有没有超时事件,
没有超时事件,那就一直阻塞,任务进入阻塞态,让其他任务运行
有超时事件,获取下次超时的时间,然后阻塞这段时间,然后进行超时检查。大致的逻辑是这样的。具体的代码细节,暂时不管,这里主要是方便我的记忆。
这样就在整体上能对lwip有个认识。
然后呢,关注数据的收发了,这里呢只描述上图左边,靠近底层的数据收发的实现过程。就是在那种软件、硬件交叉的地方。
数据的接收过程
我们先来看一张逻辑框图:
对于接收过程:我们传输的电平信号通过网线的接口进入到外部PHY中,然后再被MAC所接收,当然这个MAC具有地址过滤的机制们也就是说能根据MAC地址能进行过滤,还会进行CRC校验进行帧的接收,这些是可配置的,配置完后,硬件会自动帮你做的,还会硬件帮你过滤掉以太网帧的前导符,帧首符,还有CRC校验字段。
MAC完成到它的功能后,就会将接收到的数据放入2k字节的接收FIFO中,然后呢,通过DMA将数据传送至物理内存,直白一点说就是传送至你定义数组啊之类的能存放数据的空间。
对于发送过程:就是上述的逆过程了。
数据包装过程
我们知道,tcp/ip是不同层次的,如果是层与层之间递交数据时,要发生数据的拷贝,那么整个lwip内核的运行效率就会十分低下。
所以说为了避免数据产生层层拷贝,引入了pbuf 结构体,通过一个指向该结构体的指针来访问全部数据。
该结构体的原理图如下:
此时我们大致来说说数据的流向:
数据被以太网外设接收后,再通过DMA传输至物理内存,这里的物理内存呢实际上就是数组(也称为缓存),也就是说数据经过DMA后,就到一个数组中,等着你来用。你不能直接占据这个空间来使用,因为这个空间还需要接收其他数据,所以数据来了后要马上把这个空间的数据搬出来,把这个空间释放出来以接收其他以太网数据,因为以太网一直不断的在接收数据,你不出来,后续数据不就没有空间可以放了吗。
所以说,一旦有数据之后呢,就要立刻去将数据清出来,放入上面提及的pbuf 中。
为了实现上面这句话,在lwip中呢,创建了一个计数信号量和一个接收线程来实现任务同步的功能,为什么是计数信号量而不是二值信号量呢,二值不适合频繁发生中断的场合。
在以太网外设初始化时,初始化为接收完成中断,然后在接收中断完成的回调函数中释放信号量,信号量的释放导致接收线程的运行,接收线程干什么事呢?核心就是将缓存中的数据清出来,然后包装成pbuf ,然后对pbuf再进行简单的包装变成消息,然后投递给前面提到的tcpip_mbox ,然后线程tcpip_thread 就运行起来了,就可以开始处理消息了。
到这里为止,我们涉及到关键的二个线程,一个邮箱,一个计数信号量。
一个tcp/ip处理线程tcpip_thread :不断地尝试去读邮箱的消息,然后进行消息处理
一个先进先出的邮箱tcpip_mbox :不管是接收的还是需要发送的,最终都会在邮箱中排队,等待处理
一个计数信号量s_xSemaphore :用于在接收完成时,触发中断,在中断回调中释放信号量,触发接收线程的运行
一个接收线程ethernetif_input :核心是将缓存的数据清出来,包装成pbuf ,再包装成消息,发到邮箱tcpip_mbox
上述呢,基本上将整个lwip的运行,以及数据的流向大致有个印象。当然要通过一篇文章将所有方方面面讲清楚是不可能的,最终要搞清楚什么的,必须啃代码,这里只是整体有个印象,能将整个过程联通起来。
再细节一点
前面提到数据的流向个过程是
缓存——>pbuf——>消息。前面认为缓存是一个数组,确实是一个数组,每个缓存呢对应有一个描述符来管理,这个描述符是个结构体,按道理说是软件来管理的,但是实际上确是软件定义了这个结构体,但是里面一些状态字段的更新之类的却是硬件帮你做的,搞得有点像寄存器,这是我个人就觉得是最抽象的地方,你说你软件搞得,看看程序就行,硬件搞得,看看原理就行,对吧,这里呢,这个描述符搞得又与软件相关,又与硬件相关。先看看几个定义:
ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB] ;
ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB] ;
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] ;
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] ;
从上面可以看到,描述符是就是特定类型的结构,当然结构体内部成员字段代表什么含义就去看参考手册,这里不说,定义的是一个结构体数组,每个描述符对应一个缓存,缓存就是数组,来源于下面的Rx_Buff 缓冲区(本质上就是一个二维数组)
这里区别一下缓存与缓冲区的关系,缓存是缓冲区的一部分,像野火的驱动设计中就设计缓存为1/8的缓冲区大小。缓存就是缓冲区的一个子集。
我们接收到的数据呢,就存放在缓存里,我们发送数据呢,就将数据放入发送的缓冲区中,并将发送描述符第一个成员字段的OWN 置位,就把数据发送出去了。
我们通过下面一张图来描述缓存,描述符的关系。
写过一点程序的就知道,光定义一个结构体,内部是空的,所以要建立上图中的这个样子,需要在初始化时调用一个函数
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB); 来建立起上图的关系。这个函数会在后续进行注释,可以看看其是怎么样的过程。
好了,到此,基本上呢原理性的东西基本上说的差不多了。下面能就对关键的代码语句进行说明与注释,当然有些东西在一篇文章中没法说的特别详细,大致看看,同时加深自己的印象。
数据接收过程关键性代码阅读
1)前面提到过,在接收中断中的服务函数中接收完成回调函数中释放信号量:来触发接收线程的运行,代码如下:
extern xSemaphoreHandle s_xSemaphore;
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
LED2_TOGGLE;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
2)释放信号量后,线程ethernetif_input 运行,这个线程的代码如下:核心逻辑还是如文章上面所说,具体涉及的重要函数的注释放在文章后面,感兴趣的可以看看
void ethernetif_input(void *pParams)
{
struct netif *netif;
struct pbuf *p = NULL;
netif = (struct netif*) pParams;
while(1)
{
if(xSemaphoreTake( s_xSemaphore, portMAX_DELAY ) == pdTRUE)
{
taskENTER_CRITICAL();
TRY_GET_NEXT_FRAGMENT:
p = low_level_input(netif);
taskEXIT_CRITICAL();
if(p != NULL)
{
taskENTER_CRITICAL();
if (netif->input(p, netif) != ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
else
{
xSemaphoreTake( s_xSemaphore, 0);
goto TRY_GET_NEXT_FRAGMENT;
}
taskEXIT_CRITICAL();
}
}
}
}
函数low_level_input的注释
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
struct pbuf *q = NULL;
uint16_t len = 0;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i=0;
if (HAL_ETH_GetReceivedFrame(&heth) != HAL_OK)
{
return NULL;
}
len = heth.RxFrameInfos.length;
buffer = (uint8_t *)heth.RxFrameInfos.buffer;
PRINT_INFO("receive frame len : %d\n", len);
if (len > 0)
{
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
if (p != NULL)
{
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
bufferoffset = 0;
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
for (i=0; i< heth.RxFrameInfos.SegCount; i++)
{
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
heth.RxFrameInfos.SegCount =0;
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
heth.Instance->DMASR = ETH_DMASR_RBUS;
heth.Instance->DMARPDR = 0;
}
return p;
}
函数tcpip_input注释
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
#if LWIP_ETHERNET
if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
return tcpip_inpkt(p, inp, ethernet_input);
} else
#endif
return tcpip_inpkt(p, inp, ip_input);
}
函数tcpip_inpkt注释
该函数是将数据打包成消息,并将消息发送至邮箱中
err_t
tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn)
{
struct tcpip_msg *msg;
msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
if (msg == NULL)
{
return ERR_MEM;
}
msg->type = TCPIP_MSG_INPKT;
msg->msg.inp.p = p;
msg->msg.inp.netif = inp;
msg->msg.inp.input_fn = input_fn;
if (sys_mbox_trypost(&tcpip_mbox, msg) != ERR_OK)
{
memp_free(MEMP_TCPIP_MSG_INPKT, msg);
return ERR_MEM;
}
return ERR_OK;
}
数据发送过程关键性代码阅读
数据发送最终就是将数据放到发送缓存中,然后就可以发送出去,与接收不同的是,数据接收对应有个接收线程,发送没有发送线程,数据要通过以太网发送出去,最终都要调用函数ethernet_output
函数ethernet_output注释
err_t
ethernet_output(struct netif * netif, struct pbuf * p,
const struct eth_addr * src, const struct eth_addr * dst,
u16_t eth_type) {
struct eth_hdr *ethhdr;
u16_t eth_type_be = lwip_htons(eth_type);
{
if (pbuf_add_header(p, SIZEOF_ETH_HDR) != 0)
{
goto pbuf_header_failed;
}
}
ethhdr = (struct eth_hdr *)p->payload;
ethhdr->type = eth_type_be;
SMEMCPY(ðhdr->dest, dst, ETH_HWADDR_LEN);
SMEMCPY(ðhdr->src, src, ETH_HWADDR_LEN);
return netif->linkoutput(netif, p);
pbuf_header_failed:
return ERR_BUF;
}
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
static sys_sem_t ousem = NULL;
if(ousem == NULL)
{
sys_sem_new(&ousem,0);
sys_sem_signal(&ousem);
}
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = heth.TxDesc;
bufferoffset = 0;
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
sys_sem_wait(&ousem);
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
HAL_ETH_TransmitFrame(&heth, framelength);
errval = ERR_OK;
error:
if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
heth.Instance->DMASR = ETH_DMASR_TUS;
heth.Instance->DMATPDR = 0;
}
sys_sem_signal(&ousem);
return errval;
}
线程tcpip_thread删了一小部分
static void
tcpip_thread(void *arg)
{
struct tcpip_msg *msg;
while (1)
{
TCPIP_MBOX_FETCH(&tcpip_mbox, (void **)&msg);
if (msg == NULL)
{
continue;
}
tcpip_thread_handle_msg(msg);
}
}
该线程核心就是不断尝试取邮箱中消息,然后进行消息处理。
|