Vppinfra支持可配置计时器的时间轮。可以在…/src/vppinfra/tw_timer_template.[ch]中查看源代码。以及…/src/vppinfra/tw_timer_xx.[ch]中定义的相当数量的模板实例。
关于时间轮的实现原理详细介绍可以参考文章:
https://blog.csdn.net/u013256816/article/details/80697456。是介绍kafka时间轮的实现。vpp 中实现细节时间轮也是一样的。这里不再讲解了。
tw_timer_template.h的实例化生成命名结构来实现特定的计时器时间轮。选择包括:计时器轮的数量(当前最多支持3个),每个时间轮的环槽的数量(必须是2的幂),以及每个“对象句柄”的计时器数量。
以tw_timer_2t_1w_2048sl.h模版为例子,时间轮的数量是1,时间轮的ring槽数量是2048,每个对象的句柄的占用的bit数量是2.通过make_internal_timer_handle函数可以了解到用户时间轮句柄user_handle是由2部分组成的。pool_index及timer_id。使用者需要注意。
代码语言:javascript复制#define TW_TIMER_WHEELS 1
#define TW_SLOTS_PER_RING 2048
#define TW_RING_SHIFT 11
#define TW_RING_MASK (TW_SLOTS_PER_RING -1)
#define TW_TIMERS_PER_OBJECT 2
#define LOG2_TW_TIMERS_PER_OBJECT 1
#define TW_SUFFIX _2t_1w_2048sl
#define TW_FAST_WHEEL_BITMAP 0
#define TW_TIMER_ALLOW_DUPLICATE_STOP 0
static inline u32
TW (make_internal_timer_handle) (u32 pool_index, u32 timer_id)
{
u32 handle;
ASSERT (timer_id < TW_TIMERS_PER_OBJECT);
#if LOG2_TW_TIMERS_PER_OBJECT > 0
ASSERT (pool_index < (1 << (32 - LOG2_TW_TIMERS_PER_OBJECT)));
handle = (timer_id << (32 - LOG2_TW_TIMERS_PER_OBJECT)) | (pool_index);
#else
handle = pool_index;
#endif
return handle;
}
时间轮使用实例:
我们可以查看vpp测试例子src/vppinfra/test_tw_timer.c来了解使用。
1、时间轮初始化
初始化一个间隔1s的的时间轮。每秒时间轮转一个槽位,最多支持2048s。timer interval也可以设置成0.1s,那么每秒ticks就是10。最多支持204.8s。
代码语言:javascript复制tw_timer_wheel_init_2t_1w_2048sl (&tm->single_wheel,
expired_timer_single_callback,
1.0 / * timer interval * / );
2、添加计时任务
当前设置的最大支持expiration_time_in_u32_ticks 是2048s,超过就达不到准确计时器的作用,也可能存在异常。
代码语言:javascript复制handle = tw_timer_start_2t_1w_2048sl (&tm->single_wheel, elt_index,
[0 | 1] / * timer id * / ,
expiration_time_in_u32_ticks);
3、删除计时任务
代码语言:javascript复制tw_timer_stop_2t_1w_2048sl (&tm->single_wheel, handle);
4、周期性调用查看是否过期事件。
代码语言:javascript复制 tw_timer_expire_timers_2t_1w_2048sl (tw, now)
5、计时时间到时,会调用回调处理函数
代码语言:javascript复制 static void
expired_timer_single_callback (u32 * expired_timers)
{
int i;
u32 pool_index, timer_id;
tw_timer_test_elt_t *e;
tw_timer_test_main_t *tm = &tw_timer_test_main;
for (i = 0; i < vec_len (expired_timers);
{
pool_index = expired_timers[i] & 0x7FFFFFFF;
timer_id = expired_timers[i] >> 31;
ASSERT (timer_id == 1);
e = pool_elt_at_index (tm->test_elts, pool_index);
if (e->expected_to_expire != tm->single_wheel.current_tick)
{
fformat (stdout, "[%d] expired at %d not %dn",
e - tm->test_elts, tm->single_wheel.current_tick,
e->expected_to_expire);
}
pool_put (tm->test_elts, e);
}
}
总结
简单介绍了时间轮的api,具体实现细节需要自己去阅读代码,实现逻辑并不复杂。时间轮的添加、删除、更新操作都是0(1)复杂度。在vpp主机协议栈及flowprope-plugins都有使用。