学会Zynq(17)pbuf操作函数介绍与示例程序

从前面几个UDP的程序实例中我们可以体会到pbuf的重要性,对pbuf的灵活操作也是完成程序功能和提高代码效率的关键。本篇总结lwip的pbuf.c中的常用函数并给出示例程序,其中部分函数和string.h文件中提供的传统内存操作函数功能相同。

基于上一篇中的UDP echo服务器设计,主要修改接收回调函数,将收到的数据做一定处理后,用串口打印或网口原路返回的方式查看效果。

1. pbuf_strstr
该函数在pbuf p中查找substr。但和strstr()函数不同,不会在遇到第一个’\0’时停止。
u16_t pbuf_strstr(struct pbuf* p, const char* substr)

第一个参数p是待搜索的pbuf,最大长度为0xFFFE。第二个参数substr是要在p中搜索的字符串。找到该字符串时返回索引,未找到则返回0xFFFF。
/* test1 pbuf_strstr */
u16_t pos;
pos = pbuf_strstr(p, "hello");
if (pos == 0xFFFF)
xil_printf("Havn't found 'hello'.\r\n");
else
xil_printf("find 'hello' at %d", pos);

关键代码如上,查找接收数据中的’hello’字符串。测试结果如下:

2. pbuf_memfind
该函数在pbuf p中查找出现的长度为mem_len的mem,查找位置从start_offest开始。
u16_t pbuf_memfind(struct pbuf* p, const void* mem, u16_t mem_len, u16_t start_offset)

p的最大长度也是0xFFFE。返回值意义与pbuf_strstr函数相同。该函数可以用start_offest指定在p中开始搜索的偏移位置。
/* test2 pbuf_strstr */
u16_t pos;
pos = pbuf_memfind(p, "hello", 5, 1);
if (pos == 0xFFFF)
xil_printf("Havn't found 'hello'.\r\n");
else
xil_printf("find 'hello' at %d\r\n", pos);

关键代码如上,查找接收数据中的’hello’字符串,偏移设置为1。测试结果如下,注意第二条发送hello没有被检测到,第三条被检测到了,这便是偏移量的作用:

3. pbuf_memcmp
该函数将指定偏移量的pbuf内容与内存s2进行比较,二者长度均为n。
u16_t pbuf_memcmp(struct pbuf* p, u16_t offset, const void* s2, u16_t n)

offset是开始比较的p的偏移位置;n是比较缓冲区的长度。如果二者相等则返回0。
u16_t pos;
pos = pbuf_memcmp(p, 1, "hello", 5);
if (pos != 0)
xil_printf("NOT EQUAL.\r\n");
else
xil_printf("EQUAL.\r\n");

关键代码如上,比较p中偏移为1开始的5个字节是否等于“hello”。测试结果如下,三条信息都有hello,但只有第一条被检测为相等:

4. pbuf_get_at
该函数从pbuf中的指定位置获取一个字节。
u8_t pbuf_get_at(struct pbuf* p, u16_t offset)

p是待解析的pbuf;offset是待返回字节在p中的偏移量。该函数返回偏移处的字节,但如果设置的offset大于pbuf的tot_len,则返回0。

/* test4 pbuf_get_at */
u8_t get_byte;
get_byte = pbuf_get_at(p, 1);
xil_printf("GET %c\r\n", get_byte);

关键代码如上,比较p中偏移为1的字节。测试结果如下:

5. pbuf_coalesce
该函数将一个pbuf队列合并为一个单独的pbuf。
struct pbuf* pbuf_coalesce(struct pbuf *p, pbuf_layer layer)

参数p是源pbuf;layer是新建pbuf的层。分配成功时返回一个单独的pbuf,p-next指向NULL,同时源pbuf被释放;分配失败时仍然返回旧的pbuf。因此需要我们检查分配结果。

6. pbuf_take
该函数将应用程序提供的数据复制到pbuf中。
err_t pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len)

参数buf是要填充数据的pbuf;dataptr是应用程序中的数据缓冲区;len是数据缓冲区的长度。操作成功则返回ERR_OK;如果pbuf小于要复制的长度,则返回ERR_MEM。
struct pbuf* q;
q = pbuf_alloc(PBUF_TRANSPORT, 12, PBUF_RAM);
memset(q->payload, 0, 12);
err_t err;
err = pbuf_take(q, "hello world!", 12);
if (err != ERR_OK)
xil_printf("error on pbuf_take: %d.\r\n", err);
udp_printf(q, tpcb, addr, port);
pbuf_free(q);

关键代码如上,其与memcpy函数的作用相同。发送字符触发接收回调,测试结果如下:

7. pbuf_copy_partial
该函数与pbuf_take函数相反,是将pbuf(包缓冲区)中的内容复制到应用程序的缓冲区中。
u16_t pbuf_copy_partial(struct pbuf *buf, void *dataptr, u16_t len, u16_t offset)

参数buf是数据源;datatpr是应用程序中的缓冲区;len是复制的数据长度;offset是从buf中开始复制的偏移位置,从这里开始复制len个字节。该函数返回复制了的字节数,如果操作失败则返回0。
/* test7 pbuf_copy_partial */
char uart_send_buf[6] = "";
u16_t copynum;
copynum = pbuf_copy_partial(p, (void *)uart_send_buf, 5, 1);
xil_printf(uart_send_buf);
xil_printf("\r\ncopy number: %d.\r\n", copynum);

关键代码如上,从p偏置1处开始复制5个字节到uart_send_buf中。测试结果如下,注意当p中的字符不够5个时,pbuf_copy_partial只复制了三个字节:

8. pbuf_copy
该函数创建pbuf的一个PBUF_RAM类型的副本。该函数只会复制一个包,不会复制整个包队列。
err_t pbuf_copy(struct pbuf *p_to, struct pbuf *p_from)

将p_from复制到p_to。成功则返回ERR_OK;如果有一个pbuf为NULL,或者p_to不够大无法装下p_from,则返回ERR_ARG。
/* test8 pbuf_copy */
struct pbuf* p_to = NULL;
p_to = pbuf_alloc(PBUF_TRANSPORT, 20, PBUF_RAM);
memset(p_to->payload, 0, 20);
err_t err = pbuf_copy(p_to, p);
if (err != ERR_OK)
xil_printf("error on pbuf_copy: %d.\r\n", err);
udp_printf(p_to, tpcb, addr, port);
pbuf_free(p_to);

关键代码如上,p_to分配了20个字节,将接收到的信息复制到p_to中再发回。第一条copy成功;第二条由于p_to大小不够复制失败,报错-14对应的正是ERR_ART。

9. pbuf_alloc
该函数用于分配指定类型的pbuf,分配的实际内存由设置的pbuf层和请求的大小决定。其函数原型如下:
struct pbuf * pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)

第一个参数pbuf层定义头大小;第二个参数length决定pbuf有效载荷的大小;第三个参数pbuf类型决定pbuf的方式和位置。pbuf类型共有四种:

  • PBUF_RAM:以大块(chunk)的形式为pubf分配缓冲区内存和协议头。
  • PBUF_ROM:不会为pbuf分配缓冲区内存和协议头,在分配另一个pbuf并链到ROM型pbuf前时,必须为其预先分配协议头。这种pbuf使用的内存和ROM很相似,不可变且不会被更改。动态内存应该使用PBUF_REF。
  • PBUF_REF:通用不会为pbuf分配缓冲区内存和协议头。假设pbuf只在单个线程中使用,当pbuf进入队列时要调用pbuf_take来复制缓冲区。
  • PBUF_POOL:pbuf被分配作为一个pbuf链,大量数据时使用。
  • pbuf_alloc会返回分配的pbuf。如果分配了多个pbuf,则返回pbuf链中的第一个pbuf。

    10. pbuf_alloced_custom
    该函数用于初始化已分配的pbuf,相当于自定义pbuf。
    struct pbuf* pbuf_alloced_custom(pbuf_layer l, u16_t length, pbuf_type type, struct pbuf_custom *p, void *payload_mem, u16_t payload_mem_len)

    参数l为层标志,定义头的大小;参数length是pbuf有效载荷的长度;参数type为pbuf的类型;参数p指向要初始化的已分配了的pbuf;参数pay_load_mem指向用于有效载荷和头的缓冲区,该缓冲区必须能容纳下lengh+头的大小,也可以设置为NULL,在后面再进行设置;参数mem_len即为“payload_mem”缓冲区的大小。

    11. pbuf_realloc
    该函数用于将pbuf链缩小到所需的长度。虽然它叫“realloc”,但它并不能增加pbuf链的大小。
    void pbuf_realloc(struct pbuf *p, u16_t new_len)

    p为待缩小的pbuf;new_len是新的pbuf链长。根据设置的长度,pbuf链的前几个pbuf保持不变,释放一部分pbuf,并调整新链中的最后一个pbuf的大小。
    如果操作的是ROM/REF类型的pbuf,其并不是pbuf链,因此只会调整其tot_len和len的值。该函数也不能在包队列上调用。

    12. pbuf_free
    pbuf的引用计数器相当于指向pbuf的指针数量。pbuf_free函数将减少对pbuf链或队列的引用次数。引用次数减到0时会释放pbuf。对于一个数据链,该函数对链中的每个pbuf都重复这个过程,直到第一个pbuf在递减后引用次数为非0位置。当链中所有的引用次数都是1时,则整个链被释放。
    u8_t pbuf_free(struct pbuf *p)

    该函数会返回链中从头开始释放的pbuf数量。比如一个链“a->b->c”,调用pbuf_free(a):

  • 当前引用次数为“3->3->3”,结果为“2->3->3”;
  • 当前引用次数为“1->2->3”,结果为“free->3->3”;
  • 当前引用次数为“1->1->2”,结果为“free->free->1”;
  • 当前引用次数为“2->1->1”,结果为“1->1->1”;
  • 当前引用次数为“1->1->1”,结果为“free->free->free”;
  • 13. pbuf_clen
    该函数用于计算一个链中pbuf的数量。
    u8_t pbuf_clen(struct pbuf *p)

    参数p是链中的第一个pbuf。返回链中pbuf的数量。该函数与pbuf_cat一起测试。

    14. pbuf_ref
    该函数增加pbuf的引用次数。
    void pbuf_ref(struct pbuf *p)

    15. pbuf_cat
    该函数用于连接两个pbuf,每个pbuf都可能是一个pbuf链。
    void pbuf_cat(struct pbuf *h, struct pbuf *t)

    使用此函数后,我们仅需要操作头部的pbuf即可,表示我们后面再也不会引用尾部的pbuf。
    /* test15 pbuf_cat and pbuf_clen*/
    struct pbuf* q;
    q = pbuf_alloc(PBUF_TRANSPORT, 13, PBUF_RAM);
    memset(q->payload, 0, 13);
    err_t err = pbuf_take(q, "FPGADesigner:", 13);
    if (err != ERR_OK)
    xil_printf("error on pbuf_take: %d.\r\n", err);
    pbuf_cat(q, p);
    udp_printf(q, tpcb, addr, port);
    u8_t pnum = pbuf_clen(q);
    xil_printf("The number of pbuf in q is:%d.\r\n", pnum);
    pbuf_free(q);

    关键代码如上,将本地的q和接收到的p连接在一起,后面无需再对p进行任何操作。测试结果如下,可看到pbuf_clen得到的连接后的pbuf链中由2个pbuf。

    16. pbuf_chain
    该函数也是将两个pbuf或pbuf链连接在一起。pbuf必须属于同一个包,且不要在包队列上调用此函数。使用pbuf_chain表示后续还可能会用pbuf_dechain解链,因此当不再使用t时必须用pbuf_free(t)释放它。如果希望连接后不再使用t,应该使用pbuf_cat函数。
    void pbuf_chain(struct pbuf *h, struct pbuf *t)

    参数h是连接后pbuf(链)的头;参数t是连接后pbuf(链)的尾。使用该函数会调整链中所有pbuf的tot_len字段、头的最后一个pbuf的next字段、尾的第一个pbuf的ref字段。

    该函数的测试代码与pbuf_cat基本相同,只是最后还需要释放作为连接尾部的p,而pbuf_cat不需要。下面pbuf_dechain的例子也用了pbuf_chain,这里不再赘述。

    17. pbuf_dechain
    该函数将第一个pbuf与链中剩余的pbuf分离开。该函数不能在包队列上调用。
    struct pbuf * pbuf_dechain(struct pbuf *p)

    参数p是待解链的pbuf。该函数返回pbuf链的剩余部分。
    /* test16 pbuf_chain and pbuf_dechain*/
    struct pbuf* q;
    q = pbuf_alloc(PBUF_TRANSPORT, 13, PBUF_RAM);
    memset(q->payload, 0, 13);
    err_t err = pbuf_take(q, "FPGADesigner:", 13);
    if (err != ERR_OK)
    xil_printf("error on pbuf_take: %d.\r\n", err);
    pbuf_chain(p, q); //连接
    udp_printf(p, tpcb, addr, port);
    q = pbuf_dechain(p); //解链
    udp_printf(q, tpcb, addr, port);
    pbuf_free(q);
    pbuf_free(p); //释放pbuf

    核心代码如上,将收到的p连接本地q,沿原路发送时只操作p即可;再对pbuf链p解链,去掉第一个p,恢复出q,再沿原路发送时还可以操作q。下面是测试结果。

    ---------------------


    文章来源:FPGADesigner的博客
    *本文由作者授权转发,如需转载请联系作者本人

    最新文章

    最新文章