uloop是openwrt的一个事件库,由libubox包提供,使用时需要链接libubox.so。libubox除了提供uloop还提供avl,blob等功能。
uloop头文件:libubox/uloop.h
uloop源码:uloop.c uloop-epoll.c
uloop的运行数据都存储在共享库的静态变量中,因此不可以运行两个uloop实例。在linux上,uloop使用epoll。
#define ULOOP_MAX_EVENTS 10
static struct list_head timeouts = LIST_HEAD_INIT(timeouts);
static struct list_head processes = LIST_HEAD_INIT(processes);
static int poll_fd = -1;
bool uloop_cancelled = false;
bool uloop_handle_sigchld = true;
static int uloop_status = 0;
static bool do_sigchld = false;
static struct uloop_fd_event cur_fds[ULOOP_MAX_EVENTS];
static int cur_fd, cur_nfds;
static int uloop_run_depth = 0;
导出的全局变量
extern bool uloop_cancelled;
extern bool uloop_handle_sigchld;
uloop_cancelled是否取消uloop的运行。
uloop_handle_sigchld是否注册sigchld信号处理函数,等等待进程事件时,必须设置为1。
接口:int uloop_init(void)
初始化uloop,成功返回0,失败返回-1。使用者必须调用这个接口来初始化uloop。
1 调用uloop_init_pollfd()创建poll_fd,将被将其设置为FD_CLOEXEC
2 调用waker_init()
waker是用来在信号到来的时候唤醒uloop的。
使用pipe()创建管道,第一个fd使用uloop_fd_add()添加到uloop中,其read回调函数是 waker_consume(),其读完fd中的数据。
第二个fd,保存到waker_pipe变量。
3 uloop_setup_signals(true); 设置信号处理函数,
设置SIGINT/SIGTERM的信号处理函数为uloop_handle_sigint。
如果uloop_handle_sigchld为true,设置SIGCHLD的信号处理函数为uloop_sigchld。
忽略SIGPIPE信号。
uloop_handle_sigint
设置 uloop_status 为 signo。
设置 uloop_cancelled 为 true。
调用 uloop_signal_wake() 往 waker_pipe写入一个w字符。
uloop_sigchld()
设置 do_sigchld 为true。
调用 uloop_signal_wake() 往 waker_pipe写入一个w字符。
结构体,直接定义在uloop.h,使用者可以看到这个结构体。
typedef void (*uloop_fd_handler)(struct uloop_fd *u, unsigned int events);
struct uloop_fd
{
uloop_fd_handler cb;
int fd;
bool eof;
bool error;
bool registered;
uint8_t flags;
};
flags:
#define ULOOP_READ (1 << 0)
#define ULOOP_WRITE (1 << 1)
#define ULOOP_EDGE_TRIGGER (1 << 2)
#define ULOOP_BLOCKING (1 << 3)
接口:int uloop_fd_add(struct uloop_fd *sock, unsigned int flags);
添加一个fd。
如果没有ULOOP_BLOCKING,就将fd设置成非阻塞。
调用register_poll() -> epoll_ctl() 注册到epoll。
设置 registered 为 true。
设置 eof 为false。
设置 error为false。
返回epoll_ctl的返回值。
可以看到,uloop本身并不缓存添加的uloop_fd,而是通过epoll_ctl直接下发到内核。
定时器是添加到双向链表中的。
struct uloop_timeout
{
struct list_head list;
bool pending;
uloop_timeout_handler cb;
struct timeval time;
};
注意上面的time是一个CLOCK_MONOTONIC的绝对时间
接口:int uloop_timeout_add(struct uloop_timeout *timeout)
如果pending为true,则返回-1.
将其插入到timeouts这个双向链表中,插入的时候,按时间顺序插入。
设置pending为true。
接口:int uloop_timeout_cancel(struct uloop_timeout *timeout)
如果pending为false,返回-1
将timeout从链表中删除
将pending设置为false。
接口:int uloop_timeout_set(struct uloop_timeout *timeout, int msecs);
如果已经添加,先删除。
计算timeout为当前时间之后msecs。
然后将timeout添加到双向链表。
进程也是添加到双向链表中的,当sigchild信号来之后,设置do_sigchld为true,然后uloop就去wait这些进程。
struct uloop_process
{
struct list_head list;
bool pending;
uloop_process_handler cb;
pid_t pid;
};
接口:int uloop_process_add(struct uloop_process *p)
如果pending为true,则返回-1
将p按pid的顺序插入到双向链表中
将pending设置为true。
接口:int uloop_process_delete(struct uloop_process *p)
将process从双向链表中删除。
实际上是调用 uloop_run_timeout(-1);
接口:int uloop_run_timeout(int timeout)
int uloop_run_timeout(int timeout)
{
int next_time = 0;
struct timeval tv;
uloop_run_depth++;
uloop_status = 0;
uloop_cancelled = false;
while (!uloop_cancelled)
{
uloop_gettime(&tv);
uloop_process_timeouts(&tv);
if (do_sigchld)
uloop_handle_processes();
if (uloop_cancelled)
break;
uloop_gettime(&tv);
next_time = uloop_get_next_timeout(&tv);
if (timeout >= 0 && timeout < next_time)
next_time = timeout;
uloop_run_events(next_time);
}
--uloop_run_depth;
return uloop_status;
}
在一个uloop_cancelled的while循环中
1 先处理定时器
uloop_gettime(&tv);
uloop_process_timeouts(&tv);
由于定时器是按时间顺序存储的,所以它遍历的方式是,每次遍历第一个,如果这个定时器超时,就先cancel,
然后执行这个定时器的cb。cancel之后,下一个变成第一个,又遍历第一个,直到没有超时,就break。
2 处理进程事件
如果do_sigchld为true,表明收到了SIGCHLD信号,那就调用 uloop_handle_processes()
uloop_handle_processes处理逻辑如下:
while执行wait直到没有子进程,wait到一个pid后,就去遍历双向链表,
遍历到后,就先删除uloop_process_delete(p); 然后调用cb。
3 fd事件处理
先根据定时器双向链表的第一个定时器,计算出poll最多等待的时间,这样做是为了定时器能够得到及时的处理。
另外如果传入了timeout参数,且timeout参数小于等待时间,则将等待时间设置为timeout。
然后调用uloop_run_events(next_time);
uloop_run_events
先调用uloop_fetch_events(timeout)读取事件,然后一一处理。
注意,如果没有定时器,那么timeout永远是-1。
由于uloop内核创建了poll fd,还是设置了信号处理函数,因此在结束uloop的时候需要做一些清理工作,结束uloop通常也意味着进程即将终结。
接口:void uloop_done(void)
1 关闭poll fd
2 关闭 waker pipe
3 清除timeouts
4 清除processes