分类 wifidog原理 下的文章

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(1)---fdevents结构体和fdevent系统对外接口

前面讲了lighttpd的插件系统,这一篇将看一看lighttpd中的fdevent系统。fdevent系统主要是处理各种IO事件,在web服务器中,主要就是向socket写数据和从socket读数据。通常,web服务器是IO密集型程序,这就要求在数据的读写上,web服务器必须能够具有很好的性能,不会因为某个socket的阻塞而致使其他socket也被阻塞,否则会大大降低服务器的性能。因此,大部分的web服务器都采用非阻塞IO进行数据的读写。lighttpd通过fdevent系统,采用类似OO中面向对象的方式将对IO事件的处理进行封装,对于不同的IO系统,提供一个统一的接口。
lighttpd采用了所谓的Reactor模式,也就是非阻塞IO加多路复用(non-blocking IO + IO multiplexing)。在多路复用上,lighttpd通过fdevent将各种不同的实现进行封装。lighttpd使用的多路IO有如下几个:
1.png

下面看一下fdevent.h中fdevents结构体,这个结构体相当于是一个虚基类,其中的函数指针是纯虚函数。对于每种实现,则相当于是继承了这个基类并实现了其中的纯虚函数,也就是给函数指针赋一个函数地址值。下面是代码:

typedef struct fdevents
{
    fdevent_handler_t type; //多路IO类型
     fdnode **fdarray;     //文件描述符数组
     size_t maxfds;         //最大的文件描述符数

#ifdef USE_LINUX_SIGIO
    int in_sigio;
    int signum;
    sigset_t sigset;
    siginfo_t siginfo;
    bitset *sigbset;
 #endif

#ifdef USE_LINUX_EPOLL
    int epoll_fd;
    struct epoll_event *epoll_events;
 #endif

#ifdef USE_POLL
    struct pollfd *pollfds;     //描述符及其状态的结构体数组
     size_t size;                     //数组中数据的个数
     size_t used;                     //数组的大小
    //用于存储pollfds中为使用的位置。
    //由于可能的删除操作,会是pollfds中存在空档,将这些空档
    //的索引存在unused中,便于下次插入操作时直接使用这些空档
    //减少空间的浪费。
     buffer_int unused;     
 #endif

#ifdef USE_SELECT
    //三个文件描述符集合
     fd_set select_read;             //可读,对应FDEVENT_IN
     fd_set select_write;         //可写,对应FDEVENT_OUT
     fd_set select_error;         //处于异常条件,对应FDEVENT_ERR
    //由于select函数会修改上面的三个集合,
    //因此,在这里保存一个初始的副本。
     fd_set select_set_read;
    fd_set select_set_write;
    fd_set select_set_error;
    int select_max_fd;             //最大的文件描述符数。
 #endif

#ifdef USE_SOLARIS_DEVPOLL
    int devpoll_fd;
    struct pollfd *devpollfds;
 #endif

#ifdef USE_FREEBSD_KQUEUE
    int kq_fd;
    struct kevent *kq_results;
    bitset *kq_bevents;
 #endif

#ifdef USE_SOLARIS_PORT
    int port_fd;
 #endif

    //统一的操作接口,与后面的函数声明对应。
     int (*reset) (struct fdevents * ev);
    void (*free) (struct fdevents * ev);
    int (*event_add) (struct fdevents * ev, int fde_ndx, int fd, int events);
    int (*event_del) (struct fdevents * ev, int fde_ndx, int fd);
    int (*event_get_revent) (struct fdevents * ev, size_t ndx);
    int (*event_get_fd) (struct fdevents * ev, size_t ndx);
    int (*event_next_fdndx) (struct fdevents * ev, int ndx);
    int (*poll) (struct fdevents * ev, int timeout_ms);
    int (*fcntl_set) (struct fdevents * ev, int fd);
} fdevents;

可以看到这个结构体中使用很多宏,这是为了在编译的时候去掉那些没有使用到的变量,根据当前所使用的多路IO系统,对这个结构体进行定制。
结构体的第一个成员是一个枚举类型fdevent_handler_t,定义如下:

typedef enum
{
    FDEVENT_HANDLER_UNSET,                 //未定义
     FDEVENT_HANDLER_SELECT,             //select
     FDEVENT_HANDLER_POLL,                 //poll
     FDEVENT_HANDLER_LINUX_RTSIG,         //rtsig
     FDEVENT_HANDLER_LINUX_SYSEPOLL,     //sysepoll
     FDEVENT_HANDLER_SOLARIS_DEVPOLL,     //devpoll
     FDEVENT_HANDLER_FREEBSD_KQUEUE,     //kqueue
     FDEVENT_HANDLER_SOLARIS_PORT         //port
 } fdevent_handler_t;

这个枚举类型标记了所有可能用到的多路IO系统。
结构体中的第二个成员fdnode **fdarray;,是一个fdnode类型变量的数组。fdnode的定义如下:

typedef struct _fdnode
{
    fdevent_handler handler; //处理函数指针
    void *ctx;                //文件描述符的context
    int fd;                  //文件描述符
    struct _fdnode *prev, *next; //指针
} fdnode;

fdevent_handler handler是一个函数指针,用来存储这个描述符处理函数的地址。关于这个地址,后面的文章中将会有详细的介绍,其定义为typedef handler_t(*fdevent_handler) (void *srv, void *ctx, int revents);。从最后两个变量可以看出,这应该是一个链表的节点,但是,这个结构体是以数组的形式存储的,也就是fdevents中的fdarray变量,这样可以提高查询的效率。
后面由宏包裹的变量就是对于各个不同的多路IO系统定义的变量。我们着重看一看linux下的epoll所使用的变量:

#ifdef USE_LINUX_EPOLL
     int epoll_fd; //epoll_create返回的描述符
     struct epoll_event *epoll_events;//保存fd及对应的事件
#endif

这里要说明一下poll使用的变量buffer_init unused。这个变量的类型的定义如下:

typedef struct
{
    int *ptr;         //位置索引数组。
    size_t used;     //数组中数据个数。
    size_t size;     //数组长度。
} buffer_int;

其实就是一个int数组,只不过封装了一下,增加了两个属性。unused数组中存放的是pollfds的下标值。在后面的实现中我们可以看到,pollfds是一个struct pollfd类型数组,注意,不是这个类型的指针的数组。这个数组的大小是根据fdevents中的maxfds的值定的,并且在初始化的时候数组的空间也一次性分配好。由于对pollfds数组有删除元素的操作,因此,会在数组中留下“洞”,而ununsd就是存储这些“洞”的下标值,便于在插入元素时,快速的找到位置。这是一个很有用的技巧,在对数据进行反复的删除插入元素操作时,可以提高效率。大多数情况下使用栈链表来存储这些可用空间的下标,栈顶指向链表头。
其余的变量读者可自行分析。
接下来看看这些函数指针。这些函数指针对应与结构体定义后面的一系列函数声明。从名字中可以轻易的看出对应关系。

/*
 * 重置和释放fdevent系统。
 */
int fdevent_reset(fdevents * ev);
void fdevent_free(fdevents * ev);
/*
 * 将fd增加到fd event系统中。events是要对fd要监听的事件。
 * fde_ndx是fd对应的fdnode在ev->fdarray中的下标值的指针。
 * 如果fde_ndx==NULL,则表示在fd event系统中增加fd。如果不为NULL,则表示这个
 * fd已经在系统中存在,这个函数的功能就变为将对fd监听的事件变为events。
 */
int fdevent_event_add(fdevents * ev, int *fde_ndx, int fd, int events);
/*
 * 从fd event系统中删除fd。 fde_ndx的内容和上面的一致。
 */
int fdevent_event_del(fdevents * ev, int *fde_ndx, int fd);
/*
 * 返回ndx对应的fd所发生的事件。
 * 这里的ndx和上面的fde_ndx不一样,这个ndx是ev->epoll_events中epoll_event结构体的下标。
 * 第一次调用的时候,通常ndx为-1。
 * 这个ndx和其对应的fd没有关系。而fde_ndx等于其对应的fd。
 */
int fdevent_event_get_revent(fdevents * ev, size_t ndx);
/*
 * 返回ndx对应的fd。
 */
int fdevent_event_get_fd(fdevents * ev, size_t ndx);
/*
 * 返回下一个发生IO事件的fd。
 */
int fdevent_event_next_fdndx(fdevents * ev, int ndx);
/*
 * 开始等待IO事件。timeout_ms是超时限制。
 */
int fdevent_poll(fdevents * ev, int timeout_ms);
/**
 * 设置fd的状态,通常是设置为运行exec在子进程中关闭和非阻塞。
 */
int fdevent_fcntl_set(fdevents * ev, int fd);

在fdevent.c文件中,这些函数的实现基本上都是简单的调用fdevents结构体中对应的函数指针。对于lighttpd,通过调用上面的这些函数完成IO事件的处理,对于具体到底是谁处理了这些事件,lighttpd并不知道,也不关心。
剩下的函数声明:

/*
 * 返回fd对应的事件处理函数地址。也就是fdnode中handler的值。
 */
fdevent_handler fdevent_get_handler(fdevents * ev, int fd);
/*
 * 返回fd对应的环境。也就是fdnode中ctx的值。
 */
void *fdevent_get_context(fdevents * ev, int fd);

/*
 * 注册和取消注册fd。
 * 就是生成一个fdnode,然后保存在ev->fdarray中。或者删除之。
 */
int fdevent_register(fdevents * ev, int fd, fdevent_handler handler, void *ctx);
int fdevent_unregister(fdevents * ev, int fd);
/**
 * 初始化各种多路IO。
 */
int fdevent_select_init(fdevents * ev);
int fdevent_poll_init(fdevents * ev);
int fdevent_linux_rtsig_init(fdevents * ev);
int fdevent_linux_sysepoll_init(fdevents * ev);
int fdevent_solaris_devpoll_init(fdevents * ev);
int fdevent_freebsd_kqueue_init(fdevents * ev);

下面总结一下:
文件fdevent.h中声明的一系列函数就是fdevent系统对外的接口,这相当与类的公有函数。lighttpd通过调用这些函数来实现IO事件的处理。在这些函数的具体实现仅仅是简单的调用了fdevents结构体中的函数指针。而这写函数指针所对应的函数分别定义在以fdevent_开头的.c文件中。从这些文件的名字可以看出其所对应的IO系统。在这些文件中,函数大多是static,这就行当与类的私有函数,起到隐藏具体实现的效果。后面的问装中我们会具体的分析一个多路IO系统的使用。
lighttpd作者对fdevent系统封装相当的出彩,对于理解在C中使用面向对象的方式编程具有很好的帮助。
下一篇中将看一看fdevent系统的初始化和使用。

本文章由 http://www.wifidog.pro/2015/04/20/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%AF%B9%E5%A4%96%E6%8E%A5%E5%8F%A3.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之插件系统(3)---PLUGIN_TO_SLOT宏

前面讲了lighttpd插件系统的加载和初始化,这一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT。
在将PLUGIN_TO_SLOT宏之前,我们先来看看lighttpd中插件系统的对外接口。这个接口所对的“外”指的是lighttpd服务器。前面已经提到,在运行的过程中,lighttpd不知道所加载的插件都是干什么用的,只知道这些插件所实现的接口,也就是在plugin结构体中那些函数指针有哪些对于某个插件是NULL,哪些是具体的函数地址。
既然lighttpd只知道这些,那么它又是怎样调用这些插件的呢?
答案就在plugin.h文件中的下面一系列函数声明:

handler_t plugins_call_handle_uri_raw(server * srv, connection * con);
handler_t plugins_call_handle_uri_clean(server * srv,connection * con);
handler_t plugins_call_handle_subrequest_start(server * srv,connection * con);
handler_t plugins_call_handle_subrequest(server * srv,connection * con);
handler_t plugins_call_handle_request_done(server * srv,connection * con);
handler_t plugins_call_handle_docroot(server * srv,connection * con);
handler_t plugins_call_handle_physical(server * srv,connection * con);
handler_t plugins_call_handle_connection_close(server * srv,connection * con);
handler_t plugins_call_handle_joblist(server * srv,connection * con);
handler_t plugins_call_connection_reset(server * srv,connection * con);
handler_t plugins_call_handle_trigger(server * srv);
handler_t plugins_call_handle_sighup(server * srv);
handler_t plugins_call_init(server * srv);
handler_t plugins_call_set_defaults(server * srv);
handler_t plugins_call_cleanup(server * srv);

这些函数就是插件系统对外的接口。在运行过程中,lighttpd靠调用上面的这些函数调用插件。比如:在server.c的main函数中,就调用了plugins_call_set_defaults函数:

if (HANDLER_GO_ON != plugins_call_set_defaults(srv))
    {
        log_error_write(srv, __FILE__, __LINE__, "s",
                "Configuration of plugins failed. Going down.");
        plugins_free(srv);
        network_close(srv);
        server_free(srv);
        return -1;
    }

如果你使用ctags+vim看代码,当在这些函数的调用处想跳转到定义时发现ctags没有找到这些函数的定义。难道这些函数没有实现?这显然是不会的。其实,这正是由于本文的重点───PLUGIN_TO_SLOT宏造成的。
打开plugin.c文件,会发现这几行代码:

PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE,handle_request_done)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,handle_connection_close)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START,handle_subrequest_start)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults)

再看看PLUGIN_SLOT宏的前两行:

#define PLUGIN_TO_SLOT(x, y) \
    handler_t plugins_call_##y(server *srv, connection *con) {\

这下明白了吧。上面那些函数是由这些宏调用模板化生成的。由于这些函数的代码具有很高的相似度(仅仅是调用的插件函数不同),通过宏模板进行生成,可以节约大量的代码,同时又不容易出错。这类似于C++中的模板。注:C语言预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则改参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。(摘自:C语言程序设计 K&R)在这里,plugins_call_和实参y拼接成函数名。
下面我们着重分析一下PLUGIN_SLOT宏的内容:

#define PLUGIN_TO_SLOT(x, y) \
    handler_t plugins_call_##y(server *srv, connection *con) {\
        plugin **slot;\
        size_t j;\
        if (!srv->plugin_slots) return HANDLER_GO_ON;\
        slot = ((plugin ***)(srv->plugin_slots))[x];\
        if (!slot) return HANDLER_GO_ON;\
        for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
            plugin *p = slot[j];\
            handler_t r;\
            switch(r = p->y(srv, con, p->data)) {\
            case HANDLER_GO_ON:\
                break;\
            case HANDLER_FINISHED:\
            case HANDLER_COMEBACK:\
            case HANDLER_WAIT_FOR_EVENT:\
            case HANDLER_WAIT_FOR_FD:\
            case HANDLER_ERROR:\
                return r;\
            default:\
                log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\
                return HANDLER_ERROR;\
            }\
        }\
        return HANDLER_GO_ON;\
    }

