【Jabberd2源码剖析系列 mio】

  mio是一个xmpp的I/O复用事件库, 对开发者提供透明API进行开发, 而在API之下允许灵活使用不同的I/O复用作为底层实现.

  mio采用了大量的宏替换, 实现了 开发者API -> 底层实现 的解耦, 该解耦逻辑发生在预编译阶段, 而不是我们习惯的运行阶段解耦技巧.

  mio暴露给用户的API是固定不变的, 这些API的声明存在于mio.h中, 如下:

  1, 描述符:

typedef struct mio_fd_st

{

    int fd; 

} *mio_fd_t;

  2, mio核心结构体: 内部看似采用了函数指针解耦, 但其实各个函数指针最终都指向了固定的函数, 并不是用来解耦底层I/O复用实现而存在的, 这处设计很容易造成误解.

typedef enum { action_ACCEPT, action_READ, action_WRITE, action_CLOSE } mio_action_t;

typedef int (*mio_handler_t) (struct mio_st **m, mio_action_t a, struct mio_fd_st *fd, void* data, void *arg);



typedef struct mio_st

{

  void (*mio_free)(struct mio_st **m);



  struct mio_fd_st *(*mio_listen)(struct mio_st **m, int port, char *sourceip,

                  mio_handler_t app, void *arg);



  struct mio_fd_st *(*mio_connect)(struct mio_st **m, int port, char *hostip,

                   char *srcip, mio_handler_t app, void *arg);



  struct mio_fd_st *(*mio_register)(struct mio_st **m, int fd, 

                   mio_handler_t app, void *arg);



  void (*mio_app)(struct mio_st **m, struct mio_fd_st *fd,

          mio_handler_t app, void *arg);



  void (*mio_close)(struct mio_st **m, struct mio_fd_st *fd);



  void (*mio_write)(struct mio_st **m, struct mio_fd_st *fd);



  void (*mio_read)(struct mio_st **m, struct mio_fd_st *fd);



  void (*mio_run)(struct mio_st **m, int timeout);

} **mio_t;

  3, API: 直接与用户接触, 其中m是上述mio结构体. 即直接与用户接触的两样东西是struct mio_st, 以及这些API.

/** create/free the mio subsytem */

JABBERD2_API mio_t mio_new(int maxfd); /* returns NULL if failed */



#define mio_free(m) (*m)->mio_free(m)



/** for creating a new listen socket in this mio (returns new fd or <0) */

#define mio_listen(m, port, sourceip, app, arg) \

    (*m)->mio_listen(m, port, sourceip, app, arg)



/** for creating a new socket connected to this ip:port (returns new fd or <0, use mio_read/write first) */

#define mio_connect(m, port, hostip, srcip, app, arg) \

    (*m)->mio_connect(m, port, hostip, srcip, app, arg)



/** for adding an existing socket connected to this mio */

#define mio_register(m, fd, app, arg) \

    (*m)->mio_register(m, fd, app, arg)



/** re-set the app handler */

#define mio_app(m, fd, app, arg) (*m)->mio_app(m, fd, app, arg)



/** request that mio close this fd */

#define mio_close(m, fd) (*m)->mio_close(m, fd)



/** mio should try the write action on this fd now */

#define mio_write(m, fd) (*m)->mio_write(m, fd)



/** process read events for this fd */

#define mio_read(m, fd) (*m)->mio_read(m, fd)



/** give some cpu time to mio to check it's sockets, 0 is non-blocking */

#define mio_run(m, timeout) (*m)->mio_run(m, timeout)



/** all MIO related routines should use those for error reporting */

#ifndef _WIN32

# define MIO_ERROR       errno

# define MIO_SETERROR(e) (errno = e)

# define MIO_STRERROR(e) strerror(e)

# define MIO_WOULDBLOCK  (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN)

  上述并没有涉及到任何实际函数声明, 都是函数指针.

  这些API的实现主要集中在mio_impl.h中, 在该文件中, 实现了对应上面struct mio_st结构体中的各个函数指针对应的实际实现, 这些函数实现是固定不变的, 也就是之前提到的struct mio_st结构体在设计上有一点迷惑阅读者, 简单的说, 作者希望在C中实现面向对象的思想, 只好用函数指针配合C全局函数实现类似的风格.

  1, 在API层实现中, 用了一些私有定义, 这些定义不会暴露给用户: mio_type_t是一个fd所处的状态, 特别需要注意:

