DPDK分析——KNI

DPDK KNI原理和实现 分配struct net_device结构使用kni_net_init进行初始化,并将struct kni_dev作为net_device的private,并使用入参初始化net_device,入参我们从DPDK用户态部分知道为struct rte_kni_device_info,而其又是存放kni设备对应的rte_kni_memzone_slot信息。而kni_net_rx又进一步调用kni_net_rx_func,kni_net_rx_func被初始化为kni_net_rx_normal。 阅读详情

目录

一、概述

二、KNI

2.1 应用场景

2.2 KNI内核模块

2.3 用户态使用

2.3.1 rte_kni_init

2.3.2 rte_kni_alloc

2.3.4 rte_kni_tx_burst

2.3.5 rte_kni_rx_burst

2.3.6 rte_kni_handle_request

三、参考


一、概述

项目中用到,分析一下实现记录一下。

二、KNI

2.1 应用场景

KNI(Kernel NIC Interface)是DPDK为用户态和内核协议栈交互提供的一种机制。本质上就是在内核注册一个虚拟网口,使用队列机制和用户态进行报文交换,和其他虚拟口的实现差不多。

可以用下图描述kni的作用:

kni的基本使用场景如上,在DPDK中,当物理口收到报文但是用户态无法处理时,将报文转换写入队列,以此模拟KNI口收到了报文,报文经过协议栈处理后发出,相当于写入KNI和物理口的发送队列,

2.2 KNI内核模块

KNI实现需要内核支持,本质上就是注册一个misc设备,通过用户ioctl命令注册netdevice结构。

[kernel/linux/kni/kni_misc.c]

static int __init kni_init(void)
{
	int rc;

	if (kni_parse_kthread_mode() < 0) {
		pr_err("Invalid parameter for kthread_mode\n");
		return -EINVAL;
	}

	if (multiple_kthread_on == 0)
		pr_debug("Single kernel thread for all KNI devices\n");
	else
		pr_debug("Multiple kernel thread mode enabled\n");

	if (kni_parse_carrier_state() < 0) {
		pr_err("Invalid parameter for carrier\n");
		return -EINVAL;
	}

   ...

     rc = register_pernet_subsys(&kni_net_ops);


	rc = misc_register(&kni_misc);
	if (rc != 0) {
		pr_err("Misc registration failed\n");
		goto out;
	}

	/* Configure the lo mode according to the input parameter */
	kni_net_config_lo_mode(lo_mode);

	return 0;

    ...
}

首先是检测模块参数(蓝色部分是默认值):

  • lo_mode                lo_mode_none /lo_mode_fifo/lo_mode_fifo_skb
  • kthread_mode      single /  multiple (在multiple mode下,每个port可以有多个kni口,每个kni口对应一个kni thread)
  • carrier                   off / on

具体懒的展开了,模块参数注释写的很详细。

struct kni_net {
	unsigned long device_in_use; /* device in use flag */
	struct mutex kni_kthread_lock;
	struct task_struct *kni_kthread;
	struct rw_semaphore kni_list_lock;
	struct list_head kni_list_head;
};

misc register以前的文章也说过,就是一个字符设备框架,具体看下方法:

static struct miscdevice kni_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = KNI_DEVICE,           /* #define KNI_DEVICE "kni" */
	.fops = &kni_fops,
};

static const struct file_operations kni_fops = {
	.owner = THIS_MODULE,
	.open = kni_open,
	.release = kni_release,
	.unlocked_ioctl = (void *)kni_ioctl,
	.compat_ioctl = (void *)kni_compat_ioctl,
};

具体功能用户态调用再展开,这里知道misc_register后,系统为我们建立的/dev/kni就行了

2.3 用户态使用

2.3.1 rte_kni_init

int rte_kni_init(unsigned int max_kni_ifaces __rte_unused)
{
	/* Check FD and open */
	if (kni_fd < 0) {
		kni_fd = open("/dev/" KNI_DEVICE, O_RDWR);
		if (kni_fd < 0) {
			RTE_LOG(ERR, KNI,
				"Can not open /dev/%s\n", KNI_DEVICE);
			return -1;
		}
	}

	return 0;
}

就是打开了字符文件/dev/kni,看下内核的动作:

static int kni_open(struct inode *inode, struct file *file)
{
	struct net *net = current->nsproxy->net_ns;
	struct kni_net *knet = net_generic(net, kni_net_id);

	/* kni device can be opened by one user only per netns */
	if (test_and_set_bit(KNI_DEV_IN_USE_BIT_NUM, &knet->device_in_use))
		return -EBUSY;

	file->private_data = get_net(net);
	pr_debug("/dev/kni opened\n");

	return 0;
}

2.3.2 rte_kni_alloc

