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文件中的下面一系列函數聲明:html

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函數:vim

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宏的前兩行:ide

#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;\
    }
宏 PLUGIN_TO_SLOT

根據後面的宏調用能夠看出,參數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 evespa

本文章由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 整理編輯,轉載請註明出處插件

相關文章
相關標籤/搜索