dpdk 官方第三个例子介绍了如何使用cmdline相关接口实现一个命令行工具
效果还是比较不错的,可以实现tab补全,前缀匹配的功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52nQGoeZ-1655814968513)(https://asciinema.org/a/gE1fuJykrE3mDqjtmMqacGon3.svg)]
在此梳理几个关键的函数的结构,说明一下cmdline接口的使用方法
int main(int argc, char **argv)
{
...
struct cmdline *cl;
...
cl = cmdline_stdin_new(main_ctx, "example> ");
...
cmdline_interact(cl);
cmdline_stdin_exit(cl);
...
return 0;
}
核心结构是struct cmdline *cl;
cl是一个cmdline实例的句柄,通过cmdline_stdin_new
创建并关联了自定义上下文main_ctx
之后的交互逻辑cmdline_interact
和释放逻辑cmdline_stdin_exit
都使用这个句柄
对使用者来讲只要正确的传递cl即可,最关键的结构是main_ctx
我们来看一下main_ctx
的定义
cmdline_parse_ctx_t main_ctx[] = {
(cmdline_parse_inst_t *)&cmd_obj_del_show,
(cmdline_parse_inst_t *)&cmd_obj_add,
(cmdline_parse_inst_t *)&cmd_help,
NULL,
};
可以看到main_ctx
是一个数组,用来存放所有支持的命令,每一个命令的类型是cmdline_parse_inst_t *
拿cmd_obj_del_show
的初始化来举例
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,
},
};
.f
字段是错有参数解析完成之后的回调函数
.data
是一个透传参数
.help_str
是帮助信息字符串
.token
是最关键的,包含了所有参数的类型声明
每一个token的声明都被转换成了void *放在tokens数组中,转换之前的类型是dpdk已经帮我们定义好的一些token类型
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);
可以看到前两个类型是string,最后一个类型是ip
我们用cmd_obj_ip
类型举例, 它的类型是cmdline_parse_token_ipaddr_t
struct cmdline_token_ipaddr_data {
uint8_t flags;
};
struct cmdline_token_ipaddr {
struct cmdline_token_hdr hdr;
struct cmdline_token_ipaddr_data ipaddr_data;
};
typedef struct cmdline_token_ipaddr cmdline_parse_token_ipaddr_t;
再看一下初始化宏TOKEN_IPADDR_INITIALIZER的定义
#define TOKEN_IPADDR_INITIALIZER(structure, field) \
{ \
/* hdr */ \
{ \
cmdline_token_ipaddr_ops, /* ops */ \
offsetof(structure, field), /* offset */ \
}, \
/* ipaddr_data */ \
{ \
CMDLINE_IPADDR_V4 | /* flags */ \
CMDLINE_IPADDR_V6, \
}, \
}
这里先关注/*hdr*/
注释下面的内容,cmdline_token_ipaddr_ops
用来初始化 cmdline_token_hdr
结构体中的hdr
我们会发现,所有dpdk预定义的token结构体都有一个公用的部分,是cmdline_token_hdr
struct cmdline_token_hdr {
struct cmdline_token_ops *ops;
unsigned int offset;
};
ops 是用来对输入做parse操作的一个集合
offset 表示parse操作要将解析数据之后的内容存放到临时buffer的偏移量
cmdline内部逻辑会串行的解析tokens数组中的参数,并按照注册的offset放到临时buffer中
那么offset是什么呢
可以看到,在初始化宏TOKEN_IPADDR_INITIALIZER
中是这样实现的
offsetof(structure, field)
我们将这句话展开
TOKEN_IPADDR_INITIALIZER(struct cmd_obj_add_result, ip);
就是
offsetof(cmd_obj_add_result, ip)
也就是求ip
字段在cmd_obj_add_result
结构体中的偏移
cmd_obj_add_result
结构体定义如下
struct cmd_obj_add_result {
cmdline_fixed_string_t action;
cmdline_fixed_string_t name;
cmdline_ipaddr_t ip;
};
对应三个初始化宏,可以发现,三个token的offset正好是按照结构题中的字段排布累加出来的,也就是说,最终传递给回调函数的buffer中,刚好是一个cmd_obj_add_result
的内存排布
我们看 cmd_obj_add_parsed的实现,也能够看出来,dpdk实现的通用逻辑并不关心各个parse之后要放入一个具体什么结构,而只是傻傻的将解析的内容填写到一个临时buffer 指定的offset地址,用一些宏定义巧妙的传输了结构体的内存布局信息,并且还让通用逻辑不关心这个信息
static void cmd_obj_add_parsed(void *parsed_result,
struct cmdline *cl,
__rte_unused void *data)
{
struct cmd_obj_add_result *res = parsed_result;
...
}
最终的parse逻辑之需要拿到这个buffer强转成需要的指针即可
总结一下
dpdk cmdline接口靠ctx获取所有命令字种类
靠每个命令字的tokens列表获得解析方法
将解析的结果放入命令字的回调函数
使用dpdk cmdline相关的接口函数,我们要提供以下几个内容
- 命令字列表(元素为
cmdline_parse_inst_t*
的数组) - 每个命令字的详细token的解析方式(回调函数,各个token定义)
- 对于每个token,定义解析后存放在结构题中的相对内存地址
- 如果要自定义token类型,需要一同自定义token的parse方法,初始化宏(参考parse_obj_list.h)