struct rte_kni * rte_kni_alloc(struct rte_mempool *pktmbuf_pool,
	      const struct rte_kni_conf *conf, struct rte_kni_ops *ops)
{
    ...
	kni = __rte_kni_get(conf->name);
	if (kni != NULL) {
		RTE_LOG(ERR, KNI, "KNI already exists\n");
		goto unlock;
	}

	te = rte_zmalloc("KNI_TAILQ_ENTRY", sizeof(*te), 0);
	if (te == NULL) {
		RTE_LOG(ERR, KNI, "Failed to allocate tailq entry\n");
		goto unlock;
	}

	kni = rte_zmalloc("KNI", sizeof(struct rte_kni), RTE_CACHE_LINE_SIZE);
	if (kni == NULL) {
		RTE_LOG(ERR, KNI, "KNI memory allocation failed\n");
		goto kni_fail;
	}

	snprintf(kni->name, RTE_KNI_NAMESIZE, "%s", conf->name);

	if (ops)
		memcpy(&kni->ops, ops, sizeof(struct rte_kni_ops));
	else
		kni->ops.port_id = UINT16_MAX;
    ...
}

分配每个kni口对应的结构,并管理起来:

[lib/librte_kni/rte_kni.c]

struct rte_kni {
	char name[RTE_KNI_NAMESIZE];        /**< KNI interface name */
	uint16_t group_id;                  /**< Group ID of KNI devices */
	uint32_t slot_id;                   /**< KNI pool slot ID */
	struct rte_mempool *pktmbuf_pool;   /**< pkt mbuf mempool */
	unsigned mbuf_size;                 /**< mbuf size */

	const struct rte_memzone *m_tx_q;   /**< TX queue memzone */
	const struct rte_memzone *m_rx_q;   /**< RX queue memzone */
	const struct rte_memzone *m_alloc_q;/**< Alloc queue memzone */
	const struct rte_memzone *m_free_q; /**< Free queue memzone */

	struct rte_kni_fifo *tx_q;          /**< TX queue */
	struct rte_kni_fifo *rx_q;          /**< RX queue */
	struct rte_kni_fifo *alloc_q;       /**< Allocated mbufs queue */
	struct rte_kni_fifo *free_q;        /**< To be freed mbufs queue */

	const struct rte_memzone *m_req_q;  /**< Request queue memzone */
	const struct rte_memzone *m_resp_q; /**< Response queue memzone */
	const struct rte_memzone *m_sync_addr;/**< Sync addr memzone */

	/* For request & response */
	struct rte_kni_fifo *req_q;         /**< Request queue */
	struct rte_kni_fifo *resp_q;        /**< Response queue */
	void * sync_addr;                   /**< Req/Resp Mem address */

	struct rte_kni_ops ops;             /**< operations for request */
};

接着根据rte_kni_conf信息配置rte_kni_device_info:

	memset(&dev_info, 0, sizeof(dev_info));
	dev_info.bus = conf->addr.bus;
	dev_info.devid = conf->addr.devid;
	dev_info.function = conf->addr.function;
	dev_info.vendor_id = conf->id.vendor_id;
	dev_info.device_id = conf->id.device_id;
	dev_info.core_id = conf->core_id;
	dev_info.force_bind = conf->force_bind;
	dev_info.group_id = conf->group_id;
	dev_info.mbuf_size = conf->mbuf_size;
	dev_info.mtu = conf->mtu;

	memcpy(dev_info.mac_addr, conf->mac_addr, ETHER_ADDR_LEN);

	snprintf(dev_info.name, RTE_KNI_NAMESIZE, "%s", conf->name);

这里顺便把初始化的参数也列一下:

struct rte_kni_conf {
	/*
	 * KNI name which will be used in relevant network device.
	 * Let the name as short as possible, as it will be part of
	 * memzone name.
	 */
	char name[RTE_KNI_NAMESIZE];
	uint32_t core_id;   /* Core ID to bind kernel thread on */
	uint16_t group_id;  /* Group ID */
	unsigned mbuf_size; /* mbuf size */
	struct rte_pci_addr addr;
	struct rte_pci_id id;

	__extension__
	uint8_t force_bind : 1; /* Flag to bind kernel thread */
	char mac_addr[ETHER_ADDR_LEN]; /* MAC address assigned to KNI */
	uint16_t mtu;
};

接下来会分配队列内存资源,注释写的很明白了,就不多贴代码了,主要包括:

tx_q/rx_q/alloc_q/free_q/req_q/resp_q,同时填充alloc_q

	/* Allocate mbufs and then put them into alloc_q */
	kni_allocate_mbufs(kni);

在继续向下之前分析一下用户态与内核态报文交互的方式,来看DPDK用户手册的一张图:

从ingress方向看,rte_eth_rx_burst时mbuf mempool中分配内存,通过rx_q发送给KNI,KNI线程将mbuf从rx_q中出队,将其转换成skb后,就将原mbuf通过free_q归还给用户rx thread,并由rx_thread释放,可以看出ingress方向的mbuf完全由用户态rx_thread自行管理。

从egress方向看,当KNI口发包时,从 mbuf cache中取得一个mbuf,将skb内容copy到mbuf中,进入tx_q队列,tx_thread将该mbuf出队并完成发送,因为发送后该mbuf会被释放。所以要重新alloc一个mbuf通过alloc_q归还给kernel。这部分mbuf是上面用户态填充的alloc_q。

kni的资源初始化完成后使用IOCTL创建内核对应的虚拟网口:

