vpp中plugin的api编程(1) - 简单使用

网友投稿 655 2022-10-16


vpp中plugin的api编程(1) - 简单使用

API简介

vpp其实也有自己的control-plane。它们之间的就是使用API来交互,底层是用的共享内存机制。control-plane可以是使用不同的语言来写,支持C/python/java/go 在这里了解的是用C语言与vpp通信。如图1所示。VAT通过命令行来控制VPP。

图1,VAT(vpp api test)与vpp通信: +--------------+ | | | VPP API TEST + | | +------+-------+ | binary API | (shmem) | | +------+-------+ | | | VPP | | | +--------------+

了解vat与vpp使用API编程有以下几个意义。 1.了解消息传递的大致原理 2.知道xxx.api的写法 3.知道VPP部分的代码写法 4.在一些情况下可以使用VAT进行调试

步骤

我们通过acl_del这个命令来当例子了解vat与vpp是如何使用api编程的,在vpp_api_test中有这个命令

vat# help acl_del usage: acl_del

可以看到,需要在vat中解析一个acl-index,传给vpp,接着vpp会删除这个index的acl,然后告知vat。

添加一个api需要修改三个文件。代码路径是vpp/src/plugins/acl下 acl.api   --  vat 与vpp 通信的结构体定义 acl_test.c  --  vat使用 acl.c    --  vpp使用

我们只需修改3个文件,6个部分就能完成,干货可以直接看==总结==部分

1.acl.api

acl.api中定义vat与vpp通信的结构体,然后由vppapigen文件处理,最终生成acl.api.h的头文件。两边都包含这个头文件,这样vat与vpp就使用了相同的结构体通信了。 我们看一下acl.api中的定义

