前言
对于嵌入式物联网技术来说,TCP/IP 协议几乎是不能绕过的,常见socket、tcp、udp、mqtt、coap、modbus-tcp、mdns、广播、组播等等,均是基于TCP/IP协议实现,无处不在。而目前在嵌入式领域,使用最多的TCP/IP协议栈就是LwIp,所以本系列尝试着从LwIP的详细分析,来入门学习TCP/IP协议。
在LwIP中,使用了很多高级的C语言用法,如果不了解这些高级用法,我们很难清楚的了解其实现原理,我们先从宏定义的高级用法来入手,因为在LwIP中的枚举定义、内存分配、协议配置等等,均通过宏定义协助实现。
函数宏
函数宏,也叫类函数宏,其宏定义有相关的模板,一般是类似函数的形式,通过( ) 括号内增加参数来表示,需要特别说明的是,函数宏中的参数就是简单的替换。我们先看几个实例:
示例1
#include <stdio.h>#define max(a, b) ((a > b) ? (a) : (b))#define func(a, b) a + bint main(void)
{int a, b;a = 2;b = 3;printf("max:%d\n", max(3, 2));printf("func * 3 = %d\n", func(2, 3) * 3);return 0;
}
运行结果:
max:3
func * 3 = 11
注意: 从上述计算结果可知,宏定义就是替换,所以 func 3 结果是11,即 2 + 33 = 11,而不是我们(2 + 3)* 3, 如果想要这样的效果,则需要添加(), 即,我们在使用函数宏时,不要吝啬( ) 括号的使用。
‘##’ 拼接宏
## 宏是将两边的参数拼接成1个参数,## 两边可以有空格,
示例:
#define t(x,y,z) x ## y ## z
int j[] = { t(1,2,3), t(,4,5), t(6,,7), t(8,9,),
t(10,,), t(,11,), t(,,12), t(,,) };
经过预编译后,结果如下:
int j[] = {123, 45, 67, 89,10, 11, 12, };
在lwip中,为了实现可配置,比如我们只使用UDP、TCP,通过宏配置就能够实现,比如下面的代码:
typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
#include "lwip/priv/memp_std.h"MEMP_MAX
} memp_t;
这里面有3种宏用法,分别是:
(1)函数宏,LWIP_MEMPOOL(name,num,size,desc), 编译器在预编译时,将LWIP_MEMPOOL(name,num,size,desc) 替换为MEMP_##name,其中name为参数。
(2)## 拼接宏,编译器在预编译时,将MEMP和 参数name 拼接成1个新的宏。
(3) #include xxxx, include 引用,编译器在编译时,会将lwip/priv/memp_std.h 的内容拷贝到当前位置.
所以, 经过预编译,会将memp_std.h 拷贝到当前枚举定义位置,然后将其中的LWIP_MEMPOOL 函数替换为 MMEP_name,最终如下所示:
typedef enum {
/* #line 1 "..\\..\\Middlewares\\lwip\\src\\include\\lwip/priv/memp_std.h" */
MEMP_UDP_PCB,
MEMP_TCP_PCB, MEMP_TCP_PCB_LISTEN, MEMP_TCP_SEG,
MEMP_REASSDATA,
MEMP_FRAG_PBUF,
/* #line 92 "..\\..\\Middlewares\\lwip\\src\\include\\lwip/priv/memp_std.h" */
MEMP_SYS_TIMEOUT,
/* #line 111 "..\\..\\Middlewares\\lwip\\src\\include\\lwip/priv/memp_std.h" */
MEMP_PBUF, MEMP_PBUF_POOL,
/* #line 55 "..\\..\\Middlewares\\lwip\\src\\include\\lwip/memp.h" */
MEMP_MAX
} memp_t;
这样就实现了,通过宏定义,实现了自动变化 宏定义枚举定义。
通过Keil输出预编译文件,辅助读lwip源码
在lwip中,有很多地方都会上面提到的宏方法来定义变量、函数。我们在开始看代码的时候,会觉得懵逼,如果上来就陷到解析代码中,效率会非常低,所以我们可以通过借助IDE输出预编译文件来查看宏替换后的文件结果。以Keil为例,通过设置:
Options for Target / List / C Preprocessor Listing 即可将预编译结果输出到指定目录,如下图所示:
输出文件格式为.i*, 我们可以直接使用文本编辑器查看。