首先要知道有4中类型的数据,在pbuf.H里面有:
typedef enum {
PBUF_RAM, /* pbuf data is stored in RAM */
PBUF_ROM, /* pbuf data is stored in ROM */
PBUF_REF, /* pbuf comes from the pbuf pool */
PBUF_POOL /* pbuf payload refers to RAM */
} pbuf_type;
既然都是pbuf,那么分配内存都用这个函数pbuf_alloc,函数原型为:
struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
最后一个参数为类型。
看一下这个函数内部,如下:
/* pbuf references existing (non-volatile static constant) ROM payload? */
case PBUF_ROM:
/* pbuf references existing (externally allocated) RAM payload? */
case PBUF_REF:
/* only allocate memory for the pbuf structure */
p = memp_malloc(MEMP_PBUF);
if (p == NULL) {
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("pbuf_alloc: Could not allocate MEMP_PBUF for PBUF_%s.\n",
(type == PBUF_ROM) ? "ROM" : "REF"));
return NULL;
}
/* caller must set this field properly, afterwards */
p->payload = NULL;
p->len = p->tot_len = length;
p->next = NULL;
p->type = type;
break;
default:
发现PBUF_ROM后面竟然没有break,就说明
PBUF_ROM和
PBUF_REF在pbuf_alloc函数内部是一样处理方式。
先说PBUF_ROM和PBUF_REF这两种,这两个对应在lwipOPTS.H里面的宏定义为:
/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory), this
should be set high. */
#define MEMP_NUM_PBUF 100
我把这个宏定义MEMP_NUM_PBUF设置为0,竟然程序也好用。于是我在函数pbuf_alloc的case PBUF_REF里面加个断点,竟然整个程序运行过程中,没有进入到这个断点。这就解释我为啥将这个宏定义设置为0也好用了。
老衲五木的这篇文章要看看,非常好。《LwIP协议栈源码详解——TCP/IP协议的实现》数据包pbuf(当然还有这篇《LwIP协议栈源码详解——TCP/IP协议的实现》动态内存管理),这篇文章提到的“LWIP中常用到的内存分配策略有两种,一种是内存堆分配,一种是内存池分配”,
说内存堆分配有碎片产生,我的理解是只要申请内存和释放内存一 一对应起来就不会产生碎片。
其实这几篇文章我记得几年前看过,并且研究的比较透彻,现在竟然又都忘了不少,幸好能想起一些。内存堆不用链表 串联起来。而内存池MEMP_PBUF_POOL需要用链表串联起来。内存池MEMP_PBUF这个有点特殊,就是上文关于REF和ROM的,就是这个宏定义#define MEMP_NUM_PBUF 100。
这个东东却是稍微耗费点内存,每个耗费16字节(每个耗费这么少!!那么不管实际程序用不用得到,完全可以把这个值设置大一点)。(注:耗费的这16个字节我估计是因为结构体形式的变大导致的,并没有把指向的实际内存变大。)MAP显示是bss区域,就是数组的形式耗费。
memp.C中有一部分变量通过const关键字放到了ROM里面,如下:
.constdata 0x080101e2 Section 36 memp.o(.constdata)
memp_sizes 0x080101e2 Data 18 memp.o(.constdata)
memp_num 0x080101f4 Data 18 memp.o(.constdata)
还有一部分以数组形式耗费了RAM,如下:
.bss 0x20007ba8 Section 12699 memp.o(.bss)
memp_tab 0x20007ba8 Data 36 memp.o(.bss)
memp_memory 0x20007bcc Data 12663 memp.o(.bss)
耗费最猛的就是这个老哥了,如下:
/** This is the actual memory used by the pools (all pools in one big block). */
static u8_t memp_memory[MEM_ALIGNMENT - 1
#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
#include "lwip/memp_std.h"
];
那么memp_std.h"里面有各种POOL,而lwipopts.H和opt.H里面的宏定义很多都来自memp_std.h。
在memp_STD.H里面摘几个常用的:
#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB")
#endif /* LWIP_UDP */
#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB, MEMP_NUM_TCP_PCB, sizeof(struct tcp_pcb), "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN, sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG, MEMP_NUM_TCP_SEG, sizeof(struct tcp_seg), "TCP_SEG")
#endif /* LWIP_TCP */
#if (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS)) /* LWIP_TIMERS */
LWIP_MEMPOOL(SYS_TIMEOUT, MEMP_NUM_SYS_TIMEOUT, sizeof(struct sys_timeo), "SYS_TIMEOUT")
#endif /* LWIP_TIMERS */
LWIP_PBUF_MEMPOOL(PBUF, MEMP_NUM_PBUF, 0, "PBUF_REF/ROM")
LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE, PBUF_POOL_BUFSIZE, "PBUF_POOL")
可见在lwipopts.H里面的如下设置都是在memp_std.h里面的:
/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory), this
should be set high. */
#define MEMP_NUM_PBUF 0
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
connections. */
#define MEMP_NUM_TCP_PCB 5
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 60
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
segments. */
#define MEMP_NUM_TCP_SEG 16
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
timeouts. */
#define MEMP_NUM_SYS_TIMEOUT 10
/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 6
/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE 1500
=======================
下一步分析mem.C就是上文提到的内存堆。
看下mem.C占内存情况:
.data 0x2000006c Section 12 mem.o(.data)
ram 0x2000006c Data 4 mem.o(.data)
ram_end 0x20000070 Data 4 mem.o(.data)
lfree 0x20000074 Data 4 mem.o(.data)
.bss 0x20000394 Section 30740 mem.o(.bss)
占用30740字节的数组名字是ram_heap
ram_heap 0x20000394 Data 30740 mem.o(.bss)
在mem.C里面找到这个变量定义:
/** If you want to relocate the heap to external memory, simply define
* LWIP_RAM_HEAP_POINTER as a void-pointer to that location.
* If so, make sure the memory at that location is big enough (see below on
* how that space is calculated). */
#ifndef LWIP_RAM_HEAP_POINTER
/** the heap. we need one struct mem at the end and some room for alignment */
u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];
#define LWIP_RAM_HEAP_POINTER ram_heap
#endif /* LWIP_RAM_HEAP_POINTER */
这个宏MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT当中,MEM_SIZE_ALIGNED 是最大的,看一下MEM_SIZE_ALIGNED 是啥?
#define MEM_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MEM_SIZE)
看到了吧就是MEM_SIZE,我在lwipopts.H里面有宏定义如下:
/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE (30*1024)
而30*1024=30720 可见其他部分占据30740-30720=20字节。
那么MEM_SIZE这个值设置为多少好呢?这个就得看实际应用了。可以每次分配完后,监视一下剩余多少。不知道有没有这个功能?如果没有的话,估计是因为碎片化的原因,碎片多了,统计剩余内存就没有意义了。可以统计下最后用到的内存块之后的区域有多少啊。这样心里有数。也可以在分配内存的时候关注下分配了多少,然后把程序中所有的都加起来。
最后关注一下在lwipopts.H里面的这个MEMP_NUM_SYS_TIMEOUT这个宏定义,
在opt.H里面有
/**
* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active timeouts.
* (requires NO_SYS==0)
* The default number of timeouts is calculated here for all enabled modules.
* The formula expects settings to be either '0' or '1'.
*/
#ifndef MEMP_NUM_SYS_TIMEOUT
#define MEMP_NUM_SYS_TIMEOUT (LWIP_TCP + IP_REASSEMBLY + LWIP_ARP + (2*LWIP_DHCP) + LWIP_AUTOIP + LWIP_IGMP + LWIP_DNS + PPP_SUPPORT)
#endif
上面这个关系到函数LwIP_Periodic_Handle里面用到的定时功能。
还有在memp_std.h里面有
#if (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS)) /* LWIP_TIMERS */
LWIP_MEMPOOL(SYS_TIMEOUT, MEMP_NUM_SYS_TIMEOUT, sizeof(struct sys_timeo), "SYS_TIMEOUT")
#endif /* LWIP_TIMERS */
然后再lwipopts.H里面有
/**
* NO_SYS==1: Provides VERY minimal functionality. Otherwise,
* use lwIP facilities.
*/
#define NO_SYS 1
/**
* NO_SYS_NO_TIMERS==1: Drop support for sys_timeout when NO_SYS==1
* Mainly for compatibility to old versions.
*/
#define NO_SYS_NO_TIMERS 1
然后我把MEMP_NUM_SYS_TIMEOUT设置为2,竟然程序可以正常跑。
看了一下timer.H里面的
/* Timers are not supported when NO_SYS==1 and NO_SYS_NO_TIMERS==1 */
#define LWIP_TIMERS (!NO_SYS || (NO_SYS && !NO_SYS_NO_TIMERS))
相当于#define LWIP_TIMERS 0
就是timer功能没用上,那么函数LwIP_Periodic_Handle里面的timer是啥?在lwipopts.H里面有个注释写到:
/**
* NO_SYS_NO_TIMERS==1: Drop support for sys_timeout when NO_SYS==1
* Mainly for compatibility to old versions.
*/
所以说是不是涉及到新老版本。
所以这里就糊里糊涂吧。不关心这个问题。
就把lwipopts.H里面如下定义
#define MEMP_NUM_SYS_TIMEOUT 10
好的分析到这里,这篇文档大半已过,还有一部分内容就是 关于TCP的设置了。
就是下面这些东东:
/* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255
/* Controls if TCP should queue segments that arrive out of
order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0
/* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */
/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (8*TCP_MSS)
/* TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */
#define TCP_SND_QUEUELEN (2* TCP_SND_BUF/TCP_MSS)
/* TCP receive window. */
#define TCP_WND (2*TCP_MSS)
一个一个分析,先看TCP_QUEUE_OOSEQ ,如下
/* Controls if TCP should queue segments that arrive out of
order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0
好了,知道设置为0就可以了。至于是啥作用,不关心。
然后再看TCP_MSS ,看到其他几个宏定义都是TCP_MSS 的倍数。
看下opt.H里面咋讲的?如下:
/**
* TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
* you might want to increase this.)
* For the receive side, this MSS is advertised to the remote side
* when opening a connection. For the transmit size, this MSS sets
* an upper limit on the MSS advertised by the remote host.
*/
#ifndef TCP_MSS
#define TCP_MSS 536
#endif
看了下注释,基本上是看不懂,先去百度一下再回来。
弄接个链接,一个是华为的:TCP中的MSS解读,内容如下:
MSS 是TCP选项中最经常出现,也是最早出现的选项。MSS选项占4byte。MSS是每一个TCP报文段中数据字段的最大长度,注意:只是数据部分的字段,不包括TCP的头部。TCP在三次握手中,每一方都会通告其期望收到的MSS(MSS只出现在SYN数据包中)如果一方不接受另一方的MSS值则定位默认值536byte。
MSS值太小或太大都是不合适。太小,例如MSS值只有1byte,那么为了传输这1byte数据,至少要消耗20字节IP头部+20字节TCP头部=40byte,这还不包括其二层头部所需要的开销,显然这种数据传输效率是很低的。MSS过大,导致数据包可以封装很大,那么在IP传输中分片的可能性就会增大,接受方在处理分片包所消耗的资源和处理时间都会增大,如果分片在传输中还发生了重传,那么其网络开销也会增大。因此合理的MSS是至关重要的。MSS的合理值应为保证数据包不分片的最大值。对于以太网MSS可以达到1460byte.
不MSS相似的在IP层也有一个类似的概念---MTU(Maximum Transfer Unit)下图可以清晰翻译MSS不MTU 的关系:
还有http://blog.youkuaiyun.com/xiaofei0859/article/details/51052848里面说:
TCP在三次握手建立连接过程中,会在SYN报文中使用MSS(Maximum Segment Size)选项功能,协商交互双方能够接收的最大段长MSS值。
MSS是传输层TCP协议范畴内的概念,顾名思义,其标识TCP能够承载的最大的应用数据段长度,因此,MSS=MTU-20字节TCP报头-20字节IP报头,那么在以太网环境下,MSS值一般就是1500-20-20=1460字节。
客户端与服务器端分别根据自己发包接口的MTU值计算出相应MSS值,并通过SYN报文告知对方。
所以本博主可以看出wireshark上标记SYN的帧,就有MSS相关内容。
好弄个实际例子试一试,
在服务器程序上把#define TCP_MSS (1400 - 40)
于是握手帧如下:
看到了吧,客户端请求用1460字节传输数据,但是服务器答应只能用1360字节传输。
然后传个文件试一试,如下:
看到了吧,果真是用1360字节交互。
从客户端向服务器传送文件看一下:
看到了吧,也是用1360传输的。同时看到我的服务器接收能力太差,导致显示TCP WINDOWS FULL。(我分析是因为我的服务器接收能力差导致的,因为写入文件系统太耗费时间了)。这个以后再议。
所以到这里TCP_MSS就可以说基本搞清楚了。可见TCP_MSS是对某一方来说,接收和发送都用的上的,是服务器和客户端双方协商的结果。
------------
下面是关于TCP_SND_BUF,下面这个链接有点介绍,看起来比较详细:
https://www.cnblogs.com/byeyear/p/3525433.html,如下:
// TCP发送缓冲区大小(已发出但还未收到ACK的最大允许数据量) // 只是设定大小,并不实际分配内存 // 如果你的程序需要发送大量零拷贝数据 // 可以将这个值设大一些 // (因为零拷贝数据“总是在那里”,不占LwIP动态内存) // 最小不应小于单次调用tcp_write时可能发送的最大数据量 // 调用tcp_write时的参数len若大于tpcb->snd_buf将导致返回ERR_MEM // (在LwIP代码中,每成功执行一次tcp_write相应减小tpcb->snd_buf, // 每收到一次ACK相应增加tpcb->snd_buf) // 考虑到一般系统“隔一个确认”的原则, // 一般设置为TCP_MSS的2倍或更大的值。 #ifndef TCP_SND_BUF #define TCP_SND_BUF (2 * TCP_MSS) #endif找遍所有的C函数,在函数tcp_new里发现了一处用的哦啊TCp_SND_BUF的地方,如下:
pcb->snd_buf = TCP_SND_BUF;
那么看看那些地方用到了pcb->snd_buf,如下:
事实上我还漏了一个重要的地方,在tcp.H里面,如下
#define tcp_sndbuf(pcb) ((pcb)->snd_buf)
哈哈,这个地方也不太重要了。
那么这个地方pcb->snd_buf有时候++有时候--的,到底是咋个回事呢?
我先百度一下,等会再回来,等会开午饭了。