LWIP协议 | 理论基础知识解析
说在前面:
这是通过 “幕布”写的思维导图笔记。文字看起来效果没有思维导图好,不知为何思维导图转正的图片上传不了,所以开启了一个笔记在线分享版。点击阅读。
文字版本:
- 动态内存管理
- 库自动分配
- 动态内存堆(Heap)
- 优点
- 随心所欲分配需要的大小合理内存块
- 缺点
- 释放后会存在内存碎片
- 开辟内存堆,模拟 C 运行时库的内存分配
- mem_init( ) 内存堆的初始化函数
- mem_malloc( ) 申请分配内存
- mem_calloc( ) 是对 mem_malloc( )函数的简单包装
- 动态内存池
- 用户需定义
- MEM_USE_POOLS = 1
- MEM_USE_CUSTOM_POOLS = 1
- LWIP_MALLOC_MEMPOOL_START
- LWIP_MALLOC_MEMPOOL(20, 256)
- LWIP_MALLOC_MEMPOOL(10, 512)
- LWIP_MALLOC_MEMPOOL(5, 1512)
- LWIP_MALLOC_MEMPOOL_END
- 用户需定义
- 优点
- 动态内存池(Pool)
- 优点
- 实现简单,内存的分配、释放效率高,有效防止内存碎片的产生
- 缺点
- 浪费部分内存
- 实现
-
- 里面的东西可以被简化为诸多条 LWIP_MEMPOOL(name,num,size,desc)。 又由于用了 define 关键字将 LWIP_MEMPOOL(name,num,size,desc)定义为+((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size))), 所以, memp_std.h 被编译后就为一条一条的+(), +(), +(), +() 数组 memp_memory 等价定义为: static u8_t memp_memory [ MEM_ALIGNMENT – 1 +()+() ….];
- 全局变量
- memp_num:用于保存各种类型缓冲池的成员数目
- memp_sizes:用于保存各种类型缓冲池的结构大小
- memp_tab:用于指向各种类型缓冲池当前空闲节点
- 实现函数
- memp_init(): 内存池的初始化,主要是为每种内存池建立链表 memp_tab,其链表是逆序的,此外,如果有统计功能使能的话,也把记录了各种内存池的数目
- memp_malloc(): 如果相应的 memp_tab 链表还有空闲的节点,则从中切出一个节点返回,否则返回空
- memp_free(): 把释放的节点添加到相应的链表 memp_tab 头上。
-
- 优点
- 数据包 Pbuf(可能使用任意 pbuf 类型,很可能的情况是,一大串不同类型的 pbufs 连在一起,用以保存一个数据包的数据 )
- 目的是合理利用内存池和内存块这两种分配策略
- 高效的数据包管理核心
- 即能海纳百川似的兼容各种类型的数据
- 又能避免在各层之间的复制数据的巨大开销。
-
- struct pbuf *next;
- 链在一个链表上
- 指针指向下一个 pbuf 结构
- void *payload;
- 数据指针
- 指向该 pbuf 管理的数据的起始地址
- u16_t tot_len;
- 当前 pbuf 和其后所有 pbuf的有效数据的长度
tot_len 字段是 len 字段与 pbuf 链中随后一个 pbuf 的 tot_len 字段的和;pbuf 链中第一个 pbuf 的 tot_len 字段表示整个数据包的长度,而最后一个 pbuf 的 tot_len字段必和 len 字段相等
- 当前 pbuf 和其后所有 pbuf的有效数据的长度
- u16_t len;
- 当前 pbuf 中的有效数据长度
- u8_t type;
- pbuf 的类型
- PBUF_RAM
- 通过内存堆分配得到 的
- 用的最多
- 申请时,内存堆中分配相应的大小
- p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset)+LWIP_MEM_ALIGN_SIZE(length)); (其中 p 是 pbuf 型指针 )
-
- PBUF_POOL
- 与 PBUF_RAM 相似
- 通过内存池分配得到的
- 多用于 接受数据包 时
- 申请 时,协议栈会在内存池中分配适当的内存池个数以满足需要的申请大小
- p = memp_malloc(MEMP_PBUF_POOL); (其中 p 是 pbuf 型指针 )
-
- PBUF_ROM
- 在内存堆中分配 一个相应的 pbuf 结构头,而不申请数据区的空间
- 指向 ROM 空间内的某段数据
- p = memp_malloc(MEMP_PBUF);
- 调用内存池分配函数 memp_malloc 进行内存分配
- 请求的内存 池 类 型 为 MEMP_PBUF , 而 不 是 MEMP_PBUF_POOL
- MEMP_PBUF 类型的内存池大小恰好为一个 pbuf 头的大小
- 这种池是 LWIP 专为PBUF_ROM 和 PBUF_REF 类型的 pbuf 量身制作的
-
- PBUF_REF
- 在内存堆中分配 一个相应的 pbuf 结构头,而不申请数据区的空间
- 指向 RAM 空间内的某段数据
- p = memp_malloc(MEMP_PBUF);
- 调用内存池分配函数 memp_malloc 进行内存分配
- 请求的内存 池 类 型 为 MEMP_PBUF , 而 不 是 MEMP_PBUF_POOL
- MEMP_PBUF 类型的内存池大小恰好为一个 pbuf 头的大小
- 这种池是 LWIP 专为PBUF_ROM 和 PBUF_REF 类型的 pbuf 量身制作的
-
- PBUF_RAM
- pbuf 的类型
- u8_t flags;
- pbuf 的类型
- 值设为 0 表示初始化一个 pbuf
- u16_t ref;
- 该 pbuf 被引用的次数
- 设置为 1 表示初始化
- 有其他 pbuf 的 next 指针指向该 pbuf 时,
- 该 pbuf 的 ref 字段值加一
- 删除一个Pbuf
- 为 1 删除成功
- 非 1 删除失败
- Pbuf 释放
-
在 LWIP 中,因为 pbuf 的 ref 字段表示该 pbuf 被引用的次数,当 pbuf 被创建时,该字段的初始值为 1,由此可判断,当 pbuf 的 ref 字段为 1 时,该 pbuf 才可以被删除,所以位于pbufs 链表中间的 pbuf 结构是不会被删除成功的,因为他们的 ref 值至少是 2。 - memp_free()函数删除
- PBUF_POOL 类型
- PBUF_ROM 类型
- PBUF_REF 类型
- mem_free()函数删除
- PBUF_RAM 类型
-
- 网络接口结构
- 特点
- 逻辑上看分为四层
- 链路层
- 网络层
- 传输层
- 应用层
- 各层协议之间可以进行或多或少的交叉存取
- 各层可以更有效地重用缓冲区
- 应用进程和协议栈代码可以使用相同的内存
- 应用可以直接读写内部缓存,因此节省了执行拷贝的开销
- 逻辑上看分为四层
- 链路层
- 硬件网络接口
- netif 网络结构体实现
- 源代码分析
-
-
- (1)声明了一个 netif 结构的变量 enc28j60 (网卡芯片enc28j60 )
- (2)声明了三个分别用于暂存 IP 地址、子网掩码和网关地址的变量,它们是 32 位长度的
- (3)~ (5)分别是对上述三个地址值的初始化
- (6)只需初始化上面所述的全局变量 netif_list 即可: netif_list = NULL
- (7)调用 netif_add 函数初始化变量 enc28j60
- 初始化函数调用了用户自己定义的底层接口初始化函数
- ethernetif_init函数
- low_level_init 函数
- 初始化函数调用了用户自己定义的底层接口初始化函数
- (8)调用 netif_set_default 函数初始化缺省网络接口
- 除了有 netif_list 全局变量指向 netif 网络接口结构的链表,
- 还有全局变量 netif_default 全局变量指向缺省的网络接口结构。
- 把变量 enc28j60 描述的网络接口设置为缺省的网络接口
- (9)调用函数 netif_set_up 使能网络接口
- netif->flags |= NETIF_FLAG_UP
-
- 硬件网络接口
- 特点
- 以太网数据接收与发送
- 数据包的接收
- 通过专门的进程实现
- 应用系统三个进程
- 上层应用程序
- LWIP 协议栈
- 底层硬件数据包接收
- 注意点
- 数据包接收的方法
- 查询方式
- 进程其优先级必须低于系统中其他进程的优先级
- 缺点
- 数据包的接收得不到及时的响应
- 改进方式
- 中断方式
- 当有数据包到来时,网卡芯片产生一个中断信号,处理器进入中断处理,并释放一个信号量。
- 中断退出后,数据包接收进程得到信号量,并从网卡芯片中读取数据包,并将数据包递交给上层进行处理。
- 中断方式
- 查询方式
- 数据包接收的方法
- htons(ethhdr->type)函数的使用
- 功能
- 将一个半字长的数据从网络字节顺序转换到我们的处理器支持的字节顺序
- 通常采用的存储机制
- big-endian 和 little-endian,即大端和小端
- 功能
- netif->input 函数
- 在结构 enc28j60 初始化时已经被设置为指向tcpip_input 函数
- 所以实际上上面是调用 tcpip_input 函数往上层递交数据包
- 注意点
- 数据包的发送
- 不需要专门的进程来实现
- 实现方式
- ·当上层有数据包要发送时,直接调用 netif->linkoutput 发送数据包就可以了
- 数据包的接收