数据结构
[include/types/fd.h]
/* info about one given fd */
struct fdtab {
struct {
int (*f)(int fd); /* read/write function */
struct buffer *b; /* read/write buffer */
} cb[DIR_SIZE];
void *owner; /* the session (or proxy) associated with this fd */
struct { /* used by pollers which support speculative polling */
unsigned char e; /* read and write events status. 4 bits*/
unsigned int s1; /* Position in spec list+1. 0=not in list. */
} spec;
unsigned short flags; /* various flags precising the exact status of this fd */
unsigned char state; /* the state of this fd */
unsigned char ev; /* event seen in return of poll() : FD_POLL_* */
};
struct poller {
void *private; /* any private data for the poller */
int REGPRM2 (*is_set)(const int fd, int dir); /* check if <fd> is being polled for dir <dir> */
int REGPRM2 (*set)(const int fd, int dir); /* set polling on <fd> for <dir> */
int REGPRM2 (*clr)(const int fd, int dir); /* clear polling on <fd> for <dir> */
int REGPRM2 (*cond_s)(const int fd, int dir); * set polling on <fd> for <dir> if unset */
int REGPRM2 (*cond_c)(const int fd, int dir); /* clear polling on <fd> for <dir> if set */
void REGPRM1 (*rem)(const int fd); /* remove any polling on <fd> */
void REGPRM1 (*clo)(const int fd); /* mark <fd> as closed */
void REGPRM2 (*poll)(struct poller *p, int exp); /* the poller itself */
int REGPRM1 (*init)(struct poller *p); /* poller initialization */
void REGPRM1 (*term)(struct poller *p); /* termination of this poller */
int REGPRM1 (*test)(struct poller *p); /* pre-init check of the poller */
int REGPRM1 (*fork)(struct poller *p); /* post-fork re-opening */
const char *name; /* poller name */
int pref; /* try pollers with higher preference first */
};
[src/fd.c]
struct fdtab *fdtab = NULL; /* array of all the file descriptors */
struct fdinfo *fdinfo = NULL; /* less-often used infos for file descriptors */
int maxfd; /* # of the highest fd + 1 */
int totalconn; /* total # of terminated sessions */
int actconn; /* # of active sessions */
struct poller pollers[MAX_POLLERS];
struct poller cur_poller;
int nbpollers = 0;
在看到fdtab和poller的结构体时,然后查看ev_epoll.c的时候可能会奇怪为什么会设置成这样。但是如果先查看ev_sepoll.c的话可能很多疑惑都没有了。
sepoll
在Haproxy中,作者在epoll上将模型推进至sepoll(我不知道是否在此之前就有人提出或者使用这种方法),从理论上来说,这种模型的总体效率应该比epoll更好,虽然说它是基于epoll的,因为它能够减少较多与epoll相关的昂贵的系统调用。
sepoll,作者在代码注释中称为speculative I/O。Sepoll的原理就是,对于刚accept完的套接字描述符,一般都是直接能够读取导数据的;对于connect完的描述符,一般都是可写的;即使是对于在传输数据的链接,它也是能提升效率的,因为假设对于某一条链接的某端已经处于epoll的等待队列中,那么另一端也是需要做出反应的,要么发送数据,要么接收数据,这依赖于(读/写)缓冲区的水位。
当然,作者也描述了sepoll的缺点,那就是这可能会导致在epoll队列中的可用事件缺少而变得饥饿(starve the polled events)(我对此处饥饿的理解是,有足够资源的时候不给予需要的人;poll本来就是用于处理多个描述符专用,假设只处理几个描述符,那么poll根本就提升不了多少性能,因为它本身也是系统调用,因此需要保持poll队列含有一定数量的fd,否则就是出现饥饿情况),作者说实验证明,当epoll队列出现饥饿的情况时,压力会转到spec I/O上面,此时由于每次去读取或者写入,但是都失败,陷入恶性循环,会严重的降低系统性能(spec list描述符较多,一直轮询肯定会导致性能问题)。用于解决此问题的方法,可以通过减少epoll一次处理的事件来解决这个问题(对spec list的不能使用这个方法,因为实验显示,spec list中2/3的fd是新的,只有1/3的fd是老的)。作者说这是基于以下两点事实,第一,对于位于spec list的fd,不能也将它们注册在epoll中等待;第二是,即使在系统压力非常大的时候,我们基本上也不会同时对同一个fd进行读