/** \brief Delete an ACL @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param acl_index - ACL index to delete */ autoreply manual_print define acl_del { u32 client_index; //系统使用 u32 context; //系统使用 u32 acl_index; //通过命令acl_del 输入的acl-idx };

这个结构体的定义由3个关键字(autoreply 、manual_print、define )加上名称再加成员构成,最终会被转化为

typedef VL_API_PACKED(struct _vl_api_acl_del { u16 _vl_msg_id; u32 client_index; u32 context; u32 acl_index; }) vl_api_acl_del_t; typedef VL_API_PACKED(struct _vl_api_acl_del_reply { u16 _vl_msg_id; u32 context; i32 retval; }) vl_api_acl_del_reply_t;

这样就可以用使用vl_api_acl_del_t与vl_api_acl_del_reply这个结构体通信了。 具体说一下每个部分

关键字

关键字 autoreply 在这里需要先提一下reply 正常情况下,vat发送一个请求消息,然后等待一个reply消息。所以xxx_reply_t结构是不可少的,可以自己写,也可自动生成。 而这个关键字表示了自动生成xxx_reply_t结构体,但是自动生成的结构体只有默认的参数_vl_msg_id,context,retval。如上所示vl_api_acl_del_reply_t。 这个转换的函数实现如下。 void autoreply (void *np_arg) { static u8 *s; node_t *np = (node_t *)np_arg; int i; vec_reset_length (s); s = format (0, " define %s_reply\n", (char *)(np->data[0])); s = format (s, "{\n"); s = format (s, " u32 context;\n"); s = format (s, " i32 retval;\n"); s = format (s, "};\n"); for (i = 0; i < vec_len (s); i++) clib_fifo_add1 (push_input_fifo, s[i]); } 关键字manual_print xxx_print函数是用来打印消息结构体内容的。默认情况下会自动生成。如果你想自己来实现,就需要加入这个关键字,然后在模块路径下的manual_fns.h中实现。

196 static inline void * 197 vl_api_acl_del_t_print (vl_api_macip_acl_del_t * a, void *handle) 198 { 199 u8 *s; 200 201 s = format (0, "SCRIPT: acl_del %d ", 202 clib_host_to_net_u32 (a->acl_index)); 203 204 PRINT_S; 205 return handle; 206 }

关键字define define 关键字是转化的关键。每个定义都要加上。 其他关键字 还有一些其他的关键字,大家对比一下定义与生成的结果也基本都能看出来,在这里就不赘述了。

名称

这个结构体的名称为acl_del,非常重要,最终会使用它生成各种相关的结构体、函数。

结构体成员

见注释。

2.acl_test.c

这个文件是vat使用。有三件事要做,1.写cli的help 2.写函数 3.函数加载

2.1写cli的help

#define foreach_vpe_api_msg \ _(acl_del, "")

2.2写函数

我们需要写两个函数api_acl_del与vl_api_acl_del_reply_t_handler 这两个函数是配合使用的,来一个一个看

api_acl_del 这个函数是与cli直接关联,命令输入后就调用的就是这个函数.

526 static int api_acl_del (vat_main_t * vam) 527 { 528 unformat_input_t * i = vam->input; //这个结构体就是在acl.api中定义的消息传递结构体 529 vl_api_acl_del_t * mp; 530 u32 acl_index = ~0; 531 int ret; 532 //解析字符串,跟vpp的命令行解析一样 533 if (!unformat (i, "%d", &acl_index)) { 534 errmsg ("missing acl index\n"); 535 return -99; 536 } 537 //给mp分配内存,然后填写要传递的值 538 /* Construct the API message */ 539 M(ACL_DEL, mp); 540 mp->acl_index = ntohl(acl_index); 541 542 /* send it... */ 543 S(mp); 544 545 /* Wait for a reply... */ 546 W (ret); 547 return ret; 548 }

在这里把这几个宏的实现也贴一下。对应一下,就能看明白了。

/* M: construct, but don't yet send a message */ #define M(T, mp) \ do { \ vam->result_ready = 0; \ mp = vl_msg_api_alloc_as_if_client(sizeof(*mp)); \ memset (mp, 0, sizeof (*mp)); \ mp->_vl_msg_id = ntohs (VL_API_##T+__plugin_msg_base); \ mp->client_index = vam->my_client_index; \ } while(0); /* S: send a message */ #define S(mp) (vl_msg_api_send_shmem (vam->vl_input_queue, (u8 *)&mp)) /* W: wait for results, with timeout */ #define W(ret) \ do { \ f64 timeout = vat_time_now (vam) + 1.0; \ ret = -99; \ \ while (vat_time_now (vam) < timeout) { \ if (vam->result_ready == 1) { \ ret = vam->retval; \ break; \ } \ vat_suspend (vam->vlib_main, 1e-5); \ } \ } while(0);

vl_api_acl_del_reply_t_handler 这个函数是在vpp回复消息后,clinet接收回应的函数。由于大多数都一样,就直接用宏来实现了。

#define foreach_standard_reply_retval_handler \ _(acl_del_reply) #define _(n) \ static void vl_api_##n##_t_handler \ (vl_api_##n##_t * mp) \ { \ vat_main_t * vam = acl_test_main.vat_main; \ i32 retval = ntohl(mp->retval); \ if (vam->async_mode) { \ vam->async_errors += (retval < 0); \ } else { \ vam->retval = retval; \ vam->result_ready = 1; \ } \ } foreach_standard_reply_retval_handler; #undef _

这两个函数的关系 这两个函数是在不同的线程,在宏M中,是在等待result_ready被置位;而result_ready 就是在xxx_reply_handler中被置位的。

2.3加载函数

需要把写的两个函数挂载上。只需要在对应的宏下按格式写就好了。

2.3.1 在宏中添加定义

api_acl_del 在foreach_vpe_api_msg宏下定义 其实这个在“2.1写cli的help”中已经写过,就不用再写了

/* * List of messages that the api test plugin sends, * and that the data plane plugin processes */ #define foreach_vpe_api_msg _(acl_del, "") \

vl_api_acl_del_reply_t_handler 在foreach_vpe_api_reply_msg宏下定义 /* * Table of message reply handlers, must include boilerplate handlers * we just generated */ #define foreach_vpe_api_reply_msg \ _(ACL_DEL_REPLY, acl_del_reply)

2.3.2 函数挂载

上一节提到的宏,都是在acl_vat_api_hookup这个函数中使用的,我们不需要做任何修改。

static void acl_vat_api_hookup (vat_main_t *vam) { acl_test_main_t * sm = &acl_test_main; /* Hook up handlers for replies from the data plane plug-in */ #define _(N,n) \ vl_msg_api_set_handlers((VL_API_##N + sm->msg_id_base), \ #n, \ vl_api_##n##_t_handler, \ vl_noop_handler, \ vl_api_##n##_t_endian, \ vl_api_##n##_t_print, \ sizeof(vl_api_##n##_t), 1); foreach_vpe_api_reply_msg; #undef _ /* API messages we can send */ #define _(n,h) hash_set_mem (vam->function_by_name, #n, api_##n); foreach_vpe_api_msg; #undef _ /* Help strings */ #define _(n,h) hash_set_mem (vam->help_by_name, #n, h); foreach_vpe_api_msg; #undef _ }

3.acl.c

这个文件是vpp使用,它用来接收vat(vpp-api-test)发送的消息,然后处理,最后回应给vat。 我们需要写对应的函数,然后挂上就可以了

写宏 只需要在这个宏里,把函数添加进去即可

/* List of message types that this plugin understands */ #define foreach_acl_plugin_api_msg \ _(ACL_DEL, acl_del)

写函数 这里的函数也得遵循格式,vl_api_xxx_t_handler,函数如下所示

static void vl_api_acl_del_t_handler (vl_api_acl_del_t * mp) { acl_main_t *am = &acl_main; //这个结构体就是在acl.api中定义的消息应答传递结构体,用于给VAT发送应答消息 vl_api_acl_del_reply_t *rmp; int rv; //mp中就是VAT发送来的结构体,我们可以从中取得配置的acl_index使用。然后调用相应的处理函数。 rv = acl_del_list (ntohl (mp->acl_index)); //这里是消息处理完毕后的应答消息,VAT会在那里等待回应。也是通过共享内存的方式来通信。 //如果需要在回应消息里传递参数,可以使用另一个宏 --- REPLY_MACRO2 REPLY_MACRO (VL_API_ACL_DEL_REPLY); }

挂钩子 使用定义的宏来挂载函数。这里也不用做任何的改变。 /* Set up the API message handling tables */ static clib_error_t * acl_plugin_api_hookup (vlib_main_t * vm) { acl_main_t *am = &acl_main; #define _(N,n) \ vl_msg_api_set_handlers((VL_API_##N + am->msg_id_base), \ #n, \ vl_api_##n##_t_handler, \ vl_noop_handler, \ vl_api_##n##_t_endian, \ vl_api_##n##_t_print, \ sizeof(vl_api_##n##_t), 1); foreach_acl_plugin_api_msg; #undef _ return 0; } 总结 这样我们就完成了从vat到vpp的通信。现在回顾一下。 需要修改3个文件6个步骤。 acl.api acl_test.c acl.c acl.api 定义通信结构体 autoreply manual_print define acl_del { u32 client_index; u32 context; u32 acl_index; };

acl_test.c

1.定义命令行帮助

#define foreach_vpe_api_msg \ _(acl_del, "")

2.实现2个函数

static int api_acl_del (vat_main_t * vam) static void vl_api_acl_del_reply_t_handler(vl_api_acl_del_t * mp)

3.分别写宏

#define foreach_vpe_api_msg //此宏与命令行帮助宏共用 _(acl_del, "") #define foreach_vpe_api_reply_msg _(ACL_DEL_REPLY, acl_del_reply)

acl.c

1.实现函数

static void vl_api_acl_del_t_handler (vl_api_acl_del_t * mp) { acl_main_t *am = &acl_main; vl_api_acl_del_reply_t *rmp; int rv; rv = acl_del_list (ntohl (mp->acl_index)); REPLY_MACRO (VL_API_ACL_DEL_REPLY); }

2.写宏

#define foreach_acl_plugin_api_msg \ _(ACL_DEL, acl_del)


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:vpp中plugin的api编程(2) - VPE vs. Plugin Messages id
下一篇:SpringBoot实现异步事件驱动的方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~