dpdk的中断是用户态的中断,实现方式是通过vfio或uio模块将内核的中断传递到用户态,并且dpdk实现的中断机制属于控制中断,用来实现一些控制操作,例如uio中断用来设置一些网卡的状态之类。网卡收发包过程,还是使用轮询的方式从网卡接收报文。
中断初始化
中断初始化主要在rte_eal_intr_init中完成
首先初始化intr_sources链表。所有设备的中断都挂在这个链表上,中断处理线程通过遍历这个链表,来执行设备的中断。
然后 创建intr_pipe管道,用于epoll模型的消息通知。
再创建线程intr_thread,线程的执行体是eal_intr_thread_main()函数,创建epoll模型,遍历intr_sources链表,监听已注册的所有设备的中断事件,并调用对应设备的中断处理函数。中断控制线程对应的例程为eal_intr_thread_main(),其使用了 epoll_create() 创建了一个epoll对象,然后使用 epoll_ctl() 将intr_pipe的读端的fd,以及中断源list中的对应的fd加入到epoll对象的fd interesting list中。然后使用 epoll_wait() 监听epoll对象的fd interesting list
static __rte_noreturn void *
eal_intr_thread_main(__rte_unused void *arg)
{
/* host thread, never break out */
for (;;) {
/* build up the epoll fd with all descriptors we are to
* wait on then pass it to the handle_interrupts function
*/
static struct epoll_event pipe_event = {
.events = EPOLLIN | EPOLLPRI,
};
struct rte_intr_source *src;
unsigned numfds = 0;
/* create epoll fd */
int pfd = epoll_create(1);
if (pfd < 0)
rte_panic("Cannot create epoll instance\n");
pipe_event.data.fd = intr_pipe.readfd;
/**
* add pipe fd into wait list, this pipe is used to
* rebuild the wait list.
*/
if (epoll_ctl(pfd, EPOLL_CTL_ADD, intr_pipe.readfd,
&pipe_event) < 0) {
rte_panic("Error adding fd to %d epoll_ctl, %s\n",
intr_pipe.readfd, strerror(errno));
}
numfds++;
rte_spinlock_lock(&intr_lock);
TAILQ_FOREACH(src, &intr_sources, next) {
struct epoll_event ev;
if (src->callbacks.tqh_first == NULL)
continue; /* skip those with no callbacks */
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN | EPOLLPRI | EPOLLRDHUP | EPOLLHUP;
ev.data.fd = rte_intr_fd_get(src->intr_handle);
/**
* add all the uio device file descriptor
* into wait list.
*/
if (epoll_ctl(pfd, EPOLL_CTL_ADD,
rte_intr_fd_get(src->intr_handle), &ev) < 0) {
rte_panic("Error adding fd %d epoll_ctl, %s\n",
rte_intr_fd_get(src->intr_handle),
strerror(errno));
}
else
numfds++;
}
rte_spinlock_unlock(&intr_lock);
/* serve the interrupt */
eal_intr_handle_interrupts(pfd, numfds);
/**
* when we return, we need to rebuild the
* list of fds to monitor.
*/
close(pfd);
}
}
中断注册和注销
可以通过 rte_intr_callback_register() 注册一个中断源以及对应的callback,注册完成后,中断控制线程会对其信号进行监听,内部会将中断源链表中的所有中断源描述符都加入到epoll实现的红黑树中, 当相应中断源有事件发生时,epoll会调用这些中断源注册的回调函数。
还可以通过 rte_intr_callback_unregister() 注销一个中断源的callback, 此时中断控制线程会停止对中断信号进行监听。相关示例代码如下
int
mlx4_intr_install(struct mlx4_priv *priv)
{
const struct rte_eth_intr_conf *const intr_conf =
Ð_DEV(priv)->data->dev_conf.intr_conf;
int rc;
mlx4_intr_uninstall(priv);
if (intr_conf->lsc | intr_conf->rmv) {
if (rte_intr_fd_set(priv->intr_handle, priv->ctx->async_fd))
return -rte_errno;
rc = rte_intr_callback_register(priv->intr_handle,
(void (*)(void *))
mlx4_interrupt_handler,
priv);
if (rc < 0) {
rte_errno = -rc;
goto error;
}
}
return 0;
error:
mlx4_intr_uninstall(priv);
return -rte_errno;
}
int
mlx4_intr_uninstall(struct mlx4_priv *priv)
{
int err = rte_errno; /* Make sure rte_errno remains unchanged. */
if (rte_intr_fd_get(priv->intr_handle) != -1) {
rte_intr_callback_unregister(priv->intr_handle,
(void (*)(void *))
mlx4_interrupt_handler,
priv);
if (rte_intr_fd_set(priv->intr_handle, -1))
return -rte_errno;
}
rte_eal_alarm_cancel((void (*)(void *))mlx4_link_status_alarm, priv);
priv->intr_alarm = 0;
mlx4_rxq_intr_disable(priv);
rte_errno = err;
return 0;
}
相关函数调用图如下