💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
数据包申请函数pbuf\_alloc()在系统中的许多地方都会用到,例如在网卡接收数据时,需要申请一个数据包,然后将网卡中的数据填入数据包中;在发送数据的时候,协议栈会申请一个pbuf数据包,并将即将发送的数据装入到pbuf中的数据区域,同时相关的协议首部信息也会被填入到pbuf中的layer区域内,所以pbuf数据包的申请函数几乎无处不在,存在协议栈于各层之中,当然,在不同层的协议中,layer字段的大小是不一样的,因为不一样的协议其首部大小是不同的,这个知识点会在后文讲解各协议的时候讲解,此处只需了解一下即可。协议栈中各层首部的大小都会被预留出来,LwIP采用枚举类型的变量将各个层的首部大小记录下来,在申请的时候就把layer需要空间的大小根据协议进行分配,具体见代码清单 6‑5。 ``` 1 #define PBUF_TRANSPORT_HLEN 20 2 #define PBUF_IP_HLEN 20 3 4 typedef enum 5 { 6 PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + 7 PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,(1) 8 9 PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + 10 PBUF_LINK_HLEN + PBUF_IP_HLEN, (2) 11 12 PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN, (3) 13 14 PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN, (4) 15 16 PBUF_RAW = 0 (5) 17 } pbuf_layer; ``` (1):传输层协议首部内存空间,如UDP、TCP报文协议首部。 (2):网络层协议首部内存空间,如IP协议。 (3):链路层协议首部内存空间,如以太网。 (4)(5): 原始层,不预留空间, PBUF_LINK_ENCAPSULATION_HLEN宏定义默认为0。 数据包申请函数有两个重要的参数:数据包pbuf的类型和数据包在哪一层被申请。数据包类型就是我们之前讲的那四种,数据包在哪一层申请这个参数主要是为了预留各层协议的内存大小,也就是前面所说的layer值,当数据包申请时,所处的层次不同,就会导致预留空间的的layer值不同。 pbuf分配函数pbuf_alloc()的实现具体见: ``` 1 struct pbuf * 2 pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type) 3 { 4 struct pbuf *p; 5 u16_t offset = (u16_t)layer; 6 7 switch (type) 8 { 9 case PBUF_REF: /* fall through */ 10 case PBUF_ROM: 11 p = pbuf_alloc_reference(NULL, length, type); (1) 12 break; 13 case PBUF_POOL: (2) 14 { 15 struct pbuf *q, *last; 16 u16_t rem_len; /* remaining length */ 17 p = NULL; 18 last = NULL; 19 rem_len = length; 20 do 21 { 22 u16_t qlen; 23 q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL); (3) 24 if (q == NULL) (4) 25 { 26 PBUF_POOL_IS_EMPTY(); 27 /* free chain so far allocated */ 28 if (p) 29 { 30 pbuf_free(p); (5) 31 } 32 /* bail out unsuccessfully */ 33 return NULL; 34 } 35 qlen = LWIP_MIN(rem_len,(u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - 36 LWIP_MEM_ALIGN_SIZE(offset))); (6) 37 pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *) 38 ((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)), 39 rem_len, qlen, type, 0); (7) 40 41 if (p == NULL) 42 { 43 /* allocated head of pbuf chain (into p) */ 44 p = q; 45 } 46 else 47 { 48 /* make previous pbuf point to this pbuf */ 49 last->next = q; (8) 50 } 51 last = q; 52 rem_len = (u16_t)(rem_len - qlen); (9) 53 offset = 0; 54 } 55 while (rem_len > 0); (10) 56 break; 57 } 58 case PBUF_RAM: (11) 59 { 60 u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + 61 LWIP_MEM_ALIGN_SIZE(length)); 62 mem_size_t alloc_len = (mem_size_t) 63 (LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len); (12) 64 65 66 if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) || 67 (alloc_len < LWIP_MEM_ALIGN_SIZE(length))) 68 { 69 return NULL; 70 } 71 72 /* If pbuf is to be allocated in RAM, allocate memory for it. */ 73 p = (struct pbuf *)mem_malloc(alloc_len); (13) 74 if (p == NULL) 75 { 76 return NULL; 77 } 78 pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *) 79 ((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)), 80 length, length, type, 0); (14) 81 82 break; 83 } 84 default: 85 return NULL; (15) 86 } 87 return p; 88 } ``` (1):根据具体的pbuf类型进行分配,对于PBUF_ROM与PBUF_REF类型的pbuf,只分配pbuf结构体空间大小。 (2):对于PBUF_POOL这种类型的pbuf,可能需要进行分配几个内存块才能描述一个数据包。 (3):调用memp_malloc(MEMP_PBUF_POOL)分配内存块吗,内存块类型为MEMP_PBUF_POOL。 (4):分配失败,可能内存块已经用完。 (5):如果前面分配内存块成功,但是这次分配失败,无法描述一个完整的数据包,则将之前分配的内存块都释放掉。 (6):分配成功,得到实际数据区域长度。 (7):初始化pbuf结构体的成员变量。 (8):将这些pbuf连接成pbuf链表。 (9):计算存下所有数据需要的长度。 (10):继续分配内存块,直到将所有的数据装下为止 (11):对于PBUF_RAM这种类型的pbuf,内核将从内存堆中申请pbuf。 (12):计算要申请的内存大小。 (13):调用mem_malloc()函数申请内存。 (14):初始化pbuf结构体的成员变量。 (15):类型超出预期,直接返回。 pbuf_alloc()函数的思路很清晰,根据传入的pbuf类型及协议层次layer,去申请对应的pbuf,就能预留出对应的协议首部空间,对于PBUF_ROM与PBUF_REF类型的pbuf,内核不会申请数据区域,因此,pbuf结构体中payload指针就需要用户自己去设置,我们通常在申请PBUF_ROM与PBUF_REF类型的pbuf成功后,紧接着就将payload指针指向某个数据区域。 举个例子,假设TCP协议需要申请一个pbuf数据包,那么就会调用下面代码进行申请: ``` p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM); ``` 内核就会根据这句代码进行分配一个PBUF_RAM类型的pbuf,其数据区域大小是1472字节,并且会根据协议层次进行预留协议首部空间,由于是传输层,所以内核需要预留54个字节空间,即以太网帧首部长度PBUF_LINK_HLEN(14字节)、IP数据报首部长度PBUF_IP_HLEN(20字节)、TCP首部长度PBUF_TRANSPORT_HLEN(20字节)。当数据报往下层递交的时候,其他层直接填充对应的协议首部即可,无需对数据进行拷贝等操作,这也是LwIP能快速处理的优势。