TCP/IP协议与lwip库——源代码分析(一)

编程入门 行业动态 更新时间:2024-10-18 16:46:56

TCP/IP协议与lwip库——<a href=https://www.elefans.com/category/jswz/34/1769335.html style=源代码分析(一)"/>

TCP/IP协议与lwip库——源代码分析(一)

目录

  • 端口
  • `GIC`中断控制器
  • 定时器
  • 初始化函数`lwip_init`
    • `stats_init`函数
      • 调试信息
      • 动态内存管理
      • 总结
  • `NO_SYS`
  • `mem_init()`
    • `mem`结构体
    • lwip中内存宏配置
    • 内存对齐
    • 实际仿真分析
  • `memp_init()`
  • `pbuf_init()`

从lwip_echo例程出发,调通自己的网口TCP/UDP程序,并彻底搞懂TCP/IP协议
加油ヾ(◍°∇°◍)ノ゙

#ifndef TCP_LOCAL_PORT_RANGE_START
/* From :"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的端口号来表示不同的应用程序,把源端口号和目的端口号分别存入报文首部。端口可分为以下三种:

  1. Well-known ports:公认端口号,范围0~1023,被Unix服务器上的许多核心服务所使用,并且具有服务器上要实现的大多数必需特权。
  2. Registered ports:注册端口,范围1024-49151,不可以动态调整,没有明确定义服务哪些特定的对象。不同的程序可以根据自己的需要自己定义。这里说明一下IANA,这是一个internet号码分配机构,会保留知名端口和所有注册端口上运行的所有服务。示例: Microsoft远程桌面协议RDP(3389),网络文件协议NFS(2049)
  3. 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实际上由以下三个部分组成:

  1. distributor:SPI中断管理,将中断发送给redistributor
    检测各个中断源的状态,将各个中断源产生的中断分发到指定的一个或多个CPU接口上(优先级最高的请求送往CPU接口),包括中断全局控制GIC_DIST_CTRL,和针对各个中断源进行控制GIC_DIST_ENABLE_CLEAR
  2. redistributor:PPI、SGI、LPI中断管理,将中断发送给cpu interface。主要功能是启用/禁用SGI和PPI,设置SGI和PPI的优先级,将PPI设置为电平触发或边沿触发
  3. 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定义为一个无类型指针。两个知识点:

  1. 无类型指针:可以指向任意类型的数据,不允许进行算术运算(ANSI C中, GNU中是允许的,因为GNU默认void*等同于char*
  2. 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_SYSsys_init();
#endif /* !NO_SYS */mem_init();memp_init();pbuf_init();netif_init();
#if LWIP_SOCKETlwip_socket_init();
#endif /* LWIP_SOCKET */ip_init();
#if LWIP_ARPetharp_init();
#endif /* LWIP_ARP */
#if LWIP_RAWraw_init();
#endif /* LWIP_RAW */
#if LWIP_UDPudp_init();
#endif /* LWIP_UDP */
#if LWIP_TCPtcp_init();
#endif /* LWIP_TCP */
#if LWIP_SNMPsnmp_init();
#endif /* LWIP_SNMP */
#if LWIP_AUTOIPautoip_init();
#endif /* LWIP_AUTOIP */
#if LWIP_IGMPigmp_init();
#endif /* LWIP_IGMP */
#if LWIP_DNSdns_init();
#endif /* LWIP_DNS */#if LWIP_TIMERSsys_timeouts_init();
#endif /* LWIP_TIMERS */#if !NO_SYS/* in the Xilinx lwIP 1.2.0 port, lwip_init() was added as a convenience utility functionto initialize all the lwIP layers. lwIP 1.3.0 introduced lwip_init() in the base lwIPitself. However a user cannot use lwip_init() regardless of whether it is raw or socketmodes. The following call to lwip_sock_init() is made to make sure that lwIP is properlyinitialized 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_STATSconst 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_STATSlwip_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_STATSstruct stats_proto link;
#endif
#if ETHARP_STATSstruct stats_proto etharp;
#endif
#if IPFRAG_STATSstruct stats_proto ip_frag;
#endif
#if IP_STATSstruct stats_proto ip;
#endif
#if ICMP_STATSstruct stats_proto icmp;
#endif
#if IGMP_STATSstruct stats_igmp igmp;
#endif
#if UDP_STATSstruct stats_proto udp;
#endif
#if TCP_STATSstruct stats_proto tcp;
#endif
#if MEM_STATSstruct stats_mem mem;
#endif
#if MEMP_STATSstruct stats_mem memp[MEMP_MAX];
#endif
#if SYS_STATSstruct stats_sys sys;
#endif
};

看前缀:

  1. 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;
};

统计了发包数量、收包数量、丢包数量、校验和错误、不合法的长度错误、超出内存容量错误、路线错误、协议错误等等问题

  1. ETHARP_STATS:对应的是ARP(Address Resolution Protocol)地址解析协议。用于实现IP地址到网络接口硬件地址的映射。是将32bit的IP地址解析为48bit的MAC地址。
    总的来说,lwip将链路层Ethernet的协议分组格式分为etheretherarp分开处理
  2. ICMP_STATS:ICMP,Internet控制报文协议
    首先,ICMP是用于传递差错报文以及其他需要注意的信息,IP协议是不可靠协议,不能保证IP数据包可以成功到达目的主机,无法进行差错控制。
    ICMP被封装在IP数据包内部

    报文格式什么的先略过,具体可以参考这篇文章ICMP与IGMP,先看ICMP协议有哪些应用:
    最常见的应用:PING
    ping的工作原理:通过调用echo来发送请求,通过是否收到echo-reply来查询网络层的连通性。ICMP的ping请求报在每经过一个路由器的时候,路由器就会把自己的IP地址放到该数据包中,而目的主机则会把这个IP列表复制到ICMP数据包中发回给主机。所以ping可以给出传送的时间和TTL的数据
  3. 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_STATSlwip_stats.mem.name = "MEM";
#endif /* MEM_STATS */

MEM_STATS是一个宏定义:

#define MEM_STATS                       ((MEM_LIBC_MALLOC == 0) && (MEM_USE_POOLS == 0))

这里就涉及到lwip所定义的三种内存池:

  1. MEMPOOL:标准内存池
  2. MALLOC_MEMPOOL:提供给mem.c中的mem_malloc使用的内存池
  3. 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中内存宏配置

  1. MEM_LIBC_MALLOC:是否使用C运行时库自带的内存分配策略,默认为0,表示不使用
  2. MEMP_MEM_MALLOC:是否使用内存堆分配策略实现内存池分配(即从内存池中获取内存时,实际上是从内存堆中分配),默认为0,表示不从内存堆中分配
  3. 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 1memp = (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_CHECKmemp_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_CHECKconst 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连接的各个知识点,这个地方知识点真的太多了,以前没看源码,只是会用,现在看来自己简直就是渣渣呀
ヾ(◍°∇°◍)ノ゙

更多推荐

TCP/IP协议与lwip库——源代码分析(一)

本文发布于:2024-03-12 11:46:44,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1731437.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源代码   协议   TCP   IP   lwip

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!