DPDK自带的定时器采用跳表实现,时间复杂度是O(logn),当有大量事件要定时触发时,比如会话session老化,效率并不高。因此DPVS采用了O(1)复杂度的时间轮。
0. 概述
a. 添加定时器事件的核心是
static int __dpvs_timer_sched(struct timer_scheduler *sched,
struct dpvs_timer *timer, struct timeval *delay,
dpvs_timer_cb_t handler, void *arg, bool period)
b. rte_timer_tick_cb()中判断超时事件,调用timer_expire()执行事件注册的回调函数;
c. timer_expire()会判断事件是否循环执行,需要循环执行的,调用__dpvs_timer_sched()重新放回时间轮;
d. 一个事件的超时时间大于第一层的时间(DPVS_TIMER_HZ = 1000时,为524s),才会放到第二层,所以一般只用到第一层;
1. 定时器驱动
采用死循环,调用查询函数,判断是否达到时间轮的最小刻度,伪代码如下:
struct timer_scheduler *sched;
uint64_t next = 0;
uint64_t rounds = rte_get_timer_hz() / DPVS_TIMER_HZ; // 每秒的cpu圈数 / 每秒的次数 (DPVS_TIMER_HZ = 1000,rounds表示每毫秒的cpu圈数,定时器为毫秒级定时器)
for(;;) {
uint64_t now;
struct rte_timer *tim;
now = rte_get_timer_cycles();
if (now >= next) {
rte_timer_tick_cb(tim, sched);
next = now + rounds
}
}
2. 添加定时器事件
int dpvs_timer_sched_nolock(struct dpvs_timer *timer, struct timeval *delay,
dpvs_timer_cb_t handler, void *arg, bool global)
{
struct timer_scheduler *sched = this_lcore_sched(global);
int err;
if (!sched || !timer || !delay || !handler
|| delay->tv_sec >= TIMER_MAX_SECS)
return EDPVS_INVAL;
err = __dpvs_timer_sched(sched, timer, delay, handler, arg, false);
return err;
}
3. 更新定时器事件
int dpvs_timer_update_nolock(struct dpvs_timer *timer, struct timeval *delay, bool global)
{
struct timer_scheduler *sched = this_lcore_sched(global);
int err;
if (!sched || !timer || !delay)
return EDPVS_INVAL;
if (timer_pending(timer))
list_del(&timer->list);
err = __dpvs_timer_sched(sched, timer, delay,
timer->handler, timer->priv, timer->is_period);
return err;
}
4. 删除定时器事件
int dpvs_timer_cancel_nolock(struct dpvs_timer *timer, bool global)
{
struct timer_scheduler *sched = this_lcore_sched(global);
if (!sched || !timer)
return EDPVS_INVAL;
if (timer_pending(timer))
list_del(&timer->list);
return EDPVS_OK;
}
原文链接:https://zhuanlan.zhihu.com/p/366751595
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !