目录
从lwip_echo例程出发,调通自己的网口TCP/UDP程序,并彻底搞懂TCP/IP协议
加油ヾ(◍°∇°◍)ノ゙
#ifndef TCP_LOCAL_PORT_RANGE_START
/* From http://www.iana.org/assignments/port-numbers:
"The Dynamic and/or Private Ports are those from 49152 through 65535" */
#define TCP_LOCAL_PORT_RANGE_START 0xc000
#define TCP_LOCAL_PORT_RANGE_END 0xffff
#define TCP_ENSURE_LOCAL_PORT_RANGE(port) (((port) & ~TCP_LOCAL_PORT_RANGE_START) + TCP_LOCAL_PORT_RANGE_START)
#endif
端口
在TCP/IP和UDP网络中,端口是逻辑连接的端点。端口号标识端口的类型,比如80用于HTTP通信。TCP和UDP都用一个16bit的端口号来表示不同的应用程序,把源端口号和目的端口号分别存入报文首部。端口可分为以下三种:
Well-known ports
:公认端口号,范围0~1023,被Unix服务器上的许多核心服务所使用,并且具有服务器上要实现的大多数必需特权。Registered ports
:注册端口,范围1024-49151,不可以动态调整,没有明确定义服务哪些特定的对象。不同的程序可以根据自己的需要自己定义。这里说明一下IANA
,这是一个internet号码分配机构,会保留知名端口和所有注册端口上运行的所有服务。示例: Microsoft远程桌面协议RDP(3389),网络文件协议NFS(2049)Dynamic and/or private ports
:动态、私有或临时端口号,范围49152–65535,不可以被注册,被用于私人的或定制化的服务
grep
命令可用于查看端口号
所以上面这段代码的意思就是如果没有定义TCP_LOCAL_PORT_RANGE_START
这个变量,就将其定义为动态端口号范围的起始数49152,且定义TCP_LOCAL_PORT_RANGE_END
为结束数65535,TCP_ENSURE_LOCAL_PORT_RANGE
类似于一个宏定义的函数,输入参数为端口号,保证输入的端口号在可用范围内。
GIC
中断控制器
通用中断控制器GIC是用于管理从PS和PL发送到CPU的中断的几种资源。当CPU接口接受下一个中断时,控制器以编程的方式启用,禁用,屏蔽和区分中断源的优先级,并将其发送给选定的一个或多个CPU。
GIC是几种所有中断源,然后将优先级最高的中断源分配给各个CPU,GIC确保针对多个CPU的中断一次只能由一个CPU处理。
看一下结构体定义:
/**
* The XScuGic driver instance data. The user is required to allocate a
* variable of this type for every intc device in the system. A pointer
* to a variable of this type is then passed to the driver API functions.
*/
typedef struct
{
XScuGic_Config *Config; /**< Configuration table entry */
u32 IsReady; /**< Device is initialized and ready */
u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;
而XScuGic_Config
结构体的定义为:
typedef struct
{
u16 DeviceId; /**< Unique ID of device */
u32 CpuBaseAddress; /**< CPU Interface Register base address */
u32 DistBaseAddress; /**< Distributor Register base address */
XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
Vector table of interrupt handlers */
} XScuGic_Config;
下面是GIC的结构图
可以看到GIC实际上由以下三个部分组成:
distributor
:SPI中断管理,将中断发送给redistributor
检测各个中断源的状态,将各个中断源产生的中断分发到指定的一个或多个CPU接口上(优先级最高的请求送往CPU接口),包括中断全局控制GIC_DIST_CTRL
,和针对各个中断源进行控制GIC_DIST_ENABLE_CLEAR
redistributor
:PPI、SGI、LPI中断管理,将中断发送给cpu interface
。主要功能是启用/禁用SGI和PPI,设置SGI和PPI的优先级,将PPI设置为电平触发或边沿触发cpu interface
:传输中断给core
此外还会有一张中断向量表:包含中断号与对应的回调函数
/* The following data type defines each entry in an interrupt vector table.
* The callback reference is the base address of the interrupting device
* for the low level driver and an instance pointer for the high level driver.
*/
typedef struct
{
Xil_InterruptHandler Handler;
void *CallBackRef;
} XScuGic_VectorTableEntry;
typedef void (*Xil_InterruptHandler)(void *data);
也就是将Xil_InterruptHandler
定义为一个无类型指针。两个知识点:
- 无类型指针:可以指向任意类型的数据,不允许进行算术运算(ANSI C中, GNU中是允许的,因为GNU默认
void*
等同于char*
) typedef
与#define
的区别:typedef
仅限于为类型定义符号名称,是由编译器执行解释的;#define
可以为类型和数值定义,由预编译器进行处理的
定时器
static XScuTimer Timer;//timer
/**
* This typedef contains configuration information for the device.
*/
typedef struct {
u16 DeviceId; /**< Unique ID of device */
u32 BaseAddr; /**< Base address of the device */
} XScuTimer_Config;
/**
* The XScuTimer driver instance data. The user is required to allocate a
* variable of this type for every timer device in the system.
* A pointer to a variable of this type is then passed to the driver API
* functions.
*/
typedef struct {
XScuTimer_Config Config; /**< Hardware Configuration */
u32 IsReady; /**< Device is initialized and ready */
u32 IsStarted; /**< Device timer is running */
} XScuTimer;
私有定时器属于PS部分,可以计数、计时、有效控制模块的时序
初始化函数lwip_init
我们先来看看这个初始化函数干了些啥:
void lwip_init(void)
{
/* Modules initialization */
stats_init();
#if !NO_SYS
sys_init();
#endif /* !NO_SYS */
mem_init();
memp_init();
pbuf_init();
netif_init();
#if LWIP_SOCKET
lwip_socket_init();
#endif /* LWIP_SOCKET */
ip_init();
#if LWIP_ARP
etharp_init();
#endif /* LWIP_ARP */
#if LWIP_RAW
raw_init();
#endif /* LWIP_RAW */
#if LWIP_UDP
udp_init();
#endif /* LWIP_UDP */
#if LWIP_TCP
tcp_init();
#endif /* LWIP_TCP */
#if LWIP_SNMP
snmp_init();
#endif /* LWIP_SNMP */
#if LWIP_AUTOIP
autoip_init();
#endif /* LWIP_AUTOIP */
#if LWIP_IGMP
igmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNS
dns_init();
#endif /* LWIP_DNS */
#if LWIP_TIMERS
sys_timeouts_init();
#endif /* LWIP_TIMERS */
#if !NO_SYS
/* in the Xilinx lwIP 1.2.0 port, lwip_init() was added as a convenience utility function
to initialize all the lwIP layers. lwIP 1.3.0 introduced lwip_init() in the base lwIP
itself. However a user cannot use lwip_init() regardless of whether it is raw or socket
modes. The following call to lwip_sock_init() is made to make sure that lwIP is properly
initialized in both raw & socket modes with just a call to lwip_init().
*/
lwip_sock_init();
#endif
}
stats_init
函数
void stats_init(void)
{
#ifdef LWIP_DEBUG
#if MEMP_STATS
const char * memp_names[] = {
#define LWIP_MEMPOOL(name,num,size,desc) desc,
#include "lwip/memp_std.h"
};
int i;
for (i = 0; i < MEMP_MAX; i++) {
lwip_stats.memp[i].name = memp_names[i];
}
#endif /* MEMP_STATS */
#if MEM_STATS
lwip_stats.mem.name = "MEM";
#endif /* MEM_STATS */
#endif /* LWIP_DEBUG */
}
调试信息
LWIP_DEBUG
:Enable debug message printing, but only if debug message type is enabled AND is of correct type AND is at least LWIP_DBG_LEVEL.允许打印调试信息,但只有使能调试正确调试信息类型且至少为LWIP_DBG_LEVEL
是才可以。
LWIP_DBG_LEVEL
分为四种:_ALL
(0x00),_SERIOUS
(0x02),_SEVERE
(0x03),_WARNNING
(0x01)
动态内存管理
MEMP_STATS
MEMP_STATS==1: Enable memp.c pool stats.
lwip
的动态内存管理分为两种:内存堆和内存池,这个用到的就是pool——内存池,源码文件主要是memp.c/h
在该模式下,系统只能为用户分配几个固定大小的内存块,优点是比较快,不会产生内存碎片,缺点是产生内存浪费,适合对那些固定数据结构进行空间分配,比如TCP控制块、IP控制块等
系统在初始化时,会事先在内存中初始化相应的空间内存空间,将所有可用区域以固定的大小为单位进行划分,然后用一个简单的链表将所有空闲块连接起来。
比如,把协议栈中所有的内存池pool放在一起,并把他们放在一片连续的内存区域,就是一个大的缓存池,内部是:A类型的pool池a个,接着放B类型的内存池b个…
从代码上来看,如果定义使用内存池管理,就会执行:
const char * memp_names[] = {
#define LWIP_MEMPOOL(name,num,size,desc) desc,
#include "lwip/memp_std.h"
};
乍一看,这是什么鬼?
首先我们看一眼LWIP_MEMPOOL
的定义:
/* Create the list of all memory pools managed by memp. MEMP_MAX represents a NULL pool at the end */
typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
#include "lwip/memp_std.h"
MEMP_MAX
} memp_t;
##
是宏定义中用于连接字符串的一种用法,而将头文件写在下面是表示把这个头文件内容复制到下面
那么定义的数组就会是这个样子的:
const char * memp_names[] = {
"RAW_PCB",
"UDP_PCB",
"TCP_PCB",
.....
"PBUF_POOL"
};
这样来看就简单了,其实就是定义了一个数组memp_names
,类型为字符型指针,数组中的每个元素都是内存池类型的名字。
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
第一个参数表示pool类型,第二个参数表示该类型的数目,第三个参数代表每个pool的大小,第四个参数为描述符
int i;
for (i = 0; i < MEMP_MAX; i++) {
lwip_stats.memp[i].name = memp_names[i];
}
lwip_stats
是全局变量:
struct stats_ lwip_stats;
看一下stats_
这个结构体定义,在头文件stats.h
中:
struct stats_ {
#if LINK_STATS
struct stats_proto link;
#endif
#if ETHARP_STATS
struct stats_proto etharp;
#endif
#if IPFRAG_STATS
struct stats_proto ip_frag;
#endif
#if IP_STATS
struct stats_proto ip;
#endif
#if ICMP_STATS
struct stats_proto icmp;
#endif
#if IGMP_STATS
struct stats_igmp igmp;
#endif
#if UDP_STATS
struct stats_proto udp;
#endif
#if TCP_STATS
struct stats_proto tcp;
#endif
#if MEM_STATS
struct stats_mem mem;
#endif
#if MEMP_STATS
struct stats_mem memp[MEMP_MAX];
#endif
#if SYS_STATS
struct stats_sys sys;
#endif
};
看前缀:
LINK_STATS
表示lwip 统计量,分两种,一种是lwip自己的,一种是snmp的
结构体stats_proto
定义
struct stats_proto {
STAT_COUNTER xmit; /* Transmitted packets. */
STAT_COUNTER recv; /* Received packets. */
STAT_COUNTER fw; /* Forwarded packets. */
STAT_COUNTER drop; /* Dropped packets. */
STAT_COUNTER chkerr; /* Checksum error. */
STAT_COUNTER lenerr; /* Invalid length error. */
STAT_COUNTER memerr; /* Out of memory error. */
STAT_COUNTER rterr; /* Routing error. */
STAT_COUNTER proterr; /* Protocol error. */
STAT_COUNTER opterr; /* Error in options. */
STAT_COUNTER err; /* Misc error. */
STAT_COUNTER cachehit;
};
统计了发包数量、收包数量、丢包数量、校验和错误、不合法的长度错误、超出内存容量错误、路线错误、协议错误等等问题
ETHARP_STATS
:对应的是ARP(Address Resolution Protocol)地址解析协议。用于实现IP地址到网络接口硬件地址的映射。是将32bit的IP地址解析为48bit的MAC地址。
总的来说,lwip将链路层Ethernet的协议分组格式分为ether
和etherarp
分开处理ICMP_STATS
:ICMP,Internet控制报文协议
首先,ICMP是用于传递差错报文以及其他需要注意的信息,IP协议是不可靠协议,不能保证IP数据包可以成功到达目的主机,无法进行差错控制。
ICMP被封装在IP数据包内部
报文格式什么的先略过,具体可以参考这篇文章ICMP与IGMP,先看ICMP协议有哪些应用:
最常见的应用:PING
ping
的工作原理:通过调用echo
来发送请求,通过是否收到echo-reply
来查询网络层的连通性。ICMP的ping请求报在每经过一个路由器的时候,路由器就会把自己的IP地址放到该数据包中,而目的主机则会把这个IP列表复制到ICMP数据包中发回给主机。所以ping可以给出传送的时间和TTL的数据IGMP_STATS
:IGMP,Internet组管理协议
用于支持主机和路由器进行多播。
这里说明一下多播是什么:
单播Unicast
:对特定的主机进行数据传送。数据链路层给出的数据头里面是非常具体的目的地址,对于以太网来说就是网卡的MAC地址,目的主机的网络接口可以过滤掉和自己MAC地址不一致的数据
广播Broadcast
:主机针对某一个网络上的所有主机发送数据包。在TCP/IP协议的网络中,主机标识段host ID为全1的IP地址为广播地址
多播Multicast
:给一组特定的主机(多播组)发送数据。多播组的地址是D类IP
IGMP
就是用来在IP主机和,与其直接相邻的组播路由器之间建立、维护组播组成员的关系。
IGMP
统计量的结构体与其他协议是不同的
struct stats_igmp {
STAT_COUNTER xmit; /* Transmitted packets. */
STAT_COUNTER recv; /* Received packets. */
STAT_COUNTER drop; /* Dropped packets. */
STAT_COUNTER chkerr; /* Checksum error. */
STAT_COUNTER lenerr; /* Invalid length error. */
STAT_COUNTER memerr; /* Out of memory error. */
STAT_COUNTER proterr; /* Protocol error. */
STAT_COUNTER rx_v1; /* Received v1 frames. */
STAT_COUNTER rx_group; /* Received group-specific queries. */
STAT_COUNTER rx_general; /* Received general queries. */
STAT_COUNTER rx_report; /* Received reports. */
STAT_COUNTER tx_join; /* Sent joins. */
STAT_COUNTER tx_leave; /* Sent leaves. */
STAT_COUNTER tx_report; /* Sent reports. */
};
书上对于统计量是这样写的:
ICMP
IGMP
:
回来回来,也就是说将先前定义的memp_names
放在统计量结构体对应的memp[MEMP_MAX]
中
#if MEM_STATS
lwip_stats.mem.name = "MEM";
#endif /* MEM_STATS */
MEM_STATS
是一个宏定义:
#define MEM_STATS ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))
这里就涉及到lwip所定义的三种内存池:
MEMPOOL
:标准内存池MALLOC_MEMPOOL
:提供给mem.c
中的mem_malloc
使用的内存池PBUF_MEMPOOL
:LwIP的pbuf结构使用的内存池
总结
stats_init
这个函数是定义了需要用到的统计量数组,并将其赋值给统计量的结构体lwip_stats
NO_SYS
NO_SYS==1
:使用lwip,无需操作系统(即没有线程、信号量、互斥量或mbox),意味着无法使用线程化的api,只能使用回调类的原始API
#define NO_SYS 1
mem_init()
内存堆初始化函数,主要设置内存堆的起始地址,以及初始化空闲列表,lwip初始化时调用,内部接口。
/**
* Zero the heap and initialize start, end and lowest-free
*/
void
mem_init(void)
{
struct mem *mem;
LWIP_ASSERT("Sanity check alignment",
(SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);
/* align the heap */
/* 内存堆空间对齐,关联到ram */
ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);
/* initialize the start of the heap */
/* 在内存堆起始位置放置一个mem 类型的结构体,因为初始化后的
内存堆就是一个大的空闲内存块,每个空闲内存块的前面都需要放置一个mem 结构体 */
mem = (struct mem *)(void *)ram;
/* 下一个内存块的偏移量为MEM_SIZE_ALIGNED */
mem->next = MEM_SIZE_ALIGNED;
mem->prev = 0; // 上一个内存块为空
mem->used = 0; // 未使用
/* initialize the end of the heap */
/* 指针移动到内存堆末尾的位置,并且在那里放置一个mem 类型的
结构体,并初始化表示内存堆结束的内存块 */
ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED];
ram_end->used = 1;
/* 同时mem 结构体的next 与prev 字段都指向自身,此
处仅表示已经到了内存堆的结束的地方,并无内存可以分配 */
ram_end->next = MEM_SIZE_ALIGNED;
ram_end->prev = MEM_SIZE_ALIGNED;
/* initialize the lowest-free pointer to the start of the heap */
/* 空闲内存块链表指针指向内存堆的起始地址,因为当前只有一个内存块。 */
lfree = (struct mem *)(void *)ram;
MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);
/* 创建一个内存堆分配时候使用的互斥量,如果是无操作系统的情况,该语句等效于空。 */
if(sys_mutex_new(&mem_mutex) != ERR_OK) {
LWIP_ASSERT("failed to create mem_mutex", 0);
}
}
我们一步一步来看
mem
结构体
/**
* The heap is made up as a list of structs of this type.
* This does not have to be aligned since for getting its size,
* we only use the macro SIZEOF_STRUCT_MEM, which automatically alignes.
*/
struct mem {
/** index (-> ram[next]) of the next struct */
mem_size_t next; // 偏移量,而非指针,下一个结构体索引
/** index (-> ram[prev]) of the previous struct */
mem_size_t prev; // 偏移量,而非指针,前一个结构体索引
/** 1: this area is used; 0: this area is unused */
u8_t used; // 标记内存是否被使用
};
动态内存池管理可以分为两种:1. C运行使库自带的内存分配策略;2. lwip自己实现的内存堆分配策略。两者的选择需要通过宏定义数值MEM_LIBC_MALLOC
来选择。
内存堆的分配策略:在一个事先定义好大小的内存块中进行管理,采用最快合适(First Fit)方式,只要找到一个比所请求的内存大的空闲块,就从中切割出合适的块,并把剩余的部分返回到动态内存堆中。内存释放时,重新将申请到的内存返回堆中
优点是内存浪费小,比较简单,适合小内存的管理
缺点是如果频繁的动态分配和释放,可能造成严重的内存碎片,如果碎片情况严重,可能导致内存分配不成功。
默认内存的单位是32bit无符号数
typedef u32_t mem_size_t;
lwip中内存宏配置
MEM_LIBC_MALLOC
:是否使用C运行时库自带的内存分配策略,默认为0,表示不使用MEMP_MEM_MALLOC
:是否使用内存堆分配策略实现内存池分配(即从内存池中获取内存时,实际上是从内存堆中分配),默认为0,表示不从内存堆中分配MEM_USE_POOLS
:是否使用内存池分配策略实现内存堆分配(即从内存堆中获取内存时,实际上是从内存池中分配),默认为0,表示不使用。和第2个只能二选一
内存对齐
/** Calculate memory size for an aligned buffer - returns the next highest
* multiple of MEM_ALIGNMENT (e.g. LWIP_MEM_ALIGN_SIZE(3) and
* LWIP_MEM_ALIGN_SIZE(4) will both yield 4 for MEM_ALIGNMENT == 4).
*/
#ifndef LWIP_MEM_ALIGN_SIZE
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1) & ~(MEM_ALIGNMENT-1))
#endif
作用是将指定的大小处理成对齐后大小,对齐的大小由用户自定义的宏值MEM_ALIGNMENT
来决定
~(MEM_ALIGNMENT-1)
表示将二进制数的最后几位置为0
(size) + MEM_ALIGNMENT - 1
:处理时可以向上取整
/** All allocated blocks will be MIN_SIZE bytes big, at least!
* MIN_SIZE can be overridden to suit your needs. Smaller values save space,
* larger values could prevent too small blocks to fragment the RAM too much. */
#ifndef MIN_SIZE
#define MIN_SIZE 12
#endif /* MIN_SIZE */
/* some alignment macros: we define them here for better source code layout */
#define MIN_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MIN_SIZE)
#define SIZEOF_STRUCT_MEM LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
#define MEM_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MEM_SIZE)
所有分配的块至少是MIN_SIZE=12
个字节,较小的值可以节省空间,较大的值可以防止块太小而使RAM
过多碎片化
#define MEM_SIZE 131072
这个应该是可用的内存最大值??
实际上第二句就是判断是否做了内存对齐,可以假设对齐的字节是4,如果对齐了,那么计算结果就应该是0
/** 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 */
如果要将堆重定位到外部存储器(这个地方没明白),只需将LWIP_RAM_HEAP_POINTER定义为该位置的空指针,如果是这样,需要确保该位置的内存足够大。
内存计算公式就是介个:
MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT
实际仿真分析
我们来实际看一下这个初始化函数的运行过程,以及其中相关的变量赋值情况
实际中不会运行LWIP_DEBUG
这个宏定义下的语句,直接跳到mem_init()
函数
定义的内存堆的地址:
判定语句的具体赋值:mem
结构体的size应该是5?
可以看到这里有一个do...while(0)
的用法,用Google的Robert Love的说法,就是
do...while(0)
是C中唯一的构造程序,让定义的宏总是以相同的方式工作,这样不论如何使用宏,宏后面的分号也是同样的效果
可以参考这个链接里面举了一个反例,说明宏定义中大括号、分号等对调用方式的影响。while(0)
保证该逻辑只执行一次。
但是我自己按照sizeof(struct(mem))=5
就是两个uint
,一个uchar
的情况下计算,就是(68 & ~63) & 63
实际上是不等于0的?
ram_heap[]
就是内核的内存堆空间,LWIP_RAM_HEAP_POINTER
这个宏定义相对于重新命名ram_heap
所以这个语句等同于:ram = (u8_t *)LWIP_MEM_ALIGN(ram_heap);
按照定义,ram_heap
就是addr
,也就是内存堆的起始地址
#define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1)))
等同于((void *)(((mem_ptr_t)(ram_heap) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1)))
由于
typedef unsigned long mem_ptr_t;
就是将内存堆的地址进行一个类型转换,然后进行内存对齐
图片来源于链接
memp_init()
内存池初始化函数
void memp_init(void)
{
struct memp *memp;
u16_t i, j;
// 对于内存池中的每个块,检查统计量是否有效,也就是说应该处于未被使用,无错误状态,且可用状态
for (i = 0; i < MEMP_MAX; ++i) {
MEMP_STATS_AVAIL(used, i, 0);
MEMP_STATS_AVAIL(max, i, 0);
MEMP_STATS_AVAIL(err, i, 0);
MEMP_STATS_AVAIL(avail, i, memp_num[i]);
}
#if !MEMP_SEPARATE_POOLS // #define MEMP_SEPARATE_POOLS 1
memp = (struct memp *)LWIP_MEM_ALIGN(memp_memory);
#endif /* !MEMP_SEPARATE_POOLS */
/* for every pool: */
for (i = 0; i < MEMP_MAX; ++i) {
memp_tab[i] = NULL; // 看下文的定义,表示每个内存池中第一个空闲的部分。各个空闲的部分以链表形式结构
#if MEMP_SEPARATE_POOLS // 会进入这个语句
memp = (struct memp*)memp_bases[i]; // 看下文的定义,表示每个内存池的基地址?
#endif /* MEMP_SEPARATE_POOLS */
/* create a linked list of memp elements */
for (j = 0; j < memp_num[i]; ++j) {
memp->next = memp_tab[i];
memp_tab[i] = memp;
/* 地址偏移 */
memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]
#if MEMP_OVERFLOW_CHECK
+ MEMP_SANITY_REGION_AFTER_ALIGNED
#endif
);
}
}
#if MEMP_OVERFLOW_CHECK
memp_overflow_init();
/* check everything a first time to see if it worked */
memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK */
}
这个内存池所使用的结构体:
struct memp {
struct memp *next;
#if MEMP_OVERFLOW_CHECK
const char *file;
int line;
#endif /* MEMP_OVERFLOW_CHECK */
};
如果没有定义溢出保护检查MEMP_OVERFLOW_CHECK
,那就只是简单的定义了一个指向内存池结构的指针。结合内存池的分块特点可以理解。
在调试中MEMP_OVERFLOW_CHECK=0
,所以不会进入if中的语句执行,但是官方给出的注释是:
MEMP_OVERFLOW_CHECK: memp overflow protection reserves a configurable amount of bytes before and after each memp element in every pool and fills it with a prominent default value. MEMP_OVERFLOW_CHECK == 0 no checking
MEMP_OVERFLOW_CHECK == 1 checks each element when it is freed MEMP_OVERFLOW_CHECK >= 2 checks each element in every pool every time memp_malloc() or memp_free() is called (useful but slow!)
/** This array holds the first free element of each pool.
* Elements form a linked list. */
static struct memp *memp_tab[MEMP_MAX];
/** This array holds the base of each memory pool. */
static u8_t *const memp_bases[] = {
#define LWIP_MEMPOOL(name,num,size,desc) memp_memory_ ## name ## _base,
#include "lwip/memp_std.h"
};
这个链表看得我莫名觉得有点搞笑
pbuf_init()
/* Initializes the pbuf module. This call is empty for now, but may not be in future. */
#define pbuf_init()
hhhh,好有幽默感
先写到这里罗~,思路比较乱,就是跟着代码来,一步步搞清楚TCP连接的各个知识点,这个地方知识点真的太多了,以前没看源码,只是会用,现在看来自己简直就是渣渣呀
ヾ(◍°∇°◍)ノ゙