
uloop_timeout 是 Libubox 提供的一个定时器工具(以下简称uloop定时器), 用来实现一些简单的定时任务十分方便,下面介绍uloop定时器的原理和使用方法:
本文基于 libubox-2023-05-23-75a3b870 版本进行分析
struct uloop_timeout
{
struct list_head list;//用于将多个 uloop_timeout 结构体链接在一起
bool pending;//指示定时器是否处于挂起(等待触发)状态。
uloop_timeout_handler cb;// 定时器到期回调函数
struct timeval time;// 指定定时器的到期时间
};struct uloop_timeout用来描述一个定时器,cb是该定时器的超时回调函数,time是定时器超时时间,它是一个未来的时间。
//添加一个定时器,定时时间需要我们自己预先设定
int uloop_timeout_add(struct uloop_timeout *timeout);
//添加一个定时器,并设置超时时间,单位ms
int uloop_timeout_set(struct uloop_timeout *timeout, int msecs);
//删除一个定时器
int uloop_timeout_cancel(struct uloop_timeout *timeout);
//计算一个定时器还有多久超时,单位ms
int uloop_timeout_remaining(struct uloop_timeout *timeout);Libubox 提供的定时器都是一次性定时器,如果需要循环触发,需要重复设置定时器。
uloop.c中有维护一条定时器链表timeouts
static struct list_head timeouts = LIST_HEAD_INIT(timeouts);uloop_timeout_set和uloop_timeout_add会将新的定时器添加到链表,并且新的定时器按照超时时间由近到远的顺序被添加进链表

它的实现方式如下:
int uloop_timeout_add(struct uloop_timeout *timeout)
{
struct uloop_timeout *tmp;
struct list_head *h = &timeouts;
if (timeout->pending)
return -1;
//遍历timeouts链表,找到超时时间比新增的定时器长的成员
list_for_each_entry(tmp, &timeouts, list) {
if (tv_diff(&tmp->time, &timeout->time) > 0) {
h = &tmp->list;
break;
}
}
//将新增定时器插入到该成员的前面
list_add_tail(&timeout->list, h);
timeout->pending = true;
return 0;
}uloop_run中会遍历定时器链表,对于已经超时的定时器会执行其回调函数。
static void uloop_process_timeouts(struct timeval *tv)
{
struct uloop_timeout *t;
while (!list_empty(&timeouts)) {
//取出定时器链表中第一个成员进行判断,因为这些成员是按照超时时间由近到远的顺序,所以第一个成员肯定是最先超时
t = list_first_entry(&timeouts, struct uloop_timeout, list);
if (tv_diff(&t->time, tv) > 0)
break;//未超时 直接退出
uloop_timeout_cancel(t);
if (t->cb)
t->cb(t);
}
}uloop_process_timeouts()每次只处理timeouts中第一个成员(不是一次性处理完链表中所有的定时器),但是这个函数本身会被循环调用。
int uloop_timeout_cancel(struct uloop_timeout *timeout)
{
if (!timeout->pending)
return -1;
list_del(&timeout->list);
timeout->pending = false;
return 0;
}uloop_timeout_cancel会将定时器从全局链表中删除,但是不会释放定时器本身的资源,如果是动态申请的内存,需要手动释放。
uloop_timeout定时器的缺陷在于struct uloop_timeout结构体中没有一个存放私有数据的指针。
因此这个定时器只能实现一些简单的定时任务。
如下实例定义一个超时时间为2s的定时器u_tim ,并在回调函数中重新设置超时时间,使其循环触发。
循环3次后,取消这个定时器。
#include "stdio.h"
#include "uloop.h" //uloop_timeout_xxx
#include <sys/time.h> // struct timeval
static void u_tim_cb(struct uloop_timeout *timeout)
{
static int cnt = 0;
printf("Enter timer cb! second %ld cnt=%d\n",timeout->time.tv_sec,cnt);
uloop_timeout_set(timeout, 2000);
cnt++;
if( cnt == 3 )
{
uloop_timeout_cancel(timeout);
printf("timer cancel!\n");
}
}
static struct uloop_timeout u_tim = {
.cb = u_tim_cb,
};
int main()
{
uloop_init();
uloop_timeout_set(&u_tim, 2000);
printf("Enter uloop run!\n");
uloop_run();
return 0;
}Libubox的kvlist组件基于平衡二叉树实现,另外kvlist是纯数据结构组件,不依赖uloop.
下面介绍其kvlist工具的使用方法:
struct kvlist {
struct avl_tree avl;
int (*get_len)(struct kvlist *kv, const void *data);
};struct kvlist用于描述一个kvlist,其数据储存在平衡二叉树avl.
get_len()需要我们自行实现,其作用是获取插入KV值的长度,也就是data的实际长度。
//静态初始化一个kvlist
#define KVLIST(_name, _get_len) \
struct kvlist _name = KVLIST_INIT(_name, _get_len)
//动态初始化一个kvlist
void kvlist_init(struct kvlist *kv, int (*get_len)(struct kvlist *kv, const void *data));
//删除一个kvlist
void kvlist_free(struct kvlist *kv);
//根据key值在kvlist中查找value
void *kvlist_get(struct kvlist *kv, const char *name);
//向kvlist中添加kv键值对
bool kvlist_set(struct kvlist *kv, const char *name, const void *data);
//根据key值删除kvlist中指定的键值对
bool kvlist_delete(struct kvlist *kv, const char *name);
//kvlist_strlen是一个参考的(*get_len)函数,不一定适用于所有的情况,例如当data是结构体时就无法通过strlen计算其长度
int kvlist_strlen(struct kvlist *kv, const void *data)
{
return strlen(data) + 1;
}
//遍历kvlist中所有的成员
#define kvlist_for_each(kv, name, value)如果key的值一样,那么后添加的kv会覆盖前面的。因此,key的值必须不不同,否则会造成数据覆盖。
kvlist_for_each宏定义可以遍历一个kvlist中所有的成员,调试时使用十分方便。使用方法见下文。
#include "stdio.h"
#include "string.h"
#include "kvlist.h"
typedef struct user_data
{
int id;
char name[10];
};
struct user_data data1={1,"data1"};
struct user_data data2={2,"data2"};
struct user_data data3={3,"data3"};
static struct kvlist kv_list;
int get_len(struct kvlist *kv, const void *data)
{
return sizeof(struct user_data);
}
static void dump_kvnode(struct user_data *p)
{
if(!p)
{
printf("data null\n");
return;
}
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
}
int main()
{
struct user_data *p = NULL;
kvlist_init(&kv_list,get_len);
//set KVs
kvlist_set(&kv_list,data1.name,&data1);
kvlist_set(&kv_list,data2.name,&data2);
kvlist_set(&kv_list,data3.name,&data3);
//get KV by name
p=(struct user_data *)kvlist_get(&kv_list,data1.name);
if(!p)
{
printf("kvlist_get failed!\n");
return -1;
}
dump_kvnode(p);
//iterate over all KVs
char *p_name;
struct user_data *p_value;
kvlist_for_each(&kv_list,p_name,p_value)
{
printf("===================\n");
printf("name=%s\n",p_name);
printf("id=%d\n",p_value->id);
printf("===================\n");
}
//delete kv_list
kvlist_delete(&kv_list,data1.name);
return 0;
}调试结果
# ./kvlist
===================
name=data1
id=1
===================
===================
name=data2
id=2
===================
===================
name=data3
id=3
===================