ioctl(kni_fd, RTE_KNI_IOCTL_CREATE, &dev_info);

驱动执行kni_ioctl_create,我们分几块来看,在进行一些必要的检查后,分配注册netdev:

static int kni_ioctl_create(struct net *net, uint32_t ioctl_num, unsigned long ioctl_param)
{
    net_dev = alloc_netdev(sizeof(struct kni_dev), dev_info.name,
    #ifdef NET_NAME_USER
							NET_NAME_USER,
    #endif
							kni_net_init);
    	dev_net_set(net_dev, net);

	kni = netdev_priv(net_dev);

	kni->net_dev = net_dev;
	kni->group_id = dev_info.group_id;
	kni->core_id = dev_info.core_id;
	strncpy(kni->name, dev_info.name, RTE_KNI_NAMESIZE);

	/* Translate user space info into kernel space info */
	kni->tx_q = phys_to_virt(dev_info.tx_phys);
	kni->rx_q = phys_to_virt(dev_info.rx_phys);
	kni->alloc_q = phys_to_virt(dev_info.alloc_phys);
	kni->free_q = phys_to_virt(dev_info.free_phys);

	kni->req_q = phys_to_virt(dev_info.req_phys);
	kni->resp_q = phys_to_virt(dev_info.resp_phys);
	kni->sync_va = dev_info.sync_va;
	kni->sync_kva = phys_to_virt(dev_info.sync_phys);

	kni->mbuf_size = dev_info.mbuf_size;
    ...
    ret = register_netdev(net_dev);
}

这里首先重点关注的还是那些资源的管理,因为这些地址都位于内核线性映射,所以简单的转换一下就好了。

其次是dev_net_set,这个函数指定了netdev->ops:

void kni_net_init(struct net_device *dev)
{
	struct kni_dev *kni = netdev_priv(dev);

	init_waitqueue_head(&kni->wq);
	mutex_init(&kni->sync_lock);

	ether_setup(dev); /* assign some of the fields */
	dev->netdev_ops      = &kni_net_netdev_ops;
	dev->header_ops      = &kni_net_header_ops;
	dev->watchdog_timeo = WD_TIMEOUT;
}

具体方法先贴一下:

static const struct net_device_ops kni_net_netdev_ops = {
	.ndo_open = kni_net_open,
	.ndo_stop = kni_net_release,
	.ndo_set_config = kni_net_config,
	.ndo_change_rx_flags = kni_net_set_promiscusity,
	.ndo_start_xmit = kni_net_tx,
	.ndo_change_mtu = kni_net_change_mtu,
	.ndo_do_ioctl = kni_net_ioctl,
	.ndo_set_rx_mode = kni_net_set_rx_mode,
	.ndo_get_stats = kni_net_stats,
	.ndo_tx_timeout = kni_net_tx_timeout,
	.ndo_set_mac_address = kni_net_set_mac,
#ifdef HAVE_CHANGE_CARRIER_CB
	.ndo_change_carrier = kni_net_change_carrier,
#endif
};

还有一点就是kni thread:

	netif_carrier_off(net_dev);

	ret = kni_run_thread(knet, kni, dev_info.force_bind);
	if (ret != 0)
		return ret;

	down_write(&knet->kni_list_lock);
	list_add(&kni->list, &knet->kni_list_head);
	up_write(&knet->kni_list_lock);

具体看kni_run_thread

static int
kni_run_thread(struct kni_net *knet, struct kni_dev *kni, uint8_t force_bind)
{
	/**
	 * Create a new kernel thread for multiple mode, set its core affinity,
	 * and finally wake it up.
	 */
	if (multiple_kthread_on) {
		kni->pthread = kthread_create(kni_thread_multiple,
			(void *)kni, "kni_%s", kni->name);
		if (IS_ERR(kni->pthread)) {
			kni_dev_remove(kni);
			return -ECANCELED;
		}

		if (force_bind)
			kthread_bind(kni->pthread, kni->core_id);
		wake_up_process(kni->pthread);
	} else {
		mutex_lock(&knet->kni_kthread_lock);

		if (knet->kni_kthread == NULL) {
			knet->kni_kthread = kthread_create(kni_thread_single,
				(void *)knet, "kni_single");
			if (IS_ERR(knet->kni_kthread)) {
				mutex_unlock(&knet->kni_kthread_lock);
				kni_dev_remove(kni);
				return -ECANCELED;
			}

			if (force_bind)
				kthread_bind(knet->kni_kthread, kni->core_id);
			wake_up_process(knet->kni_kthread);
		}

		mutex_unlock(&knet->kni_kthread_lock);
	}

	return 0;
}

kni thread的作用就是扫描队列,响应收包事件,先看 multiple模式:

static int kni_thread_multiple(void *param)
{
	int j;
	struct kni_dev *dev = param;

	while (!kthread_should_stop()) {
		for (j = 0; j < KNI_RX_LOOP_NUM; j++) {
			kni_net_rx(dev);
			kni_net_poll_resp(dev);
		}
#ifdef RTE_KNI_PREEMPT_DEFAULT
		schedule_timeout_interruptible(
			usecs_to_jiffies(KNI_KTHREAD_RESCHEDULE_INTERVAL));
#endif
	}

	return 0;
}

同时也贴一下single mode:

static int kni_thread_single(void *data)
{
	struct kni_net *knet = data;
	int j;
	struct kni_dev *dev;

	while (!kthread_should_stop()) {
		down_read(&knet->kni_list_lock);
		for (j = 0; j < KNI_RX_LOOP_NUM; j++) {
			list_for_each_entry(dev, &knet->kni_list_head, list) {
				kni_net_rx(dev);
				kni_net_poll_resp(dev);
			}
		}
		up_read(&knet->kni_list_lock);
#ifdef RTE_KNI_PREEMPT_DEFAULT
		/* reschedule out for a while */
		schedule_timeout_interruptible(
			usecs_to_jiffies(KNI_KTHREAD_RESCHEDULE_INTERVAL));
#endif
	}

	return 0;
}

从代码上可以看出,single mode下只有一个内核线程:knet->kni_thread,因此在扫描时会遍历knet->list_head中挂的所有kni口。mutiple mode下则不同,每一个kni 口对应一个thread:kni->pthread。

那么看一下具体的收报情况:

static kni_net_rx_t kni_net_rx_func = kni_net_rx_normal;
void kni_net_rx(struct kni_dev *kni)
{
	/**
	 * It doesn't need to check if it is NULL pointer,
	 * as it has a default value
	 */
	(*kni_net_rx_func)(kni);
}

对照上面的图来看,在收包时,kni_thread负责rx_q和free_q。首先要确定一下rx_q出队数量,由于mbuf转换skb完成后要及时通过free_q归还,所以要考虑一下free_q当前空闲量:

	/* Get the number of free entries in free_q */
	num_fq = kni_fifo_free_count(kni->free_q);
	if (num_fq == 0) {
		/* No room on the free_q, bail out */
		return;
	}

	/* Calculate the number of entries to dequeue from rx_q */
	num_rx = min_t(uint32_t, num_fq, MBUF_BURST_SZ);

	/* Burst dequeue from rx_q */
	num_rx = kni_fifo_get(kni->rx_q, kni->pa, num_rx);

接下来就是将取出的mbuf转换为skb:

	/* Transfer received packets to netif */
	for (i = 0; i < num_rx; i++) {
		kva = pa2kva(kni->pa[i]);
		len = kva->pkt_len;
		data_kva = kva2data_kva(kva);
		kni->va[i] = pa2va(kni->pa[i], kva);

		skb = dev_alloc_skb(len + 2);
		if (!skb) {
			/* Update statistics */
			kni->stats.rx_dropped++;
			continue;
		}

		/* Align IP on 16B boundary */
		skb_reserve(skb, 2);

		if (kva->nb_segs == 1) {
			memcpy(skb_put(skb, len), data_kva, len);
		} else {
			int nb_segs;
			int kva_nb_segs = kva->nb_segs;

			for (nb_segs = 0; nb_segs < kva_nb_segs; nb_segs++) {
				memcpy(skb_put(skb, kva->data_len),
					data_kva, kva->data_len);

				if (!kva->next)
					break;

				kva = pa2kva(va2pa(kva->next, kva));
				data_kva = kva2data_kva(kva);
			}
		}

		skb->dev = dev;
		skb->protocol = eth_type_trans(skb, dev);
		skb->ip_summed = CHECKSUM_UNNECESSARY;

		/* Call netif interface */
		netif_rx_ni(skb);

		/* Update statistics */
		kni->stats.rx_bytes += len;
		kni->stats.rx_packets++;
	}

转换完成后mbuf入free_q:

	/* Burst enqueue mbufs into free_q */
	ret = kni_fifo_put(kni->free_q, kni->va, num_rx);

该函数就是通过向/sys/devices/virtual/net/[kni_name]/carrier,写0/1控制kni netdev的link情况。一般link状况和对应的physical port同步就可以了。

当然使用ifconfig将端口拉起来也是可以的

static int kni_net_open(struct net_device *dev)
{
	int ret;
	struct rte_kni_request req;
	struct kni_dev *kni = netdev_priv(dev);

	netif_start_queue(dev);
	if (dflt_carrier == 1)
		netif_carrier_on(dev);
	else
		netif_carrier_off(dev);

	memset(&req, 0, sizeof(req));
	req.req_id = RTE_KNI_REQ_CFG_NETWORK_IF;

	/* Setting if_up to non-zero means up */
	req.if_up = 1;
	ret = kni_net_process_request(kni, &req);

	return (ret == 0) ? req.result : ret;
}

端口link状态up(netif_carrier_on)这里还是受到dflt_carrier的限制

2.3.4 rte_kni_tx_burst

unsigned rte_kni_tx_burst(struct rte_kni *kni, struct rte_mbuf **mbufs, unsigned num)
{
	void *phy_mbufs[num];
	unsigned int ret;
	unsigned int i;

	for (i = 0; i < num; i++)
		phy_mbufs[i] = va2pa(mbufs[i]);

	ret = kni_fifo_put(kni->rx_q, phy_mbufs, num);

	/* Get mbufs from free_q and then free them */
	kni_free_mbufs(kni);

	return ret;
}

