源文件uloop.c
uloop_init
创建epoll句柄;并设置句柄FD_CLOEXEC属性;对描述符设置了FD_CLOEXEC,使用execl执行的程序里,此描述符被关闭,不能再使用它,但是在使用fork调用的子进程中,此描述符并不关闭,仍可使用。
uloop_run
void uloop_run(void)
{
static int recursive_calls = 0;
struct timeval tv;
/*
* Handlers are only updated for the first call to uloop_run() (and restored
* when this call is done).
*/
if (!recursive_calls++)
uloop_setup_signals(true);
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);
uloop_run_events(uloop_get_next_timeout(&tv));
}
if (!--recursive_calls)
uloop_setup_signals(false);
}
- static变量recursive_calls记录了是否该函数次数;若首次调用通过uloop_setup_signals设置信号处理函数;
- uloop_setup_signals对SIGINT、SIGTERM、SIGCHLD设置处理函数,并记录了信号修改前的处理状态,以便之后恢复;SIGINT、SIGTERM处理函数将全局变量uloop_cancelled 设置为true;SIGCHLD处理函数将全局变量do_sigchld设置为true;当子进程终止或停止时,将SIGCHLD信号发送给其父进程,系统默认忽略此信号;忽略SIGPIPE信号;
- 接着进程会进入一个while循环来处理定时器和信号事件;
- uloop_gettime获取当前系统时间;
- uloop_process_timeouts(&tv); 遍历timeouts链表内节点,检测到存在当前时间点可以触发的事件则触发回调函数,并从链表中移除该事件;若运行期间存在SIGCHLD事件,则do_sigchld置为true,调用函数uloop_handle_processes处理首先将do_sigchld置为false,等待下次信号产生再激活该函数;调用waitpid来获取已经结束的子进程的PID,并通过该pid在processes链表内寻找对应节点,从链表中删除并执行其回调函数;uloop_gettime(&tv);再次获取当前时间;uloop_get_next_timeout(&tv)通过查找timeouts链表,计算下一个超时事件的时间间隔;若链表为空返回-1;若存在立刻应该触发的时间返回0;
- uloop_get_next_timeout返回值作为uloop_run_events的入参,利用epoll_wait的超时机制来实现定时器的操作;epoll_wait超时返回则再次执行while循环;
- uloop_run_events同时还监听已经注册到poll_fd内的描述符,通过返回的struct epoll_event events数组来判断发生的事件,并执行其cb函数;
- while循环只有在uloop_cancelled为true时,才会break;也就是只有发送SIGINT、SIGTERM信号时,才会退出循环;