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 <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.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, "<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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章