ingress方向,mbuf是用户态分配的,这里注意二者交互的地址一定要是物理地址,这是因为1)内核态不知道用户态地址到物理地址的转换关系(可能是非线性)2)内核直接访问用户态空间在某些硬件条件下被禁止。稳妥起见,还是使用物理地址通过队列传递。

2.3.5 rte_kni_rx_burst

unsigned rte_kni_rx_burst(struct rte_kni *kni, struct rte_mbuf **mbufs, unsigned num)
{
	unsigned ret = kni_fifo_get(kni->tx_q, (void **)mbufs, num);

	/* If buffers removed, allocate mbufs and then put them into alloc_q */
	if (ret)
		kni_allocate_mbufs(kni);

	return ret;
}

同时看下内核发包:

static int kni_net_tx(struct sk_buff *skb, struct net_device *dev)
{
	int len = 0;
	uint32_t ret;
	struct kni_dev *kni = netdev_priv(dev);
	struct rte_kni_mbuf *pkt_kva = NULL;
	void *pkt_pa = NULL;
	void *pkt_va = NULL;

    ...
	/* dequeue a mbuf from alloc_q */
	ret = kni_fifo_get(kni->alloc_q, &pkt_pa, 1);
	if (likely(ret == 1)) {
		void *data_kva;

		pkt_kva = pa2kva(pkt_pa);
		data_kva = kva2data_kva(pkt_kva);
		pkt_va = pa2va(pkt_pa, pkt_kva);

		len = skb->len;
		memcpy(data_kva, skb->data, len);
		if (unlikely(len < ETH_ZLEN)) {
			memset(data_kva + len, 0, ETH_ZLEN - len);
			len = ETH_ZLEN;
		}
		pkt_kva->pkt_len = len;
		pkt_kva->data_len = len;

		/* enqueue mbuf into tx_q */
		ret = kni_fifo_put(kni->tx_q, &pkt_va, 1);
		if (unlikely(ret != 1)) {
			/* Failing should not happen */
			pr_err("Fail to enqueue mbuf into tx_q\n");
			goto drop;
		}
	} else {
		/* Failing should not happen */
		pr_err("Fail to dequeue mbuf from alloc_q\n");
		goto drop;
	}

	/* Free skb and update statistics */
	dev_kfree_skb(skb);
	kni->stats.tx_bytes += len;
	kni->stats.tx_packets++;

	return NETDEV_TX_OK;
}

发包的情况很简单,就是从预先分配的alloc_q获取mbuf,注意这时候取出的物理地址,转换成kernel 虚拟地址进行skb内容转换,然后写入tx_q,注意写入的是user对应的虚拟地址,这样在用户态取出来就直接用了。

2.3.6 rte_kni_handle_request

改函数处理几种请求,当kni口相关属性变化时,对physical port进行响应的处理:

int rte_kni_handle_request(struct rte_kni *kni)
{
	unsigned ret;
	struct rte_kni_request *req = NULL;

	if (kni == NULL)
		return -1;

	/* Get request mbuf */
	ret = kni_fifo_get(kni->req_q, (void **)&req, 1);

	if (req != kni->sync_addr) {
		RTE_LOG(ERR, KNI, "Wrong req pointer %p\n", req);
		return -1;
	}

	/* Analyze the request and call the relevant actions for it */
	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;
	case RTE_KNI_REQ_CHANGE_MAC_ADDR: /* Change MAC Address */
		if (kni->ops.config_mac_address)
			req->result = kni->ops.config_mac_address(
					kni->ops.port_id, req->mac_addr);
		else if (kni->ops.port_id != UINT16_MAX)
			req->result = kni_config_mac_address(
					kni->ops.port_id, req->mac_addr);
		break;
	case RTE_KNI_REQ_CHANGE_PROMISC: /* Change PROMISCUOUS MODE */
		if (kni->ops.config_promiscusity)
			req->result = kni->ops.config_promiscusity(
					kni->ops.port_id, req->promiscusity);
		else if (kni->ops.port_id != UINT16_MAX)
			req->result = kni_config_promiscusity(
					kni->ops.port_id, req->promiscusity);
		break;
	default:
		RTE_LOG(ERR, KNI, "Unknown request id %u\n", req->req_id);
		req->result = -EINVAL;
		break;
	}

	/* Construct response mbuf and put it back to resp_q */
	ret = kni_fifo_put(kni->resp_q, (void **)&req, 1);
	if (ret != 1) {
		RTE_LOG(ERR, KNI, "Fail to put the muf back to resp_q\n");
		return -1; /* It is an error of can't putting the mbuf back */
	}

	return 0;
}

整体很简单,就是用到前面剩余的req_q,resp_q来响应请求。kni对应的事件就能通知给physical port并执行相应的动作了:

enum rte_kni_req_id {
	RTE_KNI_REQ_UNKNOWN = 0,
	RTE_KNI_REQ_CHANGE_MTU,
	RTE_KNI_REQ_CFG_NETWORK_IF,
	RTE_KNI_REQ_CHANGE_MAC_ADDR,
	RTE_KNI_REQ_CHANGE_PROMISC,
	RTE_KNI_REQ_MAX,
};

内核驱动一般调用

static int kni_net_process_request(struct kni_dev *kni, struct rte_kni_request *req)
{
	int ret = -1;
	void *resp_va;
	uint32_t num;
	int ret_val;

	if (!kni || !req) {
		pr_err("No kni instance or request\n");
		return -EINVAL;
	}

	mutex_lock(&kni->sync_lock);

	/* Construct data */
	memcpy(kni->sync_kva, req, sizeof(struct rte_kni_request));
	num = kni_fifo_put(kni->req_q, &kni->sync_va, 1);
	if (num < 1) {
		pr_err("Cannot send to req_q\n");
		ret = -EBUSY;
		goto fail;
	}

	ret_val = wait_event_interruptible_timeout(kni->wq,
			kni_fifo_count(kni->resp_q), 3 * HZ);
	if (signal_pending(current) || ret_val <= 0) {
		ret = -ETIME;
		goto fail;
	}
	num = kni_fifo_get(kni->resp_q, (void **)&resp_va, 1);
	if (num != 1 || resp_va != kni->sync_va) {
		/* This should never happen */
		pr_err("No data in resp_q\n");
		ret = -ENODATA;
		goto fail;
	}

	memcpy(req, kni->sync_kva, sizeof(struct rte_kni_request));
	ret = 0;

fail:
	mutex_unlock(&kni->sync_lock);
	return ret;
}

req_q对应kni->sync_kva的内存,入队用户态地址:

    memcpy(kni->sync_kva, req, sizeof(struct rte_kni_request));
    num = kni_fifo_put(kni->req_q, &kni->sync_va, 1);

了解这一点,其他的很好理解了。

三、参考

【1】sample_app_ug-master

【2】prog_guide-master

 

DPDK用户态kni实现 以DPDK的kni example分析用户态kni实现: 1、DPDK初始化; 2、申请mbuf pool内存池:pktmbuf_pool; 3、init_kni 若网卡的kni_port_params_array的nb_lcore_k未设置,设置nb_lcore_k为1; rte_kni_init:打开kni设备文件,获取kni设备文件fd:kni_fd; 4、查找ports_mas... 阅读详情

相关推荐

VQGAN实战:从文本到高清图像的保姆级生成教程(含PyTorch代码)

本文详细介绍了VQGAN(Vector Quantized Generative Adversarial Network)在文本到高清图像生成中的应用,提供了从环境配置到模型加载、文本编码及图像生成的完整PyTorch代码教程。通过实战案例和高级调优技巧,帮助开发者快速掌握VQGAN的核心技术,实现百万像素级的高清图像生成。

u8v9w0x1y的博客 649

dpdk之kni实现

一、为什么要用kni 通常情况下dpdk用于二三层报文转发,接收到来自网卡的报文后,如果是二层报文则查找fdb表; 如果是三层报文,则进行dnat, snat处理后,查找路由表, 将报文转发给下一跳路由。这些二三层转发操作都是直接转发到另一台设备上,不需要经过内核,无需内核协议栈的参与。 然而有些场景下报文是直接发给运行dpdk程序的这台设备本身的。例如ping...

ApeLife技术博客 8661

Code Composer Studio 12.0.0版本 官方例程导入实战指南

本文详细介绍了在Code Composer Studio 12.0.0版本中,如何利用内置的Resource Explorer工具高效导入官方例程的完整实战指南。通过分步讲解定位开发板、导入工程、配置编译器及调试验证等关键操作,帮助开发者快速上手TI微控制器开发,避免常见错误,有效提升学习与开发效率。

weixin_29169899的博客 120

dpdk KNI技术详解

在加载kni模块时,可以设置它的内核线程模式::只在内核侧创建一个内核线程,来接收所有kni设备上的数据包,一个线程 vs 所有kni设备:每个kni接口创建一个内核线程,用来接收数据包,一个线程 vs 一个kni设备dpdk在加载kni模块时,默认是采用的single模式,同时还可以为此内核线程设置cpu亲和性。

china718419198的博客 2005

【论文速读】MLDT:基于开源大语言模型的复杂长时机器人任务规划多层次分解方法

—在数据驱动的人工智能技术领域,开源大语言模型(LLMs)在机器人任务规划中的应用是一个重要的里程碑。近期基于开源大语言模型的机器人任务规划方法通常利用大量的任务规划数据集来提升模型的规划能力。虽然这些方法展现出了一定的前景,但它们在处理复杂长时任务时存在困难,因为这类任务需要理解更多的上下文信息并生成更长的动作序列。本文提出多层次分解任务规划方法(MLDT)来解决这一局限性。该方法创新性地在目标级、任务级和动作级对任务进行分解,以应对复杂长时任务带来的挑战。

muyulingfengye的博客 1642

Android GNSS 可视卫星星空图/卫星天顶图 原理及画法介绍

