声明:此文档只做学习交流使用,请勿用作其他商业用途
author:朝阳_tony 转载请注明出处:http://blog.csdn.net/linzhaolove
此文中源码可以去http://dpdk.org/dev 网页中下载;更多官方文档请访问http://dpdk.org
首先调用rte_eal_intr_init()函数去初始化中断;
/* init the global interrupt source head */
TAILQ_INIT(&intr_sources);
首先初始化一个队列链表,intr_sources 是一个static struct rte_intr_source_list结构体,经过TAILQ_HEAD(rte_intr_source_list, rte_intr_source)初始化后的结构体
TAILQ_HEAD(rte_intr_cb_list, rte_intr_callback);
TAILQ_HEAD(rte_intr_source_list, rte_intr_source);
struct rte_intr_callback {
TAILQ_ENTRY(rte_intr_callback) next;
rte_intr_callback_fn cb_fn; /**< callback address */
void *cb_arg; /**< parameter for callback */
};
struct rte_intr_source {
TAILQ_ENTRY(rte_intr_source) next;
struct rte_intr_handle intr_handle; /**< interrupt handle */
struct rte_intr_cb_list callbacks; /**< user callbacks */
};
struct rte_intr_source 里面 包含一中断句柄,和一个中断回调函数链表;
/**
* create a pipe which will be waited by epoll and notified to
* rebuild the wait list of epoll.
*/
if (pipe(intr_pipe.pipefd) < 0)
去创建一个管道,用于epoll的消息通知;
/* create the host thread to wait/handle the interrupt */
ret = pthread_create(&intr_thread, NULL,
eal_intr_thread_main, NULL);
创建中断管理线程,去等待响应处理中断;
eal_intr_thread_main()作为中断线程的管理函数;
/* create epoll fd */
int pfd = epoll_create(1);
创建epoll功能文件描述符;dpdk是通过epoll消息机制进行通信,然后再从pipe管道中读取消息,完成dpdk中断机制的中断注册;
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));
}
将管道描述符赋值给epoll的结构体中;通过epoll_ctl去设置监控管道的读描述符intr_pipe.readfd;
rte_spinlock_lock(&intr_lock);
TAILQ_FOREACH(src, &intr_sources, next) {
if (src->callbacks.tqh_first == NULL)
continue; /* skip those with no callbacks */
ev.events = EPOLLIN | EPOLLPRI;
ev.data.fd = src->intr_handle.fd;
/**
* add all the uio device file descriptor
* into wait list.
*/
if (epoll_ctl(pfd, EPOLL_CTL_ADD,
src->intr_handle.fd, &ev) < 0){
rte_panic("Error adding fd %d epoll_ctl, %s\n",
src->intr_handle.fd, strerror(errno));
}
else
numfds++;
}
rte_spinlock_unlock(&intr_lock);
这一段code应该是在使用uio设备时候才会运行;需要uio去注册中断函数;也会将uio设备文件描述符添加进去;
uio设备是支持用户态驱动的一种linux内核机制,在采用uio后会在/dev目录下产生几个设备文件;
# ls /dev/uio*
/dev/uio0 /dev/uio1 /dev/uio2 /dev/uio3
rte_spinlock_lock(&intr_lock);
rte_spinlock_unlock(&intr_lock);
eal_intr_handle_interrupts(pfd, numfds);
通过上面这个函数去等待消息,读取中断注册信息;而 numfds这个参数是指,去等待几个描述符的消息 ;
eal_intr_handle_interrupts定义在dpdk/lib/librte_eal/linuxapp/eal/eal_interrupts.c文件中;
我们去看一下这函数的实现;
nfds = epoll_wait(pfd, events, totalfds,
EAL_INTR_EPOLL_WAIT_FOREVER);
此函数会调用epoll_wait去阻塞住,等待消息的到来;nfds是指消息到来的多少个;消息的内容是存储在events这个结构体数组中 ;
/* epoll_wait has at least one fd ready to read */
if (eal_intr_process_interrupts(events, nfds) < 0)
return;
在eal_intr_handle_interrupts再去调用eal_intr_process_interrupts去出处理真正的消息;
介绍一下这个函数eal_intr_process_interrupts
/**
* if the pipe fd is ready to read, return out to
* rebuild the wait list.
*/
if (events[n].data.fd == intr_pipe.readfd){
一开始还没有中断注册,所以就直接注册了一个管道的epoll wait,后来了新的中断描述符注册,就需要重新建立一个epoll的等待列表;
TAILQ_FOREACH(src, &intr_sources, next)
if (src->intr_handle.fd ==
events[n].data.fd)
break;
判断是不是中断描述符中的消息;
如果是中断描述符的信息,接下来,将调用的回调函数,赋值给中断链表;
/* for this source, make a copy of all the callbacks,
* then unlock the lock, so the callbacks can
* themselves manipulate the list for future
* instances.
*/
active_cb = 0;
memset(active_cbs, 0, sizeof(active_cbs));
TAILQ_FOREACH(cb, &src->callbacks, next)
active_cbs[active_cb++] = *cb;
/**
* Finally, call all callbacks from the copy
* we made earlier.
*/
for (i = 0; i < active_cb; i++) {
if (active_cbs[i].cb_fn == NULL)
continue;
active_cbs[i].cb_fn(&src->intr_handle,
active_cbs[i].cb_arg);
}
dpdk一般会在rte_eal_init()函数中调用rte_eal_intr_init()初始中断模块;
int rte_intr_callback_register(struct rte_intr_handle *intr_handle, rte_intr_callback_fn cb, void *cb_arg)
struct rte_intr_handle *intr_handle 为中断函数句柄;
rte_intr_callback_fn cb 这个参数为中断回调函数指针;
void *cb_arg 这个参数是给回调函数传递参数的指针;
在./lib/librte_eal/linuxapp/eal/eal_alarm.c 文件的第160行,运用了这个函数
注册函数是在int rte_eal_alarm_set(uint64_t us, rte_eal_alarm_callback cb_fn, void *cb_arg) 函数中调用的;
if (!handler_registered) {
ret |= rte_intr_callback_register(&intr_handle,
eal_alarm_callback, NULL);
handler_registered = (ret == 0) ? 1 : 0;
}
static struct rte_intr_handle intr_handle = {.fd = -1 };
intr_handle 为alarm 在初始化时,定义初始化的一个全局的时钟中断句柄;
eal_alarm_callback 为alarm 定义的回调函数指针;
最后一个参数传递的为NULL ,应该是不传递任何参数;看了一下eal_alarm_callback 函数实现,的确没有用传递的参数,我们自己写程序时也要注意留接口,虽然当时不用,但可以为以后扩展使用;
技术水平有待提高,如果文章有错误的地方希望读者指正,相互交流,互相学习;O(∩_∩)O~