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


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

  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_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
};

看前缀:

  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_STATS
  lwip_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 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连接的各个知识点,这个地方知识点真的太多了,以前没看源码,只是会用,现在看来自己简直就是渣渣呀
ヾ(◍°∇°◍)ノ゙

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lwip(Lightweight IP)协议栈是一个嵌入式实现的轻量级网络通信协议栈。下面将对lwip协议栈源码进行详解lwip协议栈包含了常用的网络协议,如IP、TCP、UDP、ICMP等,以及相应的应用层接口。它的设计目标是针对资源有限的嵌入式设备进行优化,具有较小的内存占用和较低的计算负载。lwip协议栈支持多种操作系统和硬件平台,并且易于移植和定制。 lwip协议栈源码主要包括核心组件和协议实现。核心组件包括网络接口、IP协议栈、TCP/IP协议栈和UDP协议栈。协议实现包括IPV4/6协议、ARP协议、DHCP协议、ICMP协议、TCP传输控制协议和UDP用户数据报协议等。 lwip协议栈源码结构清晰,易于阅读和理解。它采用了模块化的设计思想,各个模块之间通过函数调用进行交互。源码中使用了大量的宏定义和数据结构,以提高代码的可读性和可维护性。 在lwip协议栈源码中,可以看到它的实现流程。首先,lwip会初始化网络接口和协议栈相关的数据结构。然后,它会根据网络接口收到的数据包进行处理,包括解析和分发。接着,根据协议类型,lwip会调用相应的协议实现进行数据包的处理和转发。最后,处理完数据包后,lwip会根据协议规则生成相应的响应包,并发送到网络接口。 总之,lwip协议栈源码是一个高效、可靠且易于移植的嵌入式网络通信协议栈。通过对其源码的详细分析和理解,我们可以深入了解网络通信的实现原理,并在嵌入式设备中实现各种网络应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值