dpdk源码分析:交互式命令行的实现 初始化与退出

Posted 河边小咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dpdk源码分析:交互式命令行的实现 初始化与退出相关的知识,希望对你有一定的参考价值。

一、从一段代码入手

  下面这段代码为dpdk例程 examples/qos_sched/cmdline.c 中的内容,是对封装好的交互式命令行内容进行调用的完整流程。

  可以看到,此段代码总共有两个部分,第一部分为 main_ctx[] 的定义过程,第二部分为一个函数 prompt()

  根据上一篇的内容,可以知道 main_ctx[] 中定义的内容为具体的命令。图中代码中一共定义了11组命令,此篇笔记中不再进行赘述。

  第二部分为函数 prompt(),其中一共调用了三个函数,另外此处的 struct cmdline 即为命令行的结构体。根据传参的内容、函数的返回值以及函数名称,即可简单的猜出这三个函数的作用:

  1. cmdline_stdin_new:传参为命令组以及提示符,返回值为一个 struct cmdline。联系下文中对空值的判定以及函数名,即可推断出此函数作用大致为传入命令组与提示符生成相对应命令行
  2. cmdline_interact:传参为 cmdline_stdin_new 函数生成的命令行,返回值不详。联系函数名可推断出此函数作用大致为开始与指定命令行的交互(interact),即开始接收并解析命令
  3. cmdline_stdin_exit:传参为 cmdline_stdin_new 函数生成的命令行,返回值不详。联系函数名可推断出此函数作用大致为退出指定的交互式命令行

  接下来,将对初始化和退出这两个函数的流程和实现进行分析。

二、初始化函数 cmdline_stdin_new()

  首选查看其定义,其定义位于lib/librte_cmdline/cmdline_socket.c

  从上图源码中可以发现,此函数有两个传参,传参一为命令组,传参二为提示符。简单来看,函数中大概可以分为两部分,第一部分是对终端设置的操作,第二部分是调用了一个新的函数cmdline_new()

1. 终端设置

  首先看一下第一部分,一共是五行。

  1. 此行调用函数 tcgetattr,作用是获取指定终端的设置并储存至一个 termios 结构体内。此处传参一为0,即文件操作符为0的流,为此进程的标准输入流;传参二为获取到的内容储存的位置,为函数中声明的结构体 oldterm
  2. 此行调用函数 memcpy,将 oldterm 中的内容(即上一行中获取到的设置)copy至 term 中。
  3. 此行为修改 term 中的输入模式标志 c_lflag,此处为开启三个设置:ICANON(使用标准输入模式)、ECHO(显示输入字符)、ISIG(当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号)。
  4. 此行调用函数 tcsetattr,作用是设置终端参数。此函数的参数一为终端的文件描述符;参数二为用于控制修改起作用的时间;参数三为结构体 struct termios,其中储存了输入输出模式等设置参数。此处传参一为0,即此进程的标准输入流;传参二为 TCSANOW,代表不等数据传输完毕就立即改变属性;传参三为结构体 term,即上一行配置后的终端设置。
  5. 此行调用函数 setbuf,作用为设置用于流操作的内部缓冲区。此处传参一为标准输入流 stdin,传参二中缓冲区设置为 NULL,即关闭缓冲区。此处关闭缓冲区的意义我认为是方便后续及时对单个输入字符的处理。

2. cmdline_new()

  接着来看一下第二部分,此处调用了函数cmdline_new()。首先查看一下这个函数的定义,其定义位于lib/librte_cmdline/cmdline.c

  此函数的传参一共有四个,根据 cmdline_stdin_new 中的调用来看:传参一是命令组;传参二是提示符;传参三是输入描述符(传入fd为0,即本进程输入流);传参四为输出描述符(传入fd为1,即本进程输出流)。

  函数中首先是对命令组和提示符的合法性进行了非空判定。随后是 mallocmemset 了一块 cmdline 结构体大小的空间,用来储存申请的命令行数据。接着直接对 cmdline 结构体内容进行赋值,输入输出流设为传入的输入输出流,命令组设为传入的命令组。最后就是若干初始化命令行的函数,下面会挨个分析。

2.1 rdline_init()

  首先看一下 rdline_init() 其定义位于 lib/librte_cmdline/cmdline_rdline.c

  注释如下,我也加上了一部分内容方便理解:

/**
 * Init fields for a struct rdline. Call this only once at the beginning
 * of your program.
 * 只需要在初始化rdline前执行一次
 * \\param rdl A pointer to an uninitialized struct rdline 
 * 参数一为一个未初始化的rdline,即用户申请的cmdline中包含的rdline
 * \\param write_char The function used by the function to write a character
 * 参数二为一个函数指针,指向用来写字符的函数
 * \\param validate A pointer to the function to execute when the
 *                 user validates the buffer.
 * 参数三为一个函数指针,指向用户验证缓冲区时要执行的函数
 * \\param complete A pointer to the function to execute when the
 *                 user completes the buffer.
 * 参数四为一个函数指针,指向用户完成缓冲区时要执行的函数
 */

  再次查看 cmdline_new() 中的调用,来确定三个函数的位置。另外可以看到此函数中return语句调用了函数 cirbuf_init(),接下来将对这四个函数进行分析。