目录1 概念介绍2 绘制步骤2.1 获取卫星信息2.2 绘制底图 效果图: 1 概念介绍 首先介绍一下方位角和高度角的概念: 方位角:是从某点的指北(地理北极)方向线起,依顺时针方向到目标方向线之间的水平夹角。 高度角:从一点至观测目标的方向线与水平面间的夹角。 卫星天顶图 即是根据每一颗卫星的方位角和高度角将其画在以观测位置为中心的天顶图上。 天顶图其底图为由外向内的三个圆和四条直线。三个圆由外向内依次代表高度角0°、30°、60°,中心点代表90°;四条直线分别表示正北-正南、东北-西南、正东

x1471898145的博客 1万+

数据库修仙金丹篇八——dpdk之kni使用

dpdk之kni使用 什么是kni Kni(Kernel NIC Interface)内核网卡接口,是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和linux内核之间通讯。kni接口允许报文从用户态接收后转发到linu协议栈去。 为什么要弄一个kni接口,虽然dpdk的高速转发性能很出色,但是也有自己的一些缺点,比如没有协议栈就是其中一项缺陷,当然也可能当时设计时就将没有将协议栈考虑进去,毕竟协议栈需要将报文转发处理,可能会使处理报文的能力大大降低。 当k

growdu的网络博客 7739

手把手教你学Simulink——基于Simulink的前馈解耦SVPWM整流器仿真

本文详细介绍了基于Simulink的前馈解耦SVPWM整流器仿真方法。针对三相PWM整流器存在的dq轴交叉耦合问题,提出前馈解耦控制方案,通过在电流环中引入耦合电压补偿项,显著提升系统性能。文章包含完整的系统架构、核心算法、Simulink建模步骤及参数设置,仿真结果显示:电流跟踪误差<2%、动态响应时间<0.05s、THD<3%、功率因数>0.999。同时提供了可直接复用的MATLAB代码和工程优化建议,为电力电子工程师实现高精度AC/DC转换提供了实用解决方案。

MHD0815的博客 107

DPDK开发技能树:KNI模块的代码实现,超越内核壁垒!

动态网路接口(Kernel Network Interface,KNI)模块是DPDK的一个核心组件,它允许用户空间程序和内核进行数据通信,基于DPDK的KNI模块可以提供更高效的网络数据传输服务。通过使用KNI模块,可以实现用户空间应用程序与内核之间的高性能数据包传输,无需经过内核网络协议栈,避免了内核网络协议栈的性能瓶颈。 在DPDK开发的时候,如果有些协议不想处理,只处理关注的协议,可以把其他协议写回内核,让内核处理。此时的DPDK就起到分发的作用,类似一个过滤器。

Lion_Long的博客 1900

R语言基于机器学习三分类结果、计算三分类混淆矩阵、计算每一个类别对应的特异度(specificity)、灵敏度(sensitivity)、PPV、NPV

R语言基于机器学习三分类结果、计算三分类混淆矩阵、计算每一个类别对应的特异度(specificity)、灵敏度(sensitivity)、PPV、NPV

data+scenario+science+insight 643

Android 13 wificond讲解

Android 13 wificond讲解

专业开发者的博客,一个从事Android WiFi和蓝牙的工程师 2931

dpdk-16.04 kni 示例程序分析

dpdk-16.04 kni 示例程序分析 kni 程序在实现 dpdk 程序基础转发功能的同时,通过共享队列建立起了 dpdk 程序与内核协议栈通信的桥梁。dpdk 收到的包能够通过 kni 共享队列上送协议栈,经协议栈处理后再通过共享队列发到 pmd 中通过物理网卡发送出去,它代表了与 l2fwd 颇为不同的一种数据转发模型。 l2fwd 仅进行最简单的转发,不能对网卡接口进行控制,如 down、up、set mtu 等,kni 程序依赖 rte_kni.ko 模块,能够通过 ifconfig、etht

龙瑜的博客 1219

2026 MCM Problem A:智能手机电池连续时间建模——完整解题思路附第一问解题代码

摘要:本文针对2026年MCM A题"智能手机电池连续时间建模"问题,提出基于能量守恒原理的微分方程模型。通过建立以SOC(t)为状态变量的动态系统,将屏幕亮度、处理器负载等主要耗电因素纳入统一功率框架,实现了不同使用场景下的电量变化精确描述。研究表明,屏幕亮度和CPU负载对续航时间影响最大,温度与后台任务会产生放大效应。该模型具有物理可解释性和应用扩展性,为手机电源管理提供了理论依据。(150字)

一键难忘的博客 767

dpdk kni流程梳理和分析

学习新东西,首先问自己三个问题 kni是什么? 使用kni能解决什么问题?或者带来什么好处? 研究kni在dpdk中的api如何使用 探究dpdk kni的实现原理 一、KNI 在DPDK报文处理中,有些报文需要发送到内核协议栈进行处理,如GTP-C控制报文 如果报文数量较少,可以使用内核提供的TAP/TUN设备,但是鉴于这种设备使用的系统调用的方式,还涉及到copy_to_u...

脚踏实地,不断突破自我,每天有收获就OK 8454

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

DPDK kni模块分析