根据后面的宏调用可以看出,参数x是plugin_t枚举类型,y是plugin结构体中函数指针成名的名字。在宏调用中,x和y是相对应的。
PLUGIN_SLOT宏首先通过参数y拼接出函数名。如:这个宏调用
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean),拼接得到的函数名为plugins_call_handle_uri_clean,加上参数和返回值,正好是plugin.h中的函数handler_t plugins_call_handle_uri_clean(server * srv, connection * con)。其他的以此类推。
这条语句slot = ((plugin ***)(srv->plugin_slots))[x];通过宏参数x得到plugin_slots的第x列。plugin_slots的结构在前面的文章中已经讲解过了。不熟悉的读者可以再回头看看。这列中包含所有具有参数x所对应的功能的插件的指针。也就是,plugin结构体的成员变量y不为NULL的所有plugin实例的指针。接着,在for循环中,调用这些插件的y函数,就是这句:switch(r = p->y(srv, con, p->data))。后面就是一些返回值和错误处理了。
读者也许早就发现在plugin.c文件中有两个PLUGIN_SLOT宏。猛地一看没有任何差别。确实,着两个宏基本上都一样,只有一点不同:第二个宏的switch语句中调用y函数时,参数少了一个con:switch(r = p->y(srv, p->data))。这是他们唯一的差别。读者可以看看plugin结构体中的函数指针,有四个是两个参数的(server * srv, void *p_d),其他都是三个参数(server * srv, connection * con, void *p_d)。
在这里有一个很有意思的问题。我们注意到,在所有plugins_call_XXX函数中,由于都是通过上面的PLUGIN_SLOT宏生成的。那么,这些函数在被lighttpd进行调用的时候,无论来的请求想要干什么,lighttpd都会逐一调用所有插件对应的函数。这就有一个问题了:如果所调用的第一个插件所具有的功能不是这个连接所想要的功能,这不就出错了么?但是反过来想想,既然要隐藏插件的所有细节,那么lighttpd也就无从知道哪些插件是干什么的。因此,对与一个连接,lighttpd也就不会知道使用哪个插件进行处理。因此,这样做也是没办法的事情。虽然这样会影响服务器的效率,毕竟每次都调用了很多无用的函数,但却有助于服务器的扩展。效率换扩展性,关键要把握一个度。
那么lighttpd无法确定连接该用哪个插件来处理,那么插件自己就必须知道哪些连接是自己该处理的。这涉及到连接的细节处理,我们先暂且放一放。从下一篇开始,将讲解一下fd event系统,在分析具体的连接的处理时,在讲解这个问题。
下一篇,介绍lighttpd中的fdevents结构体和fd eve

本文章由 http://www.wifidog.pro/2015/04/17/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E6%8F%92%E4%BB%B6%E5%AE%8F%E5%AE%9A%E4%B9%89.html 整理编辑,转载请注明出处

wifidog认证源码分析Lighttpd1.4.20源码分析之插件系统(2)---插件的加载和初始化(2)

前面提到了main函数调用plugins_call_init函数对所有插件进行初始化,下面接着介绍
plugins_call_init函数在plugin.c文件中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 handler_t plugins_call_init(server * srv)
{
    size_t i;
    plugin **ps;
    ps = srv->plugins.ptr;
    /*
     * fill slots 
     */
    srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
    for (i = 0; i < srv->plugins.used; i++)
    {
        size_t j;
        /*
         * check which calls are supported 
         */
        plugin *p = ps[i];
    /**
     * 对所有的plugin进行登记造册。这个宏在后文中着重讲解。
     */
#define PLUGIN_TO_SLOT(x, y) \
    if (p->y) { \
        plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
        if (!slot) { \
            slot = calloc(srv->plugins.used, sizeof(*slot));\
            ((plugin ***)(srv->plugin_slots))[x] = slot; \
        } \
        for (j = 0; j < srv->plugins.used; j++) { \
            if (slot[j]) continue;\
            slot[j] = p;\
            break;\
        }\
    }
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,  handle_connection_close);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
        PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
#undef PLUGIN_TO_SLOT
        //对插件进行初始化,调用其初始化函数
        if (p->init)
        {
            if (NULL == (p->data = p->init()))
            {
                log_error_write(srv, __FILE__, __LINE__, "sb", "plugin-init failed for module", p->name);
                return HANDLER_ERROR;
            }
            /*
             * used for con->mode,DIRECT==0,plugins above that 
             */
            ((plugin_data *) (p->data))->id = i + 1;
            //这里检测插件的版本是否和当前服务器的版本相同。
            //这里保证如果以后插件的接口发生了改变,不会造成服务器崩溃。
            if (p->version != LIGHTTPD_VERSION_ID)
            {
                log_error_write(srv, __FILE__, __LINE__, "sb","plugin-version doesn't match lighttpd-version for", p->name);
                return HANDLER_ERROR;
            }
        } 
        else
        {
            p->data = NULL;
        }
    }
    return HANDLER_GO_ON;
}

