dpdk-16.04 kni 示例程序分析

2023-10-22 23:59
文章标签 分析 程序 示例 dpdk 16.04 kni

本文主要是介绍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 选项,这些选项的含义如下:

  1. –config 用于指定 kni 使能接口与收发包 lcore 及 kni 线程绑定的 lcore

  2. -p 选项指定要使能的接口,跟一个十六进制参数,每一位表示一个接口

  3. -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)” 这种类型的字符串。解析过程如下:

  1. 依次处理每一个配置项目,一个成对的括号表示一个配置项目
  2. 调用 rte_strsplit 使用逗号切割每个 token 到数组中
  3. 依次遍历每个 token,调用 strtoul 将字符串转化为数字
  4. 获取第一个 token 的值,其值为 port_id 值,初始化 kni_port_params_array 中此 port_id 对应的项目
  5. 初始化 kni_port_params_array 数组并分配空间,当前项目 lcore_rx 为第二个 token 的值,lcore_tx 为第三个 token 的值,然后检查 lcore 是否合法,不合法则退出
  6. 将第四个及以后的 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 填充了下面几个项目:

  1. port_id

  2. lcore_rx

  3. lcore_tx

  4. nb_lcore_k

  5. lcore_k 数组

parse_args 解析参数完成后,会执行 validate_parameters 函数来校验参数是否合法,它验证了如下几种异常情况:

  1. 未指定 -p 参数导致 portmask 为 0
  2. –config 中指定的网卡的 port_id 未在 -p 参数指定的掩码中使能,这种情况会打印 portmask 与 --config 中指定的 port id 不一致的信息并终止程序
  3. 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 口初始化如下信息:

  1. KNI_INFO

  2. kni_tx ring

  3. kni_rx ring

  4. kni_alloc ring

  5. kni_free ring

  6. kni_req ring

  7. kni_resp ring

  8. 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 中按照执行逻辑可分为三种类型:

  1. LCORE_RX

  2. LCORE_TX

  3. 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_bursttx_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 示例程序分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/264409

相关文章

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

SpringBoot实现微信小程序支付功能

《SpringBoot实现微信小程序支付功能》小程序支付功能已成为众多应用的核心需求之一,本文主要介绍了SpringBoot实现微信小程序支付功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录一、引言二、准备工作(一)微信支付商户平台配置(二)Spring Boot项目搭建(三)配置文件

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

CSS will-change 属性示例详解

《CSSwill-change属性示例详解》will-change是一个CSS属性,用于告诉浏览器某个元素在未来可能会发生哪些变化,本文给大家介绍CSSwill-change属性详解,感... will-change 是一个 css 属性,用于告诉浏览器某个元素在未来可能会发生哪些变化。这可以帮助浏览器优化

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

前端高级CSS用法示例详解

《前端高级CSS用法示例详解》在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交互和动态效果的关键技术之一,随着前端技术的不断发展,CSS的用法也日益丰富和高级,本文将深... 前端高级css用法在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO