lwip系列一之数据的收发

本文介绍了lwip协议栈的数据收发过程,包括 lwip 的宏观理解、数据接收和发送的细节,以及关键代码的注释。 lwip 作为一个消息处理器,处理接收和发送的消息,通过中断和信号量实现数据的高效处理。接收过程中,数据经过PHY和MAC硬件处理,通过DMA传输至内存,然后由接收线程进行数据清理和打包。发送则是接收的逆过程。 lwip 使用结构体避免层层拷贝,提高效率。
摘要由CSDN通过智能技术生成

lwip系列一之数据的收发

lwip宏观的

经过一段时间的反复折磨,也看了许多资料,做一下学习总结,同时希望通过向他人表述来加深对内容的理解。驱动程序是参照野火的,但是我觉得这里面有点小小的疑问没有解决。

我不知道大家曾经是否有和我一样的疑问,学完计算机网络后,对计算机网络的各个层次的原理有所了解,但是有个疑问就是如何将这个协议起来,为了能更好的说明数据收发过程,我们暂时将这个协议当成一个黑盒子,观察其如何在计算机中起来。

参考下面的图

image-20220221223233960

将lwip看成是看成是黑盒子,他能接收数据,然后对其进行处理,怎么处理暂时不管,然后递交给应用端;

反之,应用端发送数据,经过协议处理后,递交给外设,然后发送出去。

所以说这个lwip可以认为是一个消息处理器,对接收的消息进行处理,对发送的消息进行处理。

所以说可以直接将这个协议栈打包成一个高优先级的线程,干什么呢,不断的去读取邮箱中的需要处理的数据(包含发送和接收)谁先来,谁先处理。这样就将协议“立”起来了。

实际中就有一个线程,叫做tcpip_thread,有个邮箱tcpip_mbox来存放等待处理的消息,这个线程在干什么事呢?

不断的尝试从邮箱中取数据,取到呢,就进行消息处理

取不到呢?判断一下有没有超时事件,

没有超时事件,那就一直阻塞,任务进入阻塞态,让其他任务运行

有超时事件,获取下次超时的时间,然后阻塞这段时间,然后进行超时检查。大致的逻辑是这样的。具体的代码细节,暂时不管,这里主要是方便我的记忆。

这样就在整体上能对lwip有个认识。

然后呢,关注数据的收发了,这里呢只描述上图左边,靠近底层的数据收发的实现过程。就是在那种软件、硬件交叉的地方。

数据的接收过程

我们先来看一张逻辑框图:

image-20220220155306248

对于接收过程:我们传输的电平信号通过网线的接口进入到外部PHY中,然后再被MAC所接收,当然这个MAC具有地址过滤的机制们也就是说能根据MAC地址能进行过滤,还会进行CRC校验进行帧的接收,这些是可配置的,配置完后,硬件会自动帮你做的,还会硬件帮你过滤掉以太网帧的前导符,帧首符,还有CRC校验字段。

MAC完成到它的功能后,就会将接收到的数据放入2k字节的接收FIFO中,然后呢,通过DMA将数据传送至物理内存,直白一点说就是传送至你定义数组啊之类的能存放数据的空间。

对于发送过程:就是上述的逆过程了。

数据包装过程

我们知道,tcp/ip是不同层次的,如果是层与层之间递交数据时,要发生数据的拷贝,那么整个lwip内核的运行效率就会十分低下。

所以说为了避免数据产生层层拷贝,引入了pbuf结构体,通过一个指向该结构体的指针来访问全部数据。

该结构体的原理图如下:

image-20220222110113487

此时我们大致来说说数据的流向:

数据被以太网外设接收后,再通过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置位,就把数据发送出去了。

我们通过下面一张图来描述缓存,描述符的关系。

image-20220219223116744

写过一点程序的就知道,光定义一个结构体,内部是空的,所以要建立上图中的这个样子,需要在初始化时调用一个函数

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);//将缓存数据取出,并打包成pbuf
      taskEXIT_CRITICAL();
      if(p != NULL)
      {
   
        taskENTER_CRITICAL();
        if (netif->input(p, netif) != ERR_OK)//将pbuf打包成消息,并发送至邮箱中这个函数是
            //tcpip_input(struct pbuf *p, struct netif *inp)
        {
   
          LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值