本文主要是介绍dpdk-16.04 kni 示例程序分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
dpdk-16.04 kni 示例程序分析
kni 程序在实现 dpdk 程序基础转发功能的同时,通过共享队列建立起了 dpdk 程序与内核协议栈通信的桥梁。dpdk 收到的包能够通过 kni 共享队列上送协议栈,同时协议栈通过 kni 虚拟网卡发出的包也会通过共享队列发到 pmd 中通过物理网卡发送出去,它代表了与 l2fwd 颇为不同的一种数据转发模型。
l2fwd 仅进行最简单的转发,不能对网卡接口进行控制,如 down、up、set mtu 等,kni 程序依赖 rte_kni.ko 模块,能够通过 ifconfig、ethtool 等标准的 linux 网络工具来控制、获取网卡接口的状态。
在本文中我将对其内部原理进行分析。
kni 程序初始化过程
843 int
844 main(int argc, char** argv)
845 {
846 int ret;
847 uint8_t nb_sys_ports, port;
848 unsigned i;
849
850 /* Associate signal_hanlder function with USR signals */
851 signal(SIGUSR1, signal_handler);
852 signal(SIGUSR2, signal_handler);
853 signal(SIGRTMIN, signal_handler);
854 signal(SIGINT, signal_handler);
851~854 行对 SIGUSR1、SIGUSR2、SIGRTMIN、SIGINT 注册了相同的信号处理函数 signal_handler,signal_handler 函数中根据信号的类型分发到不同的逻辑上。
signal_handler 函数代码如下:
198 /* Custom handling of signals to handle stats and kni processing */
199 static void
200 signal_handler(int signum)
201 {
202 /* When we receive a USR1 signal, print stats */
203 if (signum == SIGUSR1) {
204 print_stats();
205 }
206
207 /* When we receive a USR2 signal, reset stats */
208 if (signum == SIGUSR2) {
209 memset(&kni_stats, 0, sizeof(kni_stats));
210 printf("\n**Statistics have been reset**\n");
211 return;
212 }
213
214 /* When we receive a RTMIN or SIGINT signal, stop kni processing */
215 if (signum == SIGRTMIN || signum == SIGINT){
216 printf("SIGRTMIN is received, and the KNI processing is "
217 "going to stop\n");
218 rte_atomic32_inc(&kni_stop);
219 return;
220 }
221 }
当收到 SIGUSR1 信号时,kni 程序会打印收发包统计信息,收到 SIGUSR2 后,kni 程序会清空收发包统计信息,当收到 SIGRTMIN、SIGINT 信号时,kni 会原子递增 kni_stop 变量的值。
kni_main_loop 函数会在数据转发的间隙读取 kni_stop 的值,当值大于 0 时收发包线程退出,进而触发程序主动退出。
kni 程序 eal 环境初始化,内部参数解析
856 /* Initialise EAL */
857 ret = rte_eal_init(argc, argv);
858 if (ret < 0)
859 rte_exit(EXIT_FAILURE, "Could not initialise EAL (%d)\n", ret);
860 argc -= ret;
861 argv += ret;
862
863 /* Parse application arguments (after the EAL ones) */
864 ret = parse_args(argc, argv);
865 if (ret < 0)
866 rte_exit(EXIT_FAILURE, "Could not parse input parameters\n");
rte_eal_init 初始化 eal 环境,它会解析 dpdk 内部的参数。860~861 调整 argc 与 argv 的值,为 kni 程序自身参数的 parse 做准备。
864 行调用 parse_args 解析 kni 内部选项,kni 内部定义了三个选项,–config,-p、-P 选项,这些选项的含义如下:
-
–config 用于指定 kni 使能接口与收发包 lcore 及 kni 线程绑定的 lcore
-
-p 选项指定要使能的接口,跟一个十六进制参数,每一位表示一个接口
-
-P 选项设定是否开启混淆模式,设定此参数后,混淆模式会开启
parse_args 函数
534 #define CMDLINE_OPT_CONFIG "config"
535
536 /* Parse the arguments given in the command line of the application */
537 static int
538 parse_args(int argc, char **argv)
539 {
540 int opt, longindex, ret = 0;
541 const char *prgname = argv[0];
542 static struct option longopts[] = {
543 {CMDLINE_OPT_CONFIG, required_argument, NULL, 0},
544 {NULL, 0, NULL, 0}
545 };
546
547 /* Disable printing messages within getopt() */
548 opterr = 0;
549
550 /* Parse command line */
551 while ((opt = getopt_long(argc, argv, "p:P", longopts,
552 &longindex)) != EOF) {
553 switch (opt) {
554 case 'p':
555 ports_mask = parse_unsigned(optarg);
556 break;
557 case 'P':
558 promiscuous_on = 1;
559 break;
560 case 0:
561 if (!strncmp(longopts[longindex].name,
562 CMDLINE_OPT_CONFIG,
563 sizeof(CMDLINE_OPT_CONFIG))) {
564 ret = parse_config(optarg);
565 if (ret) {
566 printf("Invalid config\n");
567 print_usage(prgname);
568 return -1;
569 }
570 }
571 break;
572 default:
573 print_usage(prgname);
574 rte_exit(EXIT_FAILURE, "Invalid option specified\n");
575 }
576 }
577
578 /* Check that options were parsed ok */
579 if (validate_parameters(ports_mask) < 0) {
580 print_usage(prgname);
581 rte_exit(EXIT_FAILURE, "Invalid parameters\n");
582 }
583
584 return ret;
585 }
542 行定义了 config 长参数,551 行定义了 -p 与 -P 这两个短选项,选项解析调用 getopt_long 函数完成,通过 switch 语句分发到子流程的处理中。
parse_unsigned 函数中调用 strtoul 来解析 -p 选项的参数,格式为十六进制,与 l2fwd 中解析 -p 参数的过程相同。
parse_config 过程
parse_config 用于解析 kni 端口配置信息,诸如 “(0,0,1,1),(0,2,3,2)” 这种类型的字符串。解析过程如下:
- 依次处理每一个配置项目,一个成对的括号表示一个配置项目
- 调用 rte_strsplit 使用逗号切割每个 token 到数组中
- 依次遍历每个 token,调用 strtoul 将字符串转化为数字
- 获取第一个 token 的值,其值为 port_id 值,初始化 kni_port_params_array 中此 port_id 对应的项目
- 初始化 kni_port_params_array 数组并分配空间,当前项目 lcore_rx 为第二个 token 的值,lcore_tx 为第三个 token 的值,然后检查 lcore 是否合法,不合法则退出
- 将第四个及以后的 token 解析的值,依次填充到当前接口占用的 kni_port_params_array 数组项的 lcore_k 数组中
当所有的 config 解析完成后,parse_config 会调用 print_config 打印 config 信息。parse_config 代码如下:
408 static int
409 parse_config(const char *arg)
410 {
411 const char *p, *p0 = arg;
412 char s[256], *end;
413 unsigned size;
414 enum fieldnames {
415 FLD_PORT = 0,
416 FLD_LCORE_RX,
417 FLD_LCORE_TX,
418 _NUM_FLD = KNI_MAX_KTHREAD + 3,
419 };
420 int i, j, nb_token;
421 char *str_fld[_NUM_FLD];
422 unsigned long int_fld[_NUM_FLD];
423 uint8_t port_id, nb_kni_port_params = 0;
424
425 memset(&kni_port_params_array, 0, sizeof(kni_port_params_array));
426 while (((p = strchr(p0, '(')) != NULL) &&
427 nb_kni_port_params < RTE_MAX_ETHPORTS) {
428 p++;
429 if ((p0 = strchr(p, ')')) == NULL)
430 goto fail;
431 size = p0 - p;
432 if (size >= sizeof(s)) {
433 printf("Invalid config parameters\n");
434 goto fail;
435 }
436 snprintf(s, sizeof(s), "%.*s", size, p);
437 nb_token = rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',');
438 if (nb_token <= FLD_LCORE_TX) {
439 printf("Invalid config parameters\n");
440 goto fail;
441 }
442 for (i = 0; i < nb_token; i++) {
443 errno = 0;444 int_fld[i] = strtoul(str_fld[i], &end, 0);
445 if (errno != 0 || end == str_fld[i]) {
446 printf("Invalid config parameters\n");
447 goto fail;
448 }
449 }
450
451 i = 0;
452 port_id = (uint8_t)int_fld[i++];
453 if (port_id >= RTE_MAX_ETHPORTS) {
454 printf("Port ID %d could not exceed the maximum %d\n",
455 port_id, RTE_MAX_ETHPORTS);
456 goto fail;
457 }
458 if (kni_port_params_array[port_id]) {
459 printf("Port %d has been configured\n", port_id);
460 goto fail;
461 }
462 kni_port_params_array[port_id] =
463 rte_zmalloc("KNI_port_params",
464 sizeof(struct kni_port_params), RTE_CACHE_LINE_SIZE);
465 kni_port_params_array[port_id]->port_id = port_id;
466 kni_port_params_array[port_id]->lcore_rx =
467 (uint8_t)int_fld[i++];
468 kni_port_params_array[port_id]->lcore_tx =
469 (uint8_t)int_fld[i++];
470 if (kni_port_params_array[port_id]->lcore_rx >= RTE_MAX_LCORE ||
471 kni_port_params_array[port_id]->lcore_tx >= RTE_MAX_LCORE) {
472 printf("lcore_rx %u or lcore_tx %u ID could not "
473 "exceed the maximum %u\n",
474 kni_port_params_array[port_id]->lcore_rx,
475 kni_port_params_array[port_id]->lcore_tx,
476 (unsigned)RTE_MAX_LCORE);
477 goto fail;
478 }
479 for (j = 0; i < nb_token && j < KNI_MAX_KTHREAD; i++, j++)
480 kni_port_params_array[port_id]->lcore_k[j] =
481 (uint8_t)int_fld[i];
482 kni_port_params_array[port_id]->nb_lcore_k = j;
483 }
484 print_config();
485
486 return 0;
487
488 fail:
489 for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
490 if (kni_port_params_array[i]) {
491 rte_free(kni_port_params_array[i]);
492 kni_port_params_array[i] = NULL;
493 }
494 }
495
496 return -1;
497 }
在继续叙述前,先来看看 kni_port_params 接口体的定义:
110 #define KNI_MAX_KTHREAD 32
111 /*
112 * Structure of port parameters
113 */
114 struct kni_port_params {
115 uint8_t port_id;/* Port ID */
116 unsigned lcore_rx; /* lcore ID for RX */
117 unsigned lcore_tx; /* lcore ID for TX */
118 uint32_t nb_lcore_k; /* Number of lcores for KNI multi kernel threads */
119 uint32_t nb_kni; /* Number of KNI devices to be created */
120 unsigned lcore_k[KNI_MAX_KTHREAD]; /* lcore ID list for kthreads */
121 struct rte_kni *kni[KNI_MAX_KTHREAD]; /* KNI context pointers */
122 } __rte_cache_aligned;
123
124 static struct kni_port_params *kni_port_params_array[RTE_MAX_ETHPORTS];
kni_port_params_array 数组中保存每个接口的 kni_port_params 结构体指针,在 parse_config 中会为每一个配置接口创建 kni_port_params 结构体,并将解析到的结果填充到 kni_prot_params 中,parse_config 填充了下面几个项目:
-
port_id
-
lcore_rx
-
lcore_tx
-
nb_lcore_k
-
lcore_k 数组
parse_args 解析参数完成后,会执行 validate_parameters 函数来校验参数是否合法,它验证了如下几种异常情况:
- 未指定 -p 参数导致 portmask 为 0
- –config 中指定的网卡的 port_id 未在 -p 参数指定的掩码中使能,这种情况会打印 portmask 与 --config 中指定的 port id 不一致的信息并终止程序
- kni_port_params_array 数组中使能端口配置的 rx lcore 与 tx lcore 是否使能,未使能则打印信息并终止程序
配置校验完成后,继续执行后续初始化过程。
dpdk 物理接口初始化与 kni 网络接口初始化
868 /* Create the mbuf pool */
869 pktmbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", NB_MBUF,
870 MEMPOOL_CACHE_SZ, 0, MBUF_DATA_SZ, rte_socket_id());
871 if (pktmbuf_pool == NULL) {
872 rte_exit(EXIT_FAILURE, "Could not initialise mbuf pool\n");
873 return -1;
874 }
875
876 /* Get number of ports found in scan */
877 nb_sys_ports = rte_eth_dev_count();
878 if (nb_sys_ports == 0)
879 rte_exit(EXIT_FAILURE, "No supported Ethernet device found\n");
880
881 /* Check if the configured port ID is valid */
882 for (i = 0; i < RTE_MAX_ETHPORTS; i++)
883 if (kni_port_params_array[i] && i >= nb_sys_ports)
884 rte_exit(EXIT_FAILURE, "Configured invalid "
885 "port ID %u\n", i);
886
887 /* Initialize KNI subsystem */
888 init_kni();
889
890 /* Initialise each port */
891 for (port = 0; port < nb_sys_ports; port++) {
892 /* Skip ports that are not enabled */
893 if (!(ports_mask & (1 << port)))
894 continue;
895 init_port(port);
896
897 if (port >= RTE_MAX_ETHPORTS)
898 rte_exit(EXIT_FAILURE, "Can not use more than "
899 "%d ports for kni\n", RTE_MAX_ETHPORTS);
900
901 kni_alloc(port);
902 }
869 行创建 pktmbuf 内存池,创建失败后则终止程序。877 ~885 行首先获取程序可用的接口数目,并检查 kni_prot_params_array 的每一个配置项目配置的接口数目是否合法,不合法则程序终止。
888 行调用 init_kni 函数,初始化每个 kni 接口需要使用的内部收发包队列等内容,kni 接口数目通过遍历 kni_port_params_array 数组,累加每一个配置端口的 nb_lcore_k 的值获取。
init_kni 函数代码如下:
588 static void
589 init_kni(void)
590 {
591 unsigned int num_of_kni_ports = 0, i;
592 struct kni_port_params **params = kni_port_params_array;
593
594 /* Calculate the maximum number of KNI interfaces that will be used */
595 for (i = 0; i < RTE_MAX_ETHPORTS; i++) {
596 if (kni_port_params_array[i]) {
597 num_of_kni_ports += (params[i]->nb_lcore_k ?
598 params[i]->nb_lcore_k : 1);
599 }
600 }
601
602 /* Invoke rte KNI init to preallocate the ports */
603 rte_kni_init(num_of_kni_ports);
604 }
rte_kni_init 函数中为每一个 kni 口初始化如下信息:
-
KNI_INFO
-
kni_tx ring
-
kni_rx ring
-
kni_alloc ring
-
kni_free ring
-
kni_req ring
-
kni_resp ring
-
kni_sync ring
初始化物理接口并创建 kni 口
891 for (port = 0; port < nb_sys_ports; port++) {
892 /* Skip ports that are not enabled */
893 if (!(ports_mask & (1 << port)))
894 continue;
895 init_port(port);
896
897 if (port >= RTE_MAX_ETHPORTS)
898 rte_exit(EXIT_FAILURE, "Can not use more than "
899 "%d ports for kni\n", RTE_MAX_ETHPORTS);
900
901 kni_alloc(port);
902 }
891~902 行初始化每个使能的 dpdk 接口,并创建 kni 口。·初始化 dpdk 接口通过调用 init_port 函数完成,此函数执行的过程与 l2fwd 中初始化物理网卡过程一致,首先调用 rte_eth_dev_configure 配置接口使用单队列并设置 port_conf,然后设置 tx_queue 与 rx_queue,完成后调用 rte_eth_dev_start up 接口,最后判断是否使能混淆模式,使能则调用 rte_eth_promiscuous_enable 开启。
kni_alloc 函数创建 kni 虚拟接口,其源码如下:
765 static int
766 kni_alloc(uint8_t port_id)
767 {
768 uint8_t i;
769 struct rte_kni *kni;
770 struct rte_kni_conf conf;
771 struct kni_port_params **params = kni_port_params_array;
772
773 if (port_id >= RTE_MAX_ETHPORTS || !params[port_id])
774 return -1;
775
776 params[port_id]->nb_kni = params[port_id]->nb_lcore_k ?
777 params[port_id]->nb_lcore_k : 1;
778
779 for (i = 0; i < params[port_id]->nb_kni; i++) {
780 /* Clear conf at first */
781 memset(&conf, 0, sizeof(conf));
782 if (params[port_id]->nb_lcore_k) {
783 snprintf(conf.name, RTE_KNI_NAMESIZE,
784 "vEth%u_%u", port_id, i);
785 conf.core_id = params[port_id]->lcore_k[i];
786 conf.force_bind = 1;
787 } else
788 snprintf(conf.name, RTE_KNI_NAMESIZE,
789 "vEth%u", port_id);
790 conf.group_id = (uint16_t)port_id;
791 conf.mbuf_size = MAX_PACKET_SZ;
792 /*
793 * The first KNI device associated to a port
794 * is the master, for multiple kernel thread
795 * environment.
796 */
797 if (i == 0) {
798 struct rte_kni_ops ops;
799 struct rte_eth_dev_info dev_info;
800
801 memset(&dev_info, 0, sizeof(dev_info));
802 rte_eth_dev_info_get(port_id, &dev_info);
803 conf.addr = dev_info.pci_dev->addr;
804 conf.id = dev_info.pci_dev->id;
805
806 memset(&ops, 0, sizeof(ops));
807 ops.port_id = port_id;
808 ops.change_mtu = kni_change_mtu;
809 ops.config_network_if = kni_config_network_interface;
810
811 kni = rte_kni_alloc(pktmbuf_pool, &conf, &ops);
812 } else
813 kni = rte_kni_alloc(pktmbuf_pool, &conf, NULL);
814
815 if (!kni)
816 rte_exit(EXIT_FAILURE, "Fail to create kni for "
817 "port: %d\n", port_id);
818 params[port_id]->kni[i] = kni;
819 }
820
821 return 0;
822 }
771~778 行获取当前 port 占用的 kni_port_params 结构体,使用 kni_port_params 的 nb_lcore_k 字段初始化 nb_kni 字段。
779~819 行填充 rte_kni_conf 结构体中的字段,此结构体中,name 表示 netdev 的名称,group_id 设置为 port_id。
797~813 行创建 kni 口,并注册 rte_kni_ops 的回调,此回调函数只在每一个 port 的第一个 kni 设备上注册。rte_kni_ops 结构体定义如下:
struct rte_kni_ops {uint8_t port_id; /* Port ID *//* Pointer to function of changing MTU */int (*change_mtu)(uint8_t port_id, unsigned new_mtu);/* Pointer to function of configuring network interface */int (*config_network_if)(uint8_t port_id, uint8_t if_up);
};
change_mtu 是修改接口 mtu 时 pmd 的回调函数,config_network_if 是执行 ifconfig kni_xxx 时 pmd 的回调函数。
这里注册的回调函数在收发包间隙通过执行 rte_kni_handle_request 函数来调用。rte_kni_handle_request 函数首先从当前 kni 接口的 req_q 请求队列中获取一个请求,解析请求报文的 req_id,通过 req_id 调用不同的 kni 回调函数,并将结果填充到 req->result 中。
相关代码如下:
switch (req->req_id) {case RTE_KNI_REQ_CHANGE_MTU: /* Change MTU */if (kni->ops.change_mtu)req->result = kni->ops.change_mtu(kni->ops.port_id,req->new_mtu);break;case RTE_KNI_REQ_CFG_NETWORK_IF: /* Set network interface up/down */if (kni->ops.config_network_if)req->result = kni->ops.config_network_if(\kni->ops.port_id, req->if_up);break;
回调函数调用完成后,req 请求会被投递到 resq_q 回复队列中,kni 内核线程会从 resq_q 中获取到 resq_q 内容,rte_kni_handle_requeset 函数中执行如下调用来将 req 请求投递到 resq_q 队列中:
ret = kni_fifo_put(kni->resp_q, (void **)&req, 1);
检查接口 link 状态,在 lcore 的线程上运行 main_loop 函数
903 check_all_ports_link_status(nb_sys_ports, ports_mask);
904
905 /* Launch per-lcore function on every lcore */
906 rte_eal_mp_remote_launch(main_loop, NULL, CALL_MASTER);
907 RTE_LCORE_FOREACH_SLAVE(i) {
908 if (rte_eal_wait_lcore(i) < 0)
909 return -1;
910 }
911
903 行函数调用检查接口 link 状态的逻辑,check_all_ports_link_status 会在 9s 内不断调用 rte_eth_link_get_nowait 获取每一个接口的 link 状态,当所有使能接口都 up、timeout 时,函数会设置 print_flag 变量为 1,打印接口状态信息后返回。
906 行在所有的 lcore 上运行 main_loop,main_loop 中按照执行逻辑可分为三种类型:
-
LCORE_RX
-
LCORE_TX
-
LCORE_NONE
其代码如下:
307 static int
308 main_loop(__rte_unused void *arg)
309 {
310 uint8_t i, nb_ports = rte_eth_dev_count();
311 int32_t f_stop;
312 const unsigned lcore_id = rte_lcore_id();
313 enum lcore_rxtx {
314 LCORE_NONE,
315 LCORE_RX,
316 LCORE_TX,
317 LCORE_MAX
318 };
319 enum lcore_rxtx flag = LCORE_NONE;
320
321 nb_ports = (uint8_t)(nb_ports < RTE_MAX_ETHPORTS ?
322 nb_ports : RTE_MAX_ETHPORTS);
323 for (i = 0; i < nb_ports; i++) {
324 if (!kni_port_params_array[i])
325 continue;
326 if (kni_port_params_array[i]->lcore_rx == (uint8_t)lcore_id) {
327 flag = LCORE_RX;
328 break;
329 } else if (kni_port_params_array[i]->lcore_tx ==
330 (uint8_t)lcore_id) {
331 flag = LCORE_TX;
332 break;
333 }
334 }
335
336 if (flag == LCORE_RX) {
337 RTE_LOG(INFO, APP, "Lcore %u is reading from port %d\n",
338 kni_port_params_array[i]->lcore_rx,
339 kni_port_params_array[i]->port_id);
340 while (1) {
341 f_stop = rte_atomic32_read(&kni_stop);
342 if (f_stop)
343 break;
344 kni_ingress(kni_port_params_array[i]);
345 }
346 } else if (flag == LCORE_TX) {
347 RTE_LOG(INFO, APP, "Lcore %u is writing to port %d\n",
348 kni_port_params_array[i]->lcore_tx,
349 kni_port_params_array[i]->port_id);
350 while (1) {
351 f_stop = rte_atomic32_read(&kni_stop);
352 if (f_stop)
353 break;
354 kni_egress(kni_port_params_array[i]);
355 }
356 } else
357 RTE_LOG(INFO, APP, "Lcore %u has nothing to do\n", lcore_id);
358
359 return 0;
360 }
323~334 通过遍历 kni_port_params_array 数组设置 flag,flag 代表了当前 lcore 的执行内容,为 LCORE_RX 时循环调用 kni_ingress 函数,为 LCORE_TX 时循环调用 kni_egress 函数,在调用间隙会不断读取 kni_stop 的值,当其值大于 0 时退出循环。
kni_ingress 函数与 kni_egress 函数的执行过程中 mbuf 的流动见下图:
kni_ingress 函数原理
kni_ingress 首先从网卡收包,收包函数从 pktmbuf pool 中申请 mbuf,收到报文后调用 rte_kni_tx_burst 将报文投递到 rx_q 中上送协议栈,rte_kni 收包函数从 rx_q 中获取报文,创建 sk_buff 并使用 mbuf 内容填充 ,之后调用 netif_rx_ni 将 sk_buf 投递给协议栈处理。
这时 mbuf 需要释放,rte_kni 的收包函数会将 mbuf 投递到 free_q 队列中,kni 程序中通过kni_free_mbufs 函数从 free_q 中获取 mbuf 并调用 rte_pktmbuf_free 来释放。
kni_ingress 函数会在报文上送协议栈后调用 rte_kni_handle_request 从 req_q 队列中获取请求报文,解析报文后调用不同的回调函数并填充回调函数执行的结果到请求报文中,最后将报文投递到 resp_q 队列中,kni kthread 从 resq_q 队列中获取报文就得到了调用的结果。
kni_egress 函数原理
rte_kni 模块调用 kni_net_tx 来投递报文到 tx_q 队列中,kni_net_tx 函数首先从 alloc_q 中获取 mbuf,使用 sk_buff 来填充 mbuf,填充完成后将 mbuf 投递到 tx_q 队列中,投递完成后释放 sk_buff。
kni_egress 函数首先调用 rte_kni_rx_burst 从 tx_q 队列中获取报文,获取成功后会申请新的 mbuf 填充到 alloc_q 中。此后 kni_egress 调用 rte_eth_tx_burst 将报文通过物理网卡发送出去并 free 掉没有成功发送的报文。
kni 程序退出逻辑
907 RTE_LCORE_FOREACH_SLAVE(i) {
908 if (rte_eal_wait_lcore(i) < 0)
909 return -1;
910 }
911
912 /* Release resources */
913 for (port = 0; port < nb_sys_ports; port++) {
914 if (!(ports_mask & (1 << port)))
915 continue;
916 kni_free_kni(port);
917 }
918 #ifdef RTE_LIBRTE_XEN_DOM0
919 rte_kni_close();
920 #endif
921 for (i = 0; i < RTE_MAX_ETHPORTS; i++)
922 if (kni_port_params_array[i]) {
923 rte_free(kni_port_params_array[i]);
924 kni_port_params_array[i] = NULL;
925 }
926
927 return 0;
907~910 行等待每个 lcore 终止,912~917 行释放 kni ,918~920 行关闭 kni 设备。921~925 行释放 kni_port_params_array 数组,此后,kni 程序正常终止。
这篇关于dpdk-16.04 kni 示例程序分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!