2.1.1 cmdline_write_char()

  其定义位于lib/librte_cmdline/cmdline.c

  此函数中,首先是根据 rdline 获取到其所属的 cmdline,随后进行检测,当输出流中存在内容时,使用 write 函数进行单个字符的写入。

2.1.2 cmdline_valid_buffer()

  其定义位于lib/librte_cmdline/cmdline.c

  此函数中,首先是根据 rdline 获取到其所属的 cmdline,随后使用命令行解析函数 cmdline_parse() (此篇笔记中不对此函数进行分析)对传入的字符串进行解析,根据解析的结果进行验证和提示。

2.1.3 cmdline_complete_buffer()

  其定义位于lib/librte_cmdline/cmdline.c

  此函数中,首先是根据 rdline 获取到其所属的 cmdline,随后使用缓冲区操作函数 cmdline_complete_buffer() (此篇笔记中不对此函数进行分析)对缓冲区进行处理。

2.1.4 cirbuf_init()

  其定义位于lib/librte_cmdline/cmdline_cirbuf.c

  此函数的作用是初始化循环缓冲区。此处函数里主要是对 &rdline->history 这部分初始化,包括大小、起始、缓冲区地址等。

2.2 cmdline_set_prompt()

  在执行此函数前,cmdline_new() 中先执行 cl->rdl.opaque = cl;,作用是将 cmdline cl 中含有的 rdline rdl 与其所属 cmdline cl 绑定。随后来看函数cmdline_set_prompt(),其定义位于lib/librte_cmdline/cmdline.c

  此处的传参二为命令行提示符,此处的主要作用就是将提示符设置进 cmdline 中。这里使用格式化写入函数 snprintf,将提示符写入 cmdline->prompt 部分。

2.3 rdline_newline()

  其定义位于lib/librte_cmdline/cmdline_rdline.c

  此函数里主要是对 rdline 中的元素进行初始化,包括左右缓冲区(rdline->left/right)和 rdline 对应的提示符相关内容等。
  这里值得一提的是 write_char 函数实际是 2.1.1 cmdline_write_char() 传入的函数,所以这里的实际操作是将输出流内的内容存入rdline->prompt。我思考了一下这部分的逻辑,觉得应该是为了让输出流中的提示符与 rdline 中的提示符相对应,也有可能是为了清除缓冲区。

  至此,初始化函数 cmdline_stdin_new() 结束,可以看到其中主要流程为:

  1. 对终端之前的设置进行保存,设置三个终端选项。
  2. cmdline 的初始化,设定输入输出流、命令组以及提示符等。
  3. rdline 的初始化,设定三个回调函数(写字符、检测、完成),对历史命令缓冲区、左右缓冲区进行初始化,以及对提示符的设定等。

三、交互函数 cmdline_interact() (略过)

  本篇笔记主要记录初始化和退出部分,解析部分暂且略过,在之后的笔记中记录分析。

四、退出函数 cmdline_stdin_exit()

  首选查看其定义,其定义位于lib/librte_cmdline/cmdline_socket.c

  其中函数 tcsetattr 的作用是设置linux下终端参数。此函数的参数一为终端的文件描述符;参数二为用于控制修改起作用的时间;参数三为结构体 struct termios,其中储存了输入输出模式等设置参数。具体含义如下:

  • 参数一为 fileno(stdin),其中 fileno() 的作用是获取传参流的文件描述符,此处传入输入流 stdin 并获取到其文件操作符。
  • 参数二为 TCSANOW,代表不等数据传输完毕就立即改变属性。
  • 参数三为 &c1->oldterm,为在调用 cmdline_stdin_new 创建 cmdline 时保存的未修改的终端设置,即交互式命令行创建之前的终端设置。

  所以,退出函数 cmdline_stdin_exit 的实际操作仅仅是恢复了创建交互式命令行之前的终端设置。

五、总结

  总的来说,dpdk中这部分源码非常严谨,参数、宏非常多。其中一些概念,比如说vt100,我也是搜了一下才知道是什么。
  源码中对 cmdlinerdline 结构体的定义,都可以说是非常的“庞大”,本篇笔记中记录的初始化和退出相关的内容,涉及到非常多的函数,但实际上都是为了初始化这两个结构体。发现了这一点之后,再汇总一下初始化的内容,整体就非常清晰了。
  其中一些对流的操作,我还是没有理解透彻,比如说 rdline_newline() 中为何要执行 rdl->write_char(),在接下来我应该会再去更深入的了解一下linux下流的内容,来理解源码中的意图和意义。当然,这系列中还剩最后也是最重要的一部分——命令解析,在下一篇中会进行分析。

以上是关于dpdk源码分析:交互式命令行的实现 初始化与退出的主要内容,如果未能解决你的问题,请参考以下文章

dpdk源码分析:交互式命令行的实现 初始化与退出

dpdk源码分析:交互式命令行的实现 命令添加

DPDK — Userspace PMD 源码分析

DPDK — Userspace PMD 源码分析

dpdk源码分析:cmdline中的命令添加

DPDK igb_uio驱动分析