整个函数中,这个宏是重点:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #define PLUGIN_TO_SLOT(x, y) \
    if (p->y) { \
        plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
        if (!slot) { \
            slot = calloc(srv->plugins.used, sizeof(*slot));\
            ((plugin ***)(srv->plugin_slots))[x] = slot; \
        } \
        for (j = 0; j < srv->plugins.used; j++) { \
            if (slot[j]) continue;\
            slot[j] = p;\
            break;\
        }\
    }

在结构体server中,plugin_slots是一个void指针。在这个宏中可以看到,plugin_slots被转换成了plugin结构体的三级指针。朝前看:
srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
plugin_slots是一个存放ps类型数据的数组,数组的长度为PLUGIN_FUNC_SIZEOF。PLUGIN_FUNC_SIZEOF在后面说明。ps的类型是plugin结构体的二级指针。在上面的宏中,我们看到,plugin_slots是一个数组,随后的if分支中可以看到,plugin_slots的元素也是数组。因此,plugin_slots是一个二维数组,数组中的元素是plugin结构体的指针,并且,plugin_slots是动态创建的。
下面在来看PLUGIN_FUNC_SIZEOF,它定义在下面的枚举结构中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 typedef enum 
{
    PLUGIN_FUNC_UNSET,
    PLUGIN_FUNC_HANDLE_URI_CLEAN,
    PLUGIN_FUNC_HANDLE_URI_RAW,
    PLUGIN_FUNC_HANDLE_REQUEST_DONE,
    PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
    PLUGIN_FUNC_HANDLE_TRIGGER,
    PLUGIN_FUNC_HANDLE_SIGHUP,
    PLUGIN_FUNC_HANDLE_SUBREQUEST,
    PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
    PLUGIN_FUNC_HANDLE_JOBLIST,
    PLUGIN_FUNC_HANDLE_DOCROOT,
    PLUGIN_FUNC_HANDLE_PHYSICAL,
    PLUGIN_FUNC_CONNECTION_RESET,
    PLUGIN_FUNC_INIT,
    PLUGIN_FUNC_CLEANUP,
    PLUGIN_FUNC_SET_DEFAULTS,

    PLUGIN_FUNC_SIZEOF
} plugin_t;

从枚举结构的名字可以看出,这个枚举类型定义了插件的功能的类型,对应着plugin结构体中那些函数指针。而最后一个量,PLUGIN_FUNC_SIZEOF,根据枚举类型的特点,正好是上面所定义的类型的数量。这是一个很常用的技巧,这样可以保证在增加类型的时候,保证程序中可以得到正确的类型数量,而不要去改动那些需要类型数量的代码。
接着回到上面的宏,这个宏有两个参数,有后面的使用可以看出,第一个参数x是枚举类型plugin_t,第二个参数x对应的在plugin结构体中函数指针的名称。因此,上面的宏的作用就是:根据参数x所指定的插件功能类型,判断插件p中是否含有功能x,也就是指针p->y是否非NULL。如果包含功能x,则将指针p添加到数组plugin_slots的第x行中。内层的if语句是为了判断plugin_slots的第x行是否存在,不存在则创建之。for循环是为了将p添加到plugin_slots的第x行的末尾。
例如,插件*p1, *p2, *p3,在执行完后面那些宏调用之后,会形成一个如下的表:
1.png

