主要分析 redis server,对应文件redis.h/redis.c,从main函数开始分析redis server的启动过程。
主要分析以下几个函数
initServerConfig():解析配置等
initServer():初始化工作
aeSetBeforeSleepProc(server.el,beforeSleep):每次进入事件轮循前都会执行[TOREAD]
aeMain(server.el):事件轮循,运行事件处理器,一直到服务器关闭为止
aeDeleteEventLoop(server.el):服务器关闭,停止事件轮循
aeMain(server.el)
源代码(ae.h/ae.c)如下:
/*
* 事件处理器的主循环
*/
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
// 如果有需要在事件处理前执行的函数,那么运行它
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
// 开始处理事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
大致流程:
- beforesleep:在处理事件前要执行的函数,处理被阻塞命令
- aeProcessEvents:处理定时事件和网络io事件
- redis server启动完毕,等待客户端请求
事件处理器状态结构体aeEventLoop定义如下:
/* State of an event based program
*
* 事件处理器的状态
*/
typedef struct aeEventLoop {
// 目前已注册的最大描述符
int maxfd; /* highest file descriptor currently registered */
// 目前已追踪的最大描述符
int setsize; /* max number of file descriptors tracked */
// 用于生成时间事件 id
long long timeEventNextId;
// 最后一次执行时间事件的时间
time_t lastTime; /* Used to detect system clock skew */
// 已注册的文件事件
aeFileEvent *events; /* Registered events */
// 已就绪的文件事件
aeFiredEvent *fired; /* Fired events */
// 时间事件
aeTimeEvent *timeEventHead;
// 事件处理器的开关
int stop;
// 多路复用库的私有数据
void *apidata; /* This is used for polling API specific data */
// 在处理事件前要执行的函数
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
函数aeProcessEvents处理所有已经达到的时间事件以及所有已就绪的文件事件,返回已处理事件的数量。根据传入的参数flags,选择性的处理事件,例如不传入特殊flags的话,函数睡眠直到文件事件就绪或者下个时间事件到达;
如果 flags 包含 AE_ALL_EVENTS ,所有类型的事件都会被处理;
如果 flags 包含 AE_FILE_EVENTS ,那么处理文件事件。
源码(ae.h/ae.c)如下:
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
有事件描述符 或者 需要处理时间事件,接着获取最近的时间事件:
如果时间事件存在的话,那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间。计算距今最近的时间事件还要多久才能达到,并将该时间距保存在 tv 结构中:
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
// 获取最近的时间事件
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
如果时间事件存在的话,那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间。计算距今最近的时间事件还要多久才能达到,并将该时间距保存在 tv 结构中:
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
// 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞)
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
如果时间事件不存在:
// 根据 AE_DONT_WAIT 是否设置来决定是否阻塞,以及阻塞的时间长度
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
// 设置文件事件不阻塞
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
// 文件事件可以阻塞直到有事件到达为止
tvp = NULL; /* wait forever */
}
处理文件事件,阻塞时间由tvp决定:
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
// 从已就绪数组中获取事件
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
// 读事件
if (fe->mask & mask & AE_READABLE) {
// rfired 确保读/写事件只能执行其中一个
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
// 写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
processed++;
}
其中函数
aeApiPoll在ae_select.c,ae_kqueue.c,ae_epoll.c中均有定义,在编译期就已经选好。
程序便是阻塞在此,可以设置最长阻塞时间。
最后处理时间事件:
// 执行时间事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}