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 <acl-idx>
可以看到,需要在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>輸入的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, "<acl-idx>")
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, "<acl-idx>") \
- 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.cacl.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, "<acl-idx>")
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, "<acl-idx>")
#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)