相关背景 dpdk缺少相应的协议栈,这提升了dpdk的入门门槛。如果我们采用dpdk来开发代码,当网卡被用户态驱动绑定之后,我们不仅需要处理业务报文,还需要对arp,ping,tcp握手等消息进行处理,对于笔者目前的一个业务场景(缺少协议栈,控制面和用户名共用一个网卡不同ip,数据面需要提升收包性能,因此采用了dpdk。为什么控制面和用户名不用两张网卡呢?产品经理决定的~)来说就比较头疼。幸运的是dpdk提供了kni模块,来解决相应的问题。这篇文章就是介绍dpdk的kni模块的实现机制。建议先阅读下dpd

the_dog_tail_grass的博客 1596

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

DPDK与KNI在DNS流量分析中的高性能实践

在网络数据包处理领域,DPDK(数据平面开发套件)通过绕过内核协议栈实现高性能数据转发,而KNI(内核网卡接口)则作为连接DPDK与Linux内核网络协议栈的关键桥梁。这种组合技术特别适用于需要同时处理高吞吐量和完整协议栈功能的场景,如DNS流量分析。通过无锁环形队列和RSS多队列负载均衡等机制,系统可以实现百万级数据包处理能力,同时保持与iptables等内核工具的兼容性。在DNS这类特殊协议处理中,还需考虑UDP分片重组和TCP回退机制等细节。典型应用场景包括网络安全监控、流量分析等需要兼顾性能与功能完

weixin_32821533的博客 176

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

dpdk-16.04 rte_kni 模块分析

rte_kni 模块分析 rte_kni 模块是 dpdk 版本中一个非常重要的功能,它充当了用户态驱动与内核协议栈之间的桥梁,让 dpdk 程序能够上送流量到内核协议栈,同时也支持使用 ethtool、ifconfig 等重要的网络管理命令来控制接口状态。 rte_kni 模块可以看作是一个虚拟的网卡驱动,符合网卡驱动的常见特征,其收发包过程却与网卡驱动有很大的差别,接口控制过程也不同于普通的网卡驱动,它涉及与 pmd 中频繁的交互工作,这是 rte_kni 功能的重点。 rte_kni 实现了一个虚拟网

龙瑜的博客 1457

dpdk kni例子程序分析

整体的流程:     1.rte_eal_init():环境初始化     2.parse_args():参数解析     3.rte_pktmbuf_pool_create():Create the mbuf pool     4.init_kni():     5.循环调用init_port()和kni_alloc():     6.rte_eal_mp_remote_launch

sinat_32343037的博客 2799

[转]dpdk kni 原理(比较详细的源码分析)

dpdk kni 队列, 线程相关原理

805

dpdk rte_kni 模块 kni_open 函数 oops 问题分析

问题描述 在适配某平台的时候遇到 kni_open 函数调用的时候内核 oops,oops 的主要信息见下图: 前期得到的输入信息是内核的 config 文件换过,换了之后重新编译测试出现了这个问题,可能是 config 文件影响,排查 config 文件的区别,没有发现怀疑点,只能怼 oops 了! 从 oops 中获取到的关键信息 上述 oops 内容将问题指向如下位置: kni_open 函数 0x16 偏移量处 objdump -d rte_kni.ko 获取到如下信息: 00000000000

龙瑜的博客 806

dpdk kni 网络收发包流程分析

dpdk kni ret_eth_tx

sf_jiang的博客 314

dpdk中kni模块

一,什么是kni,为什么要有kni Kni(Kernel NIC Interface)内核网卡接口,是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和linux内核之间通讯。kni接口允许报文从用户态接收后转发到linu协议栈去。 为什么要弄一个kni接口,虽然dpdk的高速转发性能很出色,但是也有自己的一些缺点,比如没...

1042

DPDK-KNI学习和利用(DNS服务)

0x01 缘由     看到这个例子,不禁想起前期我们做的一个项目,我们通过pfring捕获数据包,想让这个数据包再回到linux协议栈,然后访问我们的服务。我记得我们采用的方式是将两个物理网卡相连,然后数据包转发到另外一个普通网卡,让其进Linux协议栈处理流程。     同样KNI也是一种方式,用DPDK捕获数据包让其走linux协议栈去处理。 0x02 KNI是咋回事?     KN

3-Number 8187

dpdk20.02.1 kni虚拟网卡bonding问题“BUG: scheduling while atomic“

在高性能云机项目中,使用dpdk20.02.1版本时,尝试将多个kni虚拟网卡bonding生成一个对外业务IP时,遇到了“BUG: scheduling while atomic”异常。系统日志显示,在设置kni虚拟网卡的“promiscuous”属性时,触发了内核函数调用链,导致在RCU读取临界区内尝试调度,违反了内核的原子上下文规则。解决方案有两种:一是修改dpdk kni代码,在kni_net_process_request中增加禁用抢占的判断;二是升级dpdk到21.11以上版本。

pkyang2004的博客 945

深入浅出DPDK KNI核心技术

(免费订阅,永久学习)学习地址:

lingshengxiyou的博客 1367
上一篇: 编程基础(五)—— 虚拟内存
下一篇: Linux设备驱动——驱动模型
whenloce
博客等级 码龄17年 106粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值