DPDK - cmdline
1 简介
本文进行 DPDK 命令行界面的用法与分析,对应模块处于dpdk/lib/cmdline路径下,DPDK 版本为 21.11.*,其中目录头文件结构如下:
.
├── cmdline.h
├── cmdline_socket.h
├── cmdline_parse.h
├── cmdline_parse_etheraddr.h
├── cmdline_parse_ipaddr.h
├── cmdline_parse_num.c
├── cmdline_parse_num.h
├── cmdline_parse_portlist.h
├── cmdline_parse_string.h
├── cmdline_private.h
├── cmdline_rdline.h
├── cmdline_cirbuf.h
├── cmdline_vt100.h
└── version.map
据说 rte_cmdline 库并不适用于生产环境,因为他未按照XX标准进行验证,故而更适用于开发出一些调试功能
2 用例解析
DPDK 官方提供了一个示例程序,并提供了一个新的将对象存储在列表中的示例。
2.1 main.c
针对 DPDK 环境进行初始化,同时创建出新的命令行,新学习的函数如下:
- cmdline_stdin_new
struct cmdline *cmdline_stdin_new(cmdline_parse_ctx_t *ctx, const char *prompt);
/* Creating a new command line object. 8< */
struct cmdline *cl = cmdline_stdin_new(main_ctx, "example> ");
该函数对命令行进行初始化,需要指定两个参数,其中example>是启动时提示符,main_ctx中包含了指令列表
- cmdline_stdin_exit
与
1.中的函数都位于cmdline_socket.h文件中,控制命令行的交互行为,该函数控制退出命令行
void cmdline_stdin_exit(struct cmdline *cl);
- cmdline_interact
函数能够让用户键入
Ctrl-D时返回,位于cmdline.h中,该文件定义了命令行的基础功能
void cmdline_interact(struct cmdline *cl);
2.2 commands.c
DPDK 提供了命令解析的通用模板,包括 STRING、IPADDR、NUMBER、ETHERADDR等。这个文件实现了上述部分模板的功能,作为命令行所需要的指令列表。
- 单 token - string 的应用
关注
cmdline_fixed_string_t,它是匹配字符串类型的命令时,DPDK 定义的通用类型
struct cmd_help_result {
cmdline_fixed_string_t help;
};
这里使用宏TOKEN_STRING_INITIALIZER将结构体cmdline_parse_token_string_t进行了初始化,填入上一步的成员 help,以及需要匹配的字符串help
cmdline_parse_token_string_t cmd_help_help =
TOKEN_STRING_INITIALIZER(struct cmd_help_result, help, "help");
cmd_help_parsed 是具体的回调函数,当命令被匹配时,该函数会被执行
static void cmd_help_parsed(__rte_unused void *parsed_result,
struct cmdline *cl,
__rte_unused void *data)
{
cmdline_printf(cl, "Demo example of command line interface in RTE\n\n);
}
这里的 .f 配置将执行回调函数,.data 表示回调函数的额外参数,.help_str 为帮助字符,以及组成指令的令牌列表 .tokens
cmdline_parse_inst_t cmd_help = {
.f = cmd_help_parsed, /* function to call */
.data = NULL, /* 2nd arg of func */
.help_str = "show help",
.tokens = { /* token list, NULL terminated */
(void *)&cmd_help_help,
NULL,
},
};
上面就是配置一个命令的基础模板,按照指定的格式即可使用 DPDK 提供的 STRING 命令解析
- 组合 token - ip 的应用
该案例使用
cmdline_ipaddr_t能识别 IPADDR 类型参数,而无需开发者额外处理
struct cmd_obj_add_result {
cmdline_fixed_string_t action;
cmdline_fixed_string_t name;
cmdline_ipaddr_t ip;
};
和上一个案例类似的,将会提供一个宏TOKEN_IPADDR_INITIALIZER来初始化对应类型的参数cmdline_parse_token_ipaddr_t。可以观察到第二行的name变量未指定具体的字符串,它表示一个 STRING 类型的占位,填入任意字符类型即可
cmdline_parse_token_string_t cmd_obj_action_add =
TOKEN_STRING_INITIALIZER(struct cmd_obj_add_result, action, "add");
cmdline_parse_token_string_t cmd_obj_name =
TOKEN_STRING_INITIALIZER(struct cmd_obj_add_result, name, NULL);
cmdline_parse_token_ipaddr_t cmd_obj_ip =
TOKEN_IPADDR_INITIALIZER(struct cmd_obj_add_result, ip);
cmd_obj_add_parsed 是具体的回调函数
static void cmd_obj_add_parsed(void *parsed_result,
struct cmdline *cl,
__rte_unused void *data)
{
cmdline_printf(cl, "multi token example of command line interface in RTE\n\n);
}
长指令的 .tokens 项,将指令按顺序的填充
cmdline_parse_inst_t cmd_obj_add = {
.f = cmd_obj_add_parsed, /* function to call */
.data = NULL, /* 2nd arg of func */
.help_str = "Add an object (name, val)",
.tokens = { /* token list, NULL terminated */
(void *)&cmd_obj_action_add,
(void *)&cmd_obj_name,
(void *)&cmd_obj_ip,
NULL,
},
};
无论是使用 STRING 类型或是 IPADDR 类型的参数解析,都可以按照指定的模板形式来填写
- 命令的解析与调用 我们定义好 1. 与 2. 的各种指令,需要最后一步完成命令,将所有定义好的命令组织一起,交给命令行组件进行解析
/* Cmdline context list of commands in NULL-terminated table. 8< */
cmdline_parse_ctx_t main_ctx[] = {
(cmdline_parse_inst_t *)&cmd_obj_add,
(cmdline_parse_inst_t *)&cmd_help,
NULL,
};
/* >8 End of context list. */
这里组织的main_ctx数组有些熟悉,它将交由第一步的文件main.c,其中cmdline_stdin_new函数的参数传入:
struct cmdline *cl = cmdline_stdin_new(main_ctx, "example> ");
此刻整个流程都串联起来,我们也学会了 DPDK 命令行组件的基本使用。
2.3 parse_obj_list.c
利用 DPDK 封装好的类型,基本能完成大部分的命令解析能力,案例中提供了自定义类型的扩展,能让开发者玩出更多花活。在 parse_obj_list.c文件中,定义了一个 OBJ 类型,用于 IP 地址的存储任务,我们可以分析一下,完成一个自定义类型,需要哪些步骤。
- 明确自定义类型的结构
struct object {
SLIST_ENTRY(object) next;
char name[OBJ_NAME_LEN_MAX];
cmdline_ipaddr_t ip;
};
/* define struct object_list */
SLIST_HEAD(object_list, object);
/* data is a pointer to a list */
struct token_obj_list_data {
struct object_list *list;
};
这里基本类型是struct object,后续将该类型的链表封装成struct token_obj_list_data,方便指针转换与访问
- 定义令牌解析的结构
struct token_obj_list {
struct cmdline_token_hdr hdr;
struct token_obj_list_data obj_list_data;
};
typedef struct token_obj_list parse_token_obj_list_t;
这里定义的是用于解析命令、存储解析结果的包装结构,该结构体会挂接到struct cmdline_inst下的tokens字段上
#define TOKEN_OBJ_LIST_INITIALIZER(structure, field, obj_list_ptr) \
{ \
.hdr = { \
.ops = &token_obj_list_ops, \
.offset = offsetof(structure, field), \
}, \
.obj_list_data = { \
.list = obj_list_ptr, \
}, \
}
同样的,可以定义一个宏来简化上面结构体的初始化动作。
- struct cmdline_inst & struct cmdline_token_hdr
结构
struct cmdline_inst提供了命令被回调时具体的执行操作,令牌匹配逻辑等能力,其中tokens字段就使用到了 2. 中的结构
struct cmdline_inst {
...
cmdline_parse_token_hdr_t *tokens[];
};
/**
* Stores a pointer to the ops struct, and the offset: the place to
* write the parsed result in the destination structure.
*/
struct cmdline_token_hdr {
struct cmdline_token_ops *ops;
unsigned int offset;
};
typedef struct cmdline_token_hdr cmdline_parse_token_hdr_t;
故 2. 中的结构struct token_obj_list的.hdr字段需要按照struct cmdline_token_hdr的结构定义,便于功能模板公共能力的访问。而.obj_list_data属于自己夹带的私货了。
注释中介绍了struct cmdline_token_hdr,用于存储 ops 结构体指针,并利用 offset 获取的偏移量,能将解析结果写入目标位置
- struct cmdline_token_ops 这个结构体定义了某个令牌的所有能力,如何解析、补全、填充帮助信息等,直接拿出源码与注释
/**
* A token is defined by this structure.
*
* parse() takes the token as first argument, then the source buffer
* starting at the token we want to parse. The 3rd arg is a pointer
* where we store the parsed data (as binary). It returns the number of
* parsed chars on success and a negative value on error.
*
* complete_get_nb() returns the number of possible values for this
* token if completion is possible. If it is NULL or if it returns 0,
* no completion is possible.
*
* complete_get_elt() copy in dstbuf (the size is specified in the
* parameter) the i-th possible completion for this token. returns 0
* on success or and a negative value on error.
*
* get_help() fills the dstbuf with the help for the token. It returns
* -1 on error and 0 on success.
*/
struct cmdline_token_ops {
/** parse(token ptr, buf, res pts, buf len) */
int (*parse)(cmdline_parse_token_hdr_t *, const char *, void *,
unsigned int);
/** return the num of possible choices for this token */
int (*complete_get_nb)(cmdline_parse_token_hdr_t *);
/** return the elt x for this token (token, idx, dstbuf, size) */
int (*complete_get_elt)(cmdline_parse_token_hdr_t *, int, char *,
unsigned int);
/** get help for this token (token, dstbuf, size) */
int (*get_help)(cmdline_parse_token_hdr_t *, char *, unsigned int);
};
我们可以看到整个parse_obj_list.c文件,都是对该结构体方法的实现
struct cmdline_token_ops token_obj_list_ops = {
.parse = parse_obj_list,
.complete_get_nb = complete_get_nb_obj_list,
.complete_get_elt = complete_get_elt_obj_list,
.get_help = get_help_obj_list,
};
- 自定义类型扩展总结
到这里,可以看出完成一个新的命令行解析类型,大概需要分为 3 个步骤:
1)首先需要确定自定义的类型,确定好成员与其对应的数据结构
2)构造一个能包装
struct cmdline_token_hdr的结构体,适配命令行模板的struct cmdline_inst->tokens[]字段,并方便指针访问与转换操作;并可以考虑添加宏来简化该包装结构的构造实现 3)实现struct cmdline_token_hdr结构中,struct cmdline_token_ops的各个下挂函数,定义其令牌如何解析,令牌帮助,令牌补全等能力
注释: cmdline_parse_ctx_t 下挂 cmdline_parse_inst_t cmdline_parse_inst_t 的 tokens 下挂不同的分段指令 cmdline_parse_token_string_t、parse_token_obj_list_t 等