从表中可以看出,插件p1包含所有的功能,也就是实现了plugin结构体中函数指针对应的所有函数。插件p2不包含功能HANDLE_SUBREQUEST,HANDLE_SUBREQUEST_START和HANDLE_REQUEST_DONE功能,插件p3中不包含
HANDLE_TRIGGER,HANDLE_SIGHUP和CONNECTION_RESET功能。当然,这仅仅是举个例子。
上面的例子中的表就是plugins_slots。这个宏和后面的宏调用可以看成是给所有的插件“登记造册”。通过plugins_slots数组,可以快速的确定某个功能都有那些插件实现了,这方便后面的插件的调用。
完成这些宏调用后,初始化函数测试插件是否定义了init函数。如果定义了则调用之。这里的init函数和前面加载函数中的XXX_plugin_init函数不一样。XXX_plugin_init初始化函数是初始化plugin结构体,核心工作是对plugin结构体中的函数指针进行赋值。而init函数则是初始化这个插件对应的plugin_data结构体,分配数据空间,初始化成员变量并返回其指针。
如:mod_cgi.c的init函数定义为,

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 INIT_FUNC(mod_cgi_init)
{
    plugin_data *p;
    p = calloc(1, sizeof(*p));
    assert(p);
    p->tmp_buf = buffer_init();
    p->parse_response = buffer_init();
    return p;
}

返回的指针存放在plugin结构体中的data成员中。然后对data中的id进行赋值。接着,检查插件的版本是否和当前服务器的版本相同。
如果插件没有定义init函数,则data赋值NULL。
至此,插件的加载和初始化工作全部完成了。下面总结一下整个加载和初始化的过程:
(1)根据配置文件从相应的目录中加载插件的动态连接库。
(2)获得插件动态库中XXX_plugin_init函数的入口地址并调用之。此函数对plugin结构体进行赋值。
(3)在server结构体中注册插件。
(4)调用plugins_call_init初始化插件。
(5)通过上面那个宏及后面一系列的宏调用,将插件登记造册,记录在server结构体的plugins_slots成员中。plugins_slot是一个二维数组,数组成员是plugin结构体指针。
(6)最后调用插件的init函数初始化各自的plugin_data结构体。
下一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT。

本文章由 http://www.wifidog.pro/2015/04/17/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E6%8F%92%E4%BB%B6%E5%8A%A0%E8%BD%BD%E5%88%9D%E5%A7%8B%E5%8C%96-2.html 整理编辑,转载请注明出处

wifidog认证源码分析Lighttpd1.4.20源码分析之插件系统(2)---插件的加载和初始化(1)

前面讲了lighttpd插件系统的接口,下面我们来看看插件是怎么加载 和初始化的。
lighttpd的插件是以动态链接库的形式存在的。在服务器启动的时候,在初始化阶段将所有插件都加载进来。在server.c中的main函数中,加载插件是调用plugins_load函数:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 if (plugins_load(srv))
{
    log_error_write(srv, __FILE__, __LINE__, "s","loading plugins finally failed");
    plugins_free(srv);
    server_free(srv);
    return -1;
}

请读者注意一下这个函数调用的位置。这个函数是在服务器的初始化阶段进行调用的,并且该函数就在这调用了一次,其他地方没有再被调用过。虽然插件是以动态链接库的形式存在,但这些库是在服务器启动阶段一次性加载完毕,如果想再增加插件,只能配置好配置文件后重新启动服务器。
下面看一看plugins_load函数的实现:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #ifdef LIGHTTPD_STATIC
 int plugins_load(server * srv)
{
    plugin *p;
 #define PLUGIN_INIT(x)\
    p = plugin_init(); \
    if (x ## _plugin_init(p)) { \
        log_error_write(srv, __FILE__, __LINE__, "ss",#x, "plugin init failed" ); \
        plugin_free(p); \
        return -1;\
    }\
    plugins_register(srv, p);
#include "plugin-static.h"
    return 0;
}
 #else
 //动态链接
 int plugins_load(server * srv)
{
    plugin *p;
    int (*init) (plugin * pl);
    const char *error;
    size_t i;
    for (i = 0; i < srv->srvconf.modules->used; i++)
    {
        //获得动态链接库的名称。
         data_string *d = (data_string *) srv->srvconf.modules->data[i];
        char *modules = d->value->ptr;
        //库所在目录
         buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);

        buffer_append_string_len(srv->tmp_buf,     CONST_STR_LEN("/"));
        //拼接库的名称。
         buffer_append_string(srv->tmp_buf, modules);
        buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(".so"));
        p = plugin_init();
                //linux调用函数dlopen加载动态库
         if (NULL == ( p->lib = dlopen(srv->tmp_buf->ptr, RTLD_NOW | RTLD_GLOBAL)))
        {
            log_error_write(srv, __FILE__, __LINE__, "sbs", "dlopen() failed for:", srv->tmp_buf, dlerror());
            plugin_free(p);
            return -1;
        }
        //调用动态库中的XXX_plugin_init函数。
        //XXX是库的名称
         buffer_reset(srv->tmp_buf);
        buffer_copy_string(srv->tmp_buf, modules);
        buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("_plugin_init"));
 #if 1
        //调用dlsym函数获得XXX_plugin_init函数的地址。
         init = (int (*)(plugin *)) (intptr_t) dlsym(p->lib, srv->tmp_buf->ptr);
 #else
        //这句没有用
         *(void **) (&init) = dlsym(p->lib, srv->tmp_buf->ptr);
 #endif
        if ((error = dlerror()) != NULL)
        {
            log_error_write(srv, __FILE__, __LINE__, "s", error);
            plugin_free(p);
            return -1;
        }
        //初始化插件
    //在初始化的过程中,模块将自己所有的对外接口函数的入口地址都存入到p中。
         if ((*init) (p))
        {
            log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed");
            plugin_free(p);
            return -1;
        }
 #if 0
        log_error_write(srv, __FILE__, __LINE__, "ss", modules,
                        "plugin loaded");
 #endif
        plugins_register(srv, p);
    }
    return 0;
}
#endif //end of #ifdef LIGHTTPD_STATIC