type_CLOSED: fd应当被关闭, 但暂时还不能执行, 该状态存在的原因受限于作者的实现, 后面会详细说明.
type_CONNECT_READ: fd用于connect主动连接其他服务端, 并且希望完成connect后注册read事件.
type_CONNECT_WRITE: 同上, 完成connect后希望注册write事件. 这两个状态是为了满足常见的编程需求, 因为connect是由mio框架负责跟踪处理的, 所以允许用户在进行connect之前告知希望监听的事件, 在mio框架完成connect后框架会帮用户注册事件.

mio_priv_fd_st: 相对于暴露给用户的struct mio_fd_s结构体而言, mio API内部实现关注比用户更多的信息, 比如连接状态type, 回调函数app, 回调用户参数arg, 以及在API层的下层实现需要的数据, 即MIO_FD_VARS, 这是一个宏, 由下层实现提供支持.

mio_priv_st:相对于暴露给用户的struct mio_st结构体而言, mio API内部需要更多与底层实现相关的信息, 这主要是指:MIO_VARS, 这也是一个宏, 由下层实现提供支持.
可以看到, MIO_FD_VARS, MIO_VARS两个大写宏, 它们是下层实现提供支持的, 下面将会有所了解.

/** our internal wrapper around a fd */

typedef enum {

    type_CLOSED = 0x00,

    type_NORMAL = 0x01,

    type_LISTEN = 0x02,

    type_CONNECT = 0x10, 

    type_CONNECT_READ = 0x11,

    type_CONNECT_WRITE = 0x12

} mio_type_t;



typedef struct mio_priv_fd_st

{

    struct mio_fd_st mio_fd;



    mio_type_t type;

    /* app event handler and data */

    mio_handler_t app;

    void *arg;



    MIO_FD_VARS

} *mio_priv_fd_t;



/** now define our master data type */

typedef struct mio_priv_st

{

    struct mio_st *mio;



    int maxfd;

    MIO_VARS

} *mio_priv_t;

 

2, 作者玩弄指针的宏: 作者为了在心里上彻底的解耦, 在API层实现中用了一些列指针操作, 乍看是不容易懂.

下面的m是mio_t类型, 也就是typedef struct mio_st* *mio_t类型, 即暴露给用户那个mio类型. (注意, mio_t是指向指针的指针)

下面的f是mio_fd_t类型, 也就是struct mio_fd_st *mio_fd_t类型, 即暴露给用户的那个fd类型.

MIO(m)是把mio_t转换成mio_priv_t, 即完成了从用户mio转换成内部mio, 用户手持的mio_t mio(struct mio_st* *mio;)实际上是mio_priv_t中的那个struct mio_st *mio的地址.

FD(m, f)是类似的, 即完成了从用户fd转换成内部fd的过程.

ACT(m, f, a, d)借助上面两个宏, 完成了一次用户回调函数的调用, 因为我们回调的是用户的函数, 所以我们必须完成一次从内部结构体到API结构体的转换, 所以可以看到这样的代码:&FD(m,f)->mio_fd, 即FD宏完成用户到内部的转换, ->mio_fd取出了用户暴露的fd结构体. FD(m,f)->arg即FD宏完成用户到内部结构的转换, 最后取出内部结构中该用户预注册的arg, FD(m,f)->app是取出用户注册的回调函数.

为什么作者弄这么麻烦呢, 因为作者是真的希望让用户完全看不到内部结构体, 是完全... 用户想改内核的东西, 没门! (至少我不会把用户态和内核态(程序内核)分的那么清楚)

#define MIO(m) ((mio_priv_t) m)

#define FD(m,f) ((mio_priv_fd_t) f)

#define ACT(m,f,a,d) (*(FD(m,f)->app))(m,a,&FD(m,f)->mio_fd,d,FD(m,f)->arg)

3, API的实现:摘取几个有代表性的, 主要关注API是如何与底层解耦的. 既然是API, 我们要明确函数的参数都是用户态结构体, 需要借助上述的宏完成外部到内部的结构转换.

对于_mio_read来说, 如果当前fd处于type_CONNECT状态, 表明fd的连接未完成, 此时用户希望读, 我们只能先保留, 待连接完成后再实际的注册用户希望的事件. 而对于普通fd来说, 立即为它注册读事件.

对于_mio_write来说, 原理是类似的, 另外有一个额外操作即立即回调了用户的回调函数, 告知它发生了write事件, 由用户自己完成write操作, 如果用户返回非0, 表明用户没有写完所有数据, 那么我们注册write事件.  对于_mio_write, 通常是这样使用的, 即用户先将一些数据存到了buffer里, 然后调用_mio_write希望写出这些数据, _mio_write立即回调用户的注册回调函数, 给用户立即写出的机会, 但用户可能因为网卡阻塞等无法完全写出, 那么_mio_write会替用户注册write事件, 以便后续触发再次写出.

/** start processing read events */

static void _mio_read(mio_t m, mio_fd_t fd)

{

    if(m == NULL || fd == NULL) return;



    /* if connecting, do this later */

    if(FD(m,fd)->type & type_CONNECT)

    {

        FD(m,fd)->type |= type_CONNECT_READ;

        return;

    }



    MIO_SET_READ(m, FD(m,fd));

}



/** try writing to the socket via the app */

static void _mio_write(mio_t m, mio_fd_t fd)

{

    if(m == NULL || fd == NULL) return;



    /* if connecting, do this later */

    if(FD(m,fd)->type & type_CONNECT)

    {

        FD(m,fd)->type |= type_CONNECT_WRITE;

        return;

    }



    if(FD(m,fd)->type != type_NORMAL)

        return;



    if(ACT(m, fd, action_WRITE, NULL) == 0) return;



    /* not all written, do more l8r */

    MIO_SET_WRITE(m, FD(m,fd));

}

其他API实现不一一列举, 我们可以注意MIO_SET_WRITE(m, FD(m,fd)), MIO_SET_READ(m, FD(m,fd)), 这种大写MIO开头的宏, 这些即使我们所说的底层实现解耦, 作者使用宏在预编译阶段完成解耦. 作者的设计很清晰, 即暴露给用户的是用户态的数据结构与API方法, 上面都已经见到过了, 而在API的实现层, 借助了这些宏实现了底层I/O复用具体实现的解耦, 具体点说, 对于select或者epoll, 它们对于MIO_SET_WRITE宏都有一份各自的实现(注册一个fd的可写事件, 对于select, epoll肯定是各不相同的), 在预编译阶段分别将不同实现的宏包含进来即可实现灵活的I/O复用层替换.

 

在该文件中, 最后一个函数显得比较重要, 它把上面所说的在何处解耦, 作者何处设计有一些迷惑性全部解释清晰.

可以看到, 所有的API函数就像是mio_t的成员函数一样注册到mio_t结构体中, 并且调用了MIO_INIT_VARS(m), 由底层I/O完成自己需要数据的初始化, 比如epoll需要epoll_create创建一个实例.

/** eve */

static mio_t _mio_new(int maxfd)

{

    static struct mio_st mio_impl = {

        _mio_free,

        _mio_listen, _mio_connect, _mio_setup_fd,

        _mio_app,

        _mio_close,

        _mio_write, _mio_read,

        _mio_run

    };

    mio_t m;



    /* init winsock if we are in Windows */

#ifdef _WIN32

    WSADATA wsaData;

    if (WSAStartup(MAKEWORD( 1, 1 ), &wsaData))

        return NULL;

#endif



    /* allocate and zero out main memory */

    if((m = calloc(1, sizeof(struct mio_priv_st))) == NULL) {

        fprintf(stderr,"Cannot allocate MIO memory! Exiting.\n");

        exit(EXIT_FAILURE);

    }



    /* set up our internal vars */

    *m = &mio_impl;

    MIO(m)->maxfd = maxfd;



    MIO_INIT_VARS(m);



    return m;

}         

 

对于mio, 我们需要清晰的知道什么可变的, 什么是不可变的, 什么是给用户暴露的, 什么是作者想藏起来的, 最后就可以开始事件循环了:

同样, 大写的宏是底层实现相关的, 其他代码是底层无关不变的, 可以看到, MIO_CHECK监听了事件, 如果底层实现是epoll, 那么可能就是epoll_wait.

iter是迭代器, 配合MIO_ITERATE_RESULTS用于遍历所有发生事件的fd.

对于deffered是一个需要重点说明的, 可能影响阅读, 稍后解释. 

