LWIP使用经验---变态级(好文章)

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

图1内存池与内存堆

1.     数据包管理

数据包管理结构pbuf共有四种类型,它们的特点和使用场合如表1所示。

类别分配方式特点使用场合
PBUF_RAM内存堆,包括pbuf和数据区长度不定,分配耗时应用程序和协议栈
PBUF_POOL内存池,包括pbuf和数据区长度固定,分配快中断服务程序
PBUF_ROM内存池,仅包括pbuf所指数据位于ROM中应用程序引用内存区
PBUF_REF内存池,仅包括pbuf所指数据位于RAM中应用程序引用内存区

表1  pbuf类型与特点

每一种pbuf分配内存的方式都不一样,如图2所示。

图2四种数据包管理结构

只有选择合适的pbuf类型才能发挥LWIP的最大性能,一个数据包可能是多种pbuf的组合,用链表连接起来,如图3所示。

图3  pbuf链表

2.     设置内存大小

为LWIP开辟一个专用的内存堆是应该的,这样一来LWIP的mem_alloc()和mem_free()都将基于该堆内存进行分配和回收,不影响其他系统内存的使用。如图1左所示,lwipopt.h文件中宏MEM_SIZE定义了堆区的大小,对于一个负荷较重的系统堆区需要分配较大。

图4堆和PBUF_ROM内存区

LWIP使用PBUF_ROM类型的内存池来发送“只读”数据(处于ROM中或者其他进程中不可修改),宏MEMP_NUM_PBUF定义了该缓冲池的个数,如图2右所示。

在ISR(中断服务程序中)经常需要快速分配一部分内存进行数据存储,这是通过PBUF_POOL类型的缓冲区实现的。为此需要定义两个宏:PBUF_POOL_SIZE定义缓冲池的个数,PBUF_POOL_BUFSIZE定义单个缓冲区的大小,如图5所示。

图5 PBUF_POOL内存区

3 宏编译开关

若定义MEM_LIBC_MALLOC=1,直接使用C库中的malloc、free来分配动态内存;否则使用LWIP自带的mem_malloc、mem_free等函数。

若定义MEMP_MEM_MALLOC=1,则memp.c中的全部内容不会被编译,用内存堆来实现内存池分配,使用这种方式得考虑是否能忍受内存堆分配带来的时间延迟。

若定义MEM_USE_POOLS=1,则mem.c中的全部内容不会被编译,用内存池来实现内存堆的分配,使用这种方式得考虑是否能忍受因为POOL内存固定大小而带来的内存浪费。此外用户需要定义宏MEM_USE_CUSTOM_POOLS=1,还需要额外实现一个头文件lwippools.h,并在其中开辟一些用于内存堆分配函数的内存池POOL,开辟空间的格式如下:

LWIP_MALLOC_MEMPOOL_START

LWIP_MALLOC_MEMPOOL(20, 256)

LWIP_MALLOC_MEMPOOL(10, 512)

LWIP_MALLOC_MEMPOOL_END

二 LWIP启动时序

图6展示了LWIP启动时序,大部分函数都是LWIP自带的,主要的移植代码是eth_init()实现初始化以太网接口,启动程序会创建2个线程:tcpip_thread负责LWIP的绝大部分工作(主要是协议栈的解析和系统运行),ethernetif_thread负责从网口接收数据包再交付给tcpip_thread线程进行处理。

图6 LWIP启动函数

三 LWIP运行逻辑

1 接收数据包

图7接收数据包

当以太网口接收到一个数据包后,EMAC_IRQ中断服务程序通过信号量通知ethernetif线程,ethernetif线程调用low_level_input()接收该数据包并通过邮箱交付给tcpip_thread线程,tcpip_thread根据该数据包的类型进行相应处理。它是建立在消息传递的基础上的,如图8所示。

图8数据包消息的产生和处理

2  SequentialAPI函数调用

API设计的核心在于让用户进程负责尽可能多的工作,例如数据的计算、拷贝等;而协议栈进程只负责简单的通信工作,这是很合理的,因为系统可能存在多个应用程序,它们都使用协议栈进程提供的通信服务,保证内核进程的高效性和实时性是提高系统性能的重要保障。进程之间通信使用IPC技术,包括邮箱、信号量和共享内存,如图9所示。

图9协议栈API实现

以函数netconn_bind()为例看API是如何实现的,首先用户程序中调用函数netconn_bind()绑定一个连接,则这个函数实现时,是通过向内核进程发送一个TCPIP_MSG_API类型的消息,告诉内核进程执行do_bind函数:在消息发送后,函数阻塞在信号量上,等待内核处理该消息;内核在处理消息时,会根据消息内容调用do_bind,而do_bind会根据连接的类型调用内核函数udp_bind、tcp_bind或raw_bind;当do_bind执行完后,它会释放信号量,这使被阻塞的netconn_bind得以继续执行,整个过程如图10所示。

图10 API函数实现

四 TCP/IP核心知识点

1.       滑动窗口

图11滑动窗口

接收窗口相关的字段中,rcv_nxt是自己期望收到的下一个字节编号,rcv_wnd表示接收窗口的大小,rcv_ann_wnd表示将向对方通告的窗口大小值,这个值在报文发送时会被填在首部中的窗口大小字段,rcv_ann_right_edge记录了上一次窗口通告时窗口右边界取值。上面这四个字段都会随着数据的发送和接收动态地改变,如图12所示。

图12接收窗口

发送窗口中,lastack记录了被接收方确认的最高序列号,snd_nxt表示自己将要发送的下一个数据的起始编号,snd_wnd记录了当前的发送窗口大小,它常被设置为接收方通告的接收窗口值,snd_lbb记录了下一个被应用程序缓存的数据的起始编号,如图10所示。

上面这四个字段的值也是动态变化的,每当收到接收方的一个有效ACK后,lastack的值就做相应的增加,指向下一个待确认数据的编号,当发送一个报文后,snd_nxt的值就做相应的增加,指向下一个待发送数据,snd_nxt和lastack之间的差值不能超过snd_wnd的大小。

由于实际数据发送时是按照报文段的形式组织的,因此可能存在这样的情况:即使发送窗口允许,但并不是窗口内的所有数据都能发送以填满窗口,如图13中编号为11~13的数据,可能因为它们太小不能组织成一个有效的报文段,因此不会被发送。发送方会等到新的确认到来,从而使发送窗口向右滑动,使得更多的数据被包含在窗口中,这样再启动下一个报文段的发送。

图13发送窗口

2.       三次握手

图14连接建立过程

3.       断开连接

图15连接断开过程

4.      TCP状态转换

图16  TCP状态转换图

5.       同时打开

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

-H1g2fjlz-1715887908656)]
[外链图片转存中…(img-CGuMvabB-1715887908657)]

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值