上面的函数中删除了处理在windows下加载动态链接库的部分。有兴趣的读者可以自行查看源代码。下面全部讨论在linux下的实现。
这个函数作者编写了两个版本,从宏LIGHTTPD_STATIC可以看出,作者貌似是想提供一个加载静态链接库的版本。但是,该版本的函数并没有什么实质性的实现(在最新的版本中(1.4.26),该函数依然没有实现)。我们主要来看看后面的动态链接库的版本。
加载的过程如下:
(1)从配置文件中读取到动态链接库所在的文件夹和动态库的名称。
(2)创建一个plugin结构体的实例。
(3)调用dlopen函数加载动态库。在plugin结构体中保存返回的句柄。
(4)通过dlsym函数获得XXXXXX_plugin_init函数的地址。其中XXXXXX是配置文件中定义的这个插件的内容。
(5)调用XXXXXX_plugin_init函数。
(6)调用plugins_register函数注册插件。
XXXXXX_plugin_init函数在插件中定义,对这个插件进行初始化。其中,最重要的部分就是对plugin结构体中那一系列的函数指针进行赋值。如,mod_cgi模块中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 int mod_cgi_plugin_init(plugin * p)
{
    p->version = LIGHTTPD_VERSION_ID;
    p->name = buffer_init_string("cgi");
    p->connection_reset = cgi_connection_close_callback;
    p->handle_subrequest_start = cgi_is_handled;
    p->handle_subrequest = mod_cgi_handle_subrequest;
#if 0
    p->handle_fdevent = cgi_handle_fdevent;
#endif
    p->handle_trigger = cgi_trigger;
    p->init = mod_cgi_init;
    p->cleanup = mod_cgi_free;
    p->set_defaults = mod_fastcgi_set_defaults;
    p->data = NULL;
    return 0;
}

plugins_register函数是将plugin结构体的指针存放在server结构体的plugins数组中。
插件加载完毕之后,main函数有处理了一些初始化的工作,然后,调用plugins_call_init函数对所有插件进行初始化:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 if (HANDLER_GO_ON != plugins_call_init(srv)) 
{
    log_error_write(srv, __FILE__, __LINE__, "s",
            "Initialization of plugins failed. Going down.");
    plugins_free(srv);
    network_close(srv);
    server_free(srv);
    return -1;
}

本文章由 http://www.wifidog.pro/2015/04/17/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%8A%A0%E8%BD%BD%E5%92%8C%E5%88%9D%E5%A7%8B%E5%8C%96-1.html 整理编辑,转载请注明出处