可以看到, 对于connect, listen的fd而言, 它们并不是监听普通的I/O读写, 而是连接相关的状态, 所以并不需要走type_NORMAL状态相关的分支(即读分支和写分支), 所以直接goto deffered结束处理. 必须郑重提示, 对于connect, listen的fd来说, deffered标签对于它们俩而言只是一个单纯的跳转, 为了避过接下来的两个if判断, deffered标签下的if对于connect,listen的fd来说永远不可能成立, 即deffered标签下的代码仅仅为type_NORMAL的普通fd作用, 稍后分析为什么.

/** main select loop runner */

static void _mio_run(mio_t m, int timeout)

{

    int retval;

    MIO_INIT_ITERATOR(iter);



    mio_debug(ZONE, "mio running for %d sec", timeout);



    /* wait for a socket event */

    retval = MIO_CHECK(m, timeout);



    /* nothing to do */

    if(retval == 0) return;



    /* an error */

    if(retval < 0)

    {   

        mio_debug(ZONE, "MIO_CHECK returned an error (%d)", MIO_ERROR);



        return;

    }   



    mio_debug(ZONE,"mio processing %d file descriptors", retval);



    /* loop through the sockets, check for stuff to do */

    MIO_ITERATE_RESULTS(m, retval, iter)

    {   

        mio_fd_t fd = MIO_ITERATOR_FD(m,iter);

        if (fd == NULL) continue;



        /* skip already dead slots */ 

        if(FD(m,fd)->type == type_CLOSED) continue; 



        /* new conns on a listen socket */

        if(FD(m,fd)->type == type_LISTEN && MIO_CAN_READ(m,iter))

        {   

            _mio_accept(m, fd);

            goto deferred;

        }   



        /* check for connecting sockets */

        if(FD(m,fd)->type & type_CONNECT &&

           (MIO_CAN_READ(m,iter) || MIO_CAN_WRITE(m,iter)))        {

            _mio__connect(m, fd);

            goto deferred;

        }



        /* read from ready sockets */

        if(FD(m,fd)->type == type_NORMAL && MIO_CAN_READ(m,iter))

        {

            /* if they don't want to read any more right now */

            if(ACT(m, fd, action_READ, NULL) == 0)

                MIO_UNSET_READ(m, FD(m,fd));

        }



        /* write to ready sockets */

        if(FD(m,fd)->type == type_NORMAL && MIO_CAN_WRITE(m,iter))

        {

            /* don't wait for writeability if nothing to write anymore */

            if(ACT(m, fd, action_WRITE, NULL) == 0)

                MIO_UNSET_WRITE(m, FD(m,fd));

        }



    deferred:

        /* deferred closing fd

         * one of previous actions might change the state of fd */ 

        if(FD(m,fd)->type == type_CLOSED)

        {

            MIO_FREE_FD(m, fd);

        }

    }

}

 

假设在type_NORMAL的读事件中, ACT用户回调中, 用户调用了mio_close希望关闭该fd, 那么对于不同的底层实现(select/epoll), 效果是不同的, 主要区别即是否该关闭操作被deffered(延迟关闭), 这得提到mio_close的API实现了:

可以看到, 首先MIO_REMOVE_FD从底层I/O复用中移出这个fd的事件监听, 然后ACT(m, fd, action_CLOSE, NULL)回调用户注册函数, 让用户自己决定做点什么, 比如释放arg.

最后, 我们还需要释放掉完成的fd结构体, 但我们发现它做了一个判断, MIO_CAN_FREE(m) -> MIO_FREE_FD(m, fd); 我们的API实现首先询问底层I/O复用实现, 是否可以释放该fd的结构体, 可以则立即释放, 否则不释放(实际上MIO_FREE_FD里已经设置该fd被deffered了).

/** internal close function */

static void _mio_close(mio_t m, mio_fd_t fd) 

{

    if(FD(m,fd)->type == type_CLOSED)

        return;



    mio_debug(ZONE,"actually closing fd #%d", fd->fd);



    /* take out of poll sets */

    MIO_REMOVE_FD(m, FD(m,fd));



    /* let the app know, it must process any waiting write data it has and free it's arg */

    if (FD(m,fd)->app != NULL)

        ACT(m, fd, action_CLOSE, NULL);



    /* close the socket, and reset all memory */

    close(fd->fd);

    FD(m,fd)->type = type_CLOSED;

    FD(m,fd)->app = NULL;

    FD(m,fd)->arg = NULL;



    if (MIO_CAN_FREE(m))

    {   

        MIO_FREE_FD(m, fd);

    }   

}

