这一篇文章将介绍DPDK的rte_interrupt, rte_epoll, rte_timer, rte_alarm,这些功能是网卡驱动,pci以及其他的lib的基础。
由于个人水平所限,若所写的博文中存在错误,希望大家能帮忙指出。
这一篇文章主要介绍DPDK中的以下四种功能:
1,rte_interrupt,用于可以注册需要监听的中断信号以及对应的回调函数(Callback)。
2,rte_epoll允许用户通过epoll对象监听指定的fd, 并指定对应的回调函数。
3,rte_timer是DPDK的计时器,提供了delay, sleep等功能。
4,rte_alarm用于设置alarm,即设置一个定时器用于在未来的某个时间点触发一个alarm中断信号。
这部分的代码可能需要两个背景知识,IO多路复用和self-pipe trick。
若引用以上的链接侵犯了原作者的权利,请告知本人,将即刻删除。
*******************rte_eal_interrupts.h*******************
struct rte_epoll_data {
//指定了epoll对象的event
uint32_t event; /**< event type */
void *data; /**< User data */
//事件发生后需要调用的callback及其参数
rte_intr_event_cb_t cb_fun; /**< IN: callback fun */
void *cb_arg; /**< IN: callback arg */
};
/** interrupt epoll event obj, taken by epoll_event.ptr */
struct rte_epoll_event {
// 指定epoll的相关状态信息 : EXEC表示繁忙(正在处理中断信号), VALID表示空闲, INVALID表示无效
volatile uint32_t status; /**< OUT: event status */
// 需要监听的file descriptor
int fd; /**< OUT: event fd */
// epoll对象对应的文件描述符
int epfd; /**< OUT: epoll instance the ev associated with */
struct rte_epoll_data epdata;
};
/** Handle for interrupts. */
struct rte_intr_handle {
RTE_STD_C11
// 对于UIO而言,对应于/sys/class/uio/uio%u/device/config, 即PCI配置空间文件
// 对于VFIO而言,对应于设备的vfio_dev_fd, 其作用是作为参数用于ioctl()
union {
int vfio_dev_fd; /**< VFIO device file descriptor */
int uio_cfg_fd; /**< UIO cfg file desc for uio_pci_generic */
};
//对于UIO而言,对应于/dev/uioX; 对于VFIO而言,对应于eventfd; 对于ALARM而言,对应于timefd;
int fd; /**< interrupt event file descriptor */
enum rte_intr_handle_type type; /**< handle type */
uint32_t max_intr; /**< max interrupt requested */
uint32_t nb_efd; /**< number of available efd(event fd) */
uint8_t efd_counter_size; /**< size of efd counter, used for vdev */
//每一个efds都会与一个elist相对应
int efds[RTE_MAX_RXTX_INTR_VEC_ID]; /**< intr vectors/efds mapping */
struct rte_epoll_event elist[RTE_MAX_RXTX_INTR_VEC_ID];
/**< intr vector epoll event */
int *intr_vec; /**< intr vector number array */
};
*******************eal_alarm.c*******************
struct alarm_entry {
LIST_ENTRY(alarm_entry) next;
//alarm发生的时间点
struct timeval time;
//对应的callback函数以及参数
rte_eal_alarm_callback cb_fn;
void *cb_arg;
//指明alarm是否正在执行
volatile uint8_t executing;
//执行alarm的线程的id
volatile pthread_t executing_id;
};
rte_interrupt的使用 : 程序可以通过 rte_intr_callback_register() 注册一个中断源以及对应的callback,注册完成后,中断控制线程会对其信号进行监听,若有中断信号产生,则会调用对应的callback函数。
还可以通过 rte_intr_callback_unregister() 注销一个中断源的callback, 此时中断控制线程会停止对中断信号进行监听。
rte_interrupt的实现 : DPDK会维持一个关于中断源的list,list的每一个元素保存了中断的相关信息及其相应的callback list。中断控制线程对应的例程为eal_intr_thread_main(),其使用了 epoll_create() 创建了一个epoll对象,然后使用 epoll_ctl() 将intr_pipe的读端的fd,以及中断源list中的对应的fd加入到epoll对象的fd interesting list中。然后使用 epoll_wait() 监听epoll对象的fd interesting list。一旦有某些file descriptor有中断信号发生,则会对于每一个中断信号,调用其对应的callback函数。
上面提到的 intr_pipe 是作为一个self-pipe, 程序调用 rte_intr_callback_register() 注册一个新的中断源时,会向intr_pipe的写端写入一个字符,此时中断控制线程的epoll会监听到intr_pipe有I/O信号,说明中断源list发送了变化,则会重新构造epoll对象,将新的中断源加入到epoll对象的fd interesting list中,使用epoll_wait()继续监听epoll对象。
并且epoll对象采用了水平触发(level-triggered), 所以intr_pipe和其他fd都可以I/O的情况下,在重新构造epoll后,其他fd的I/O会使得epoll_wait()立即返回,继续处理其他fd的I/O。
DPDK还提供了rte_epoll, 主要包括了rte_epoll_ctl() , rte_epoll_wait(),。其底层也是使用了epoll进行实现。
rte_epoll_ctl() : 将某个fd加入到epoll对象(可由用户指定,若没有指定则由系统创建)中。
rte_epoll_wait() : 开始监听epoll对象的fd interesting list。
rte_timer的使用:,给程序提供了rte_delay_us_block, rte_delay_us_sleep等功能,主要作用是使得线程休眠一段时间;其底层是使用Linux系统的timer机制进行实现的。(使用time.h中的clock_gettime, nanosleep, sleep进行实现)
DPDK初始化时会调用 rte_eal_timer_init() 初始化timer的相关信息(比如时钟频率)
(默认使用了Time Stamp Counter, 缩写为TSC, 系统也提供了HPET的初始化,对应于rte_eal_hpet_init(),可通过配置文件启动)
DPDK提供的rte_alarm,包括设置一个alarm, 取消alarm等。其底层是使用了timefd.h, time.h, rte_interrupt进行实现。程序可以通过rte_eal_alarm_set()设置一个定时器,或者rte_eal_alarm_cancel()关闭某个定时器。
rte_alarm的实现: DPDK调用rte_eal_alarm_init()初始化一个类型为ALARM的中断源。并且维护了一个alarm list。每当用户调用 rte_eal_alarm_set() 时,会创建一个alarm_entry, 并且存放用户指定的callback函数;然后调用rte_intr_callback_register() 注册ALARM的中断源,然后使用 timerfd_settime() 设置定时器的时间,使得在对应的时间点会产生ALARM中断信号。最后信号发生时,由中断控制线程进行处理。rte_eal_alarm_cancel() 会将相对应的alarm从alarm list中移除即可。
1,本文简单地介绍了rte_interrupt, rte_epoll, rte_timer, rte_alarm, 这四个功能的实现比较简单,代码也比较少。
2,rte_interrupt 和 rte_epoll 两者的区别在于,rte_interrupt会委托中断控制线程进行监听,而rte_epoll则是由调用rte_epoll_wait()的线程进行监听。
3,rte_timer的功能主要是获取时钟相关的信息和对线程进行休眠操作,而rte_alarm主要是设置一个定时器,指定在未来的某个时间点内产生一个ALARM中断信号。
下面给出的是rte_interrupt, rte_epoll, rte_timer, rte_alarm相关的代码的函数调用图(所阅读的代码是来自DPDK官方最新的代码https://github.com/DPDK/dpdk, 如果官方代码更新,文章也会尽快地更新)
1,rte_alarm : http://naotu.baidu.com/file/5f502fb27ab9b95a237c541c108928f7?token=77e5d32f728698af
2,rte_timer : http://naotu.baidu.com/file/b0b4cdde1c182f4986a7d79f030e882c?token=aed4e01a4c885851
3,rte_interrupt 和 rte_epoll : http://naotu.baidu.com/file/92c83461a325cec7744d97c2c41216fc?token=c477de8bad81c87c