回头看一下_mio_run, 它在deffered标签下保证了对deffered的fd进行释放.

在一开始我提到, deffered是受限于作者的实现的, 假设我们在_mio_run的type_NOMARL类型fd发生了READ事件, 并在回调中mio_close(fd)希望释放掉该fd结构体, 但因为作者实现中接下来会执行WRITE事件的判断, 还会用到fd结构体, 所以mio_close(fd)需要谨慎判断是否真的可以在调用时刻立即释放内存, 所以引入了deffered逻辑.

 

具体的看, 对于select来说:

可以看到mio_select.h中如下实现, 根据我们对select的认识, 它只是遍历1024个描述符(FD_SETSIZE)依次判断是否在集合里集合, 因此对于select来说, 它的iterator迭代器仅仅是从0-1024的数字即可, 并且对于MIO_ALLOC_FD只是返回了MIO_VARS里预分配的FD_SETSIZE个mio_priv_fd_st之一而已, 因为这是select监听的极限, 这些mio_priv_fd_st不会随着客户端离开而释放, 是随着程序存在的. 自然, 可以考虑上面的情况, READ时候mio_close并立即完成了MIO_FREE_FD, 我们可以发现对于接下来的WRITE事件处理不会造成任何影响, 因为mio_close已经把这个fd设置为type_CLOSED了, if条件根本不会满足, 并且当走到deffered分支时也不会发生任何事, 因为MIO_FREE_FD实际上什么也没做.(mio_close里的MIO_REMOVE_FD已经完成了事件的取消注册, 我们现在关注的deffered关乎到是否释放fd的相关内存)

 

#define MIO_FUNCS \

static void _mio_fds_init(mio_priv_t m) \

{ \

int fd; \

for(fd = 0; fd < m->maxfd; fd++) \

{ \

m->fds[fd].mio_fd.fd = fd; \

} \

m->highfd = 0; \

m->lowfd = m->maxfd; \

} \

\

static mio_fd_t _mio_alloc_fd(mio_priv_t m, int fd) \

{ \

if(fd > m->highfd) m->highfd = fd; \

if(fd < m->lowfd) m->lowfd = fd; \

return &m->fds[fd].mio_fd; \

}



#define MIO_VARS \

struct mio_priv_fd_st *fds; \

int lowfd; \

int highfd; \

fd_set rfds_in, wfds_in, rfds_out, wfds_out;



#define MIO_INIT_VARS(m) \

do { \

if (maxfd > FD_SETSIZE) \

{ \

mio_debug(ZONE,"wanted MIO larger than %d file descriptors", FD_SETSIZE); \

free(m); \

return NULL; \

} \

\

if((MIO(m)->fds = calloc(1, sizeof(struct mio_priv_fd_st) * maxfd)) == NULL) \

{ \

mio_debug(ZONE,"internal error creating new mio"); \

free(m); \

return NULL; \

} \

\

_mio_fds_init(MIO(m)); \

FD_ZERO(&MIO(m)->rfds_in); \

FD_ZERO(&MIO(m)->wfds_in); \

} while(0)



#define MIO_FREE_VARS(m) free(MIO(m)->fds)



#define MIO_ALLOC_FD(m, rfd) _mio_alloc_fd(MIO(m), rfd)

#define MIO_FREE_FD(m, mfd) 
#define MIO_CAN_FREE(m) 1

 

最后简单看一下mio_epoll.h为什么需要deffered吧, 因为它对于每一个客户端分配了一个mio_priv_fd_st结构体, 释放它将影响下面if type_WRITE分支, 所以必须延迟释放.

 

#define MIO_FUNCS \

    static int _mio_poll(mio_t m, int t)                                \

    {                                                                   \

        return epoll_wait(MIO(m)->epoll_fd,                             \

                          MIO(m)->res_event, 32, t*1000);               \

    }                                                                   \

                                                                        \

    static mio_fd_t _mio_alloc_fd(mio_t m, int fd)                      \

    {                                                                   \

        struct epoll_event event;                                       \

        mio_priv_fd_t priv_fd = malloc(sizeof (struct mio_priv_fd_st)); \

        memset(priv_fd, 0, sizeof (struct mio_priv_fd_st));             \

                                                                        \

        priv_fd->mio_fd.fd = fd;                                        \

        priv_fd->events = 0;                                            \

                                                                        \

        event.events = priv_fd->events;                                 \

        event.data.u64 = 0;                                             \

        event.data.ptr = priv_fd;                              \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_ADD, fd, &event);         \

                                                                        \

        return (mio_fd_t)priv_fd;                                        \

    }





#define MIO_FD_VARS \

    uint32_t events;



#define MIO_VARS \

    int defer_free;                                                     \

    int epoll_fd;                                                       \

    struct epoll_event res_event[32];



#define MIO_INIT_VARS(m) \

    do {                                                                \

        MIO(m)->defer_free = 0;                                         \

        if ((MIO(m)->epoll_fd = epoll_create(maxfd)) < 0)               \

        {                                                               \

            mio_debug(ZONE,"unable to initialize epoll mio");           \

            free(m);                                                    \

            return NULL;                                                \

        }                                                               \

    } while(0)

#define MIO_FREE_VARS(m) \

    do {                                                                \

        close(MIO(m)->epoll_fd);                                        \

    } while(0)





#define MIO_ALLOC_FD(m, rfd)    _mio_alloc_fd(m, rfd)

#define MIO_FREE_FD(m, mfd)     if(mfd)free(mfd)



#define MIO_REMOVE_FD(m, mfd) \

    do {                                                                \

        struct epoll_event event;                                       \

        event.events = 0;                                               \

        event.data.u64 = 0;                                             \

        event.data.ptr = mfd;                                           \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_DEL,                      \

                  mfd->mio_fd.fd, &event);                              \

    } while (0)



#define MIO_CHECK(m, t)         _mio_poll(m, t)



#define MIO_SET_READ(m, mfd) \

    do {                                                                \

        struct epoll_event event;                                       \

        mfd->events |= EPOLLIN;                                         \

        event.events = mfd->events;                                     \

        event.data.u64 = 0;                                             \

        event.data.ptr = mfd;                                           \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_MOD,                      \

                  mfd->mio_fd.fd, &event);                              \

    } while (0)



#define MIO_SET_WRITE(m, mfd) \

    do {                                                                \

        struct epoll_event event;                                       \

        mfd->events |= EPOLLOUT;                                        \

        event.events = mfd->events;                                     \

        event.data.u64 = 0;                                             \

        event.data.ptr = mfd;                                           \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_MOD,                      \

                  mfd->mio_fd.fd, &event);                              \

    } while (0)



#define MIO_UNSET_READ(m, mfd) \

    do {                                                                \

        struct epoll_event event;                                       \

        mfd->events &= ~EPOLLIN;                                        \

        event.events = mfd->events;                                     \

        event.data.u64 = 0;                                             \

        event.data.ptr = mfd;                                           \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_MOD,                      \

                  mfd->mio_fd.fd, &event);                              \

    } while (0)

#define MIO_UNSET_WRITE(m, mfd) \

    do {                                                                \

        struct epoll_event event;                                       \

        mfd->events &= ~(EPOLLOUT);                                     \

        event.events = mfd->events;                                     \

        event.data.u64 = 0;                                             \

        event.data.ptr = mfd;                                           \

        epoll_ctl(MIO(m)->epoll_fd, EPOLL_CTL_MOD,                      \

                  mfd->mio_fd.fd, &event);                              \

    } while (0)





#define MIO_CAN_READ(m,iter) \

    (MIO(m)->res_event[iter].events & (EPOLLIN|EPOLLERR|EPOLLHUP))



#define MIO_CAN_WRITE(m,iter) \

    (MIO(m)->res_event[iter].events & EPOLLOUT)



#define MIO_CAN_FREE(m)         (!MIO(m)->defer_free)



#define MIO_INIT_ITERATOR(iter) \

    int iter



#define MIO_ITERATE_RESULTS(m, retval, iter) \

    for(MIO(m)->defer_free = 1, iter = 0; (iter < retval) || ((MIO(m)->defer_free = 0)); iter++)



#define MIO_ITERATOR_FD(m, iter) \

    (MIO(m)->res_event[iter].data.ptr)

 

关于mio就这么多, 其实是非常简单的, 只是作者玩了太多宏, 实在有点架构洁癖..

你可能感兴趣的:(IO)