前面讲了lighttpd插件系统的接口,下面我们来看看插件是怎么加载
和初始化的。
lighttpd的插件是以动态链接库的形式存在的。在服务器启动的时候,在初始化阶段将所有插件都加载进来。在server.c中的main函数中,加载插件是调用plugins_load函数:
1 if (plugins_load(srv))
2 {
3 log_error_write(srv, __FILE__, __LINE__, "s","loading plugins finally failed");
4 plugins_free(srv);
5 server_free(srv);
6 return -1;
7 }
2 {
3 log_error_write(srv, __FILE__, __LINE__, "s","loading plugins finally failed");
4 plugins_free(srv);
5 server_free(srv);
6 return -1;
7 }
请读者注意一下这个函数调用的位置。这个函数是在服务器的初始化阶段进行调用的,并且该函数就在这调用了一次,其他地方没有再被调用过。虽然插件是以动态链接库的形式存在,但这些库是在服务器启动阶段一次性加载完毕,如果想再增加插件,只能配置好配置文件后重新启动服务器。
下面看一看plugins_load函数的实现:
1 #ifdef LIGHTTPD_STATIC
2 int plugins_load(server * srv)
3 {
4 plugin *p;
5 #define PLUGIN_INIT(x)\
6 p = plugin_init(); \
7 if (x ## _plugin_init(p)) { \
8 log_error_write(srv, __FILE__, __LINE__, "ss",#x, "plugin init failed" ); \
9 plugin_free(p); \
10 return -1;\
11 }\
12 plugins_register(srv, p);
13 #include "plugin-static.h"
14 return 0;
15 }
16 #else
17 //动态链接
18 int plugins_load(server * srv)
19 {
20 plugin *p;
21 int (*init) (plugin * pl);
22 const char *error;
23 size_t i;
24 for (i = 0; i < srv->srvconf.modules->used; i++)
25 {
26 //获得动态链接库的名称。
27 data_string *d = (data_string *) srv->srvconf.modules->data[i];
28 char *modules = d->value->ptr;
29 //库所在目录
30 buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);
31
32 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/"));
33 //拼接库的名称。
34 buffer_append_string(srv->tmp_buf, modules);
35 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(".so"));
36 p = plugin_init();
37 //linux调用函数dlopen加载动态库
38 if (NULL == ( p->lib = dlopen(srv->tmp_buf->ptr, RTLD_NOW | RTLD_GLOBAL)))
39 {
40 log_error_write(srv, __FILE__, __LINE__, "sbs", "dlopen() failed for:", srv->tmp_buf, dlerror());
41 plugin_free(p);
42 return -1;
43 }
44 //调用动态库中的XXX_plugin_init函数。
45 //XXX是库的名称
46 buffer_reset(srv->tmp_buf);
47 buffer_copy_string(srv->tmp_buf, modules);
48 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("_plugin_init"));
49 #if 1
50 //调用dlsym函数获得XXX_plugin_init函数的地址。
51 init = (int (*)(plugin *)) (intptr_t) dlsym(p->lib, srv->tmp_buf->ptr);
52 #else
53 //这句没有用
54 *(void **) (&init) = dlsym(p->lib, srv->tmp_buf->ptr);
55 #endif
56 if ((error = dlerror()) != NULL)
57 {
58 log_error_write(srv, __FILE__, __LINE__, "s", error);
59 plugin_free(p);
60 return -1;
61 }
62 //初始化插件
63 //在初始化的过程中,模块将自己所有的对外接口函数的入口地址都存入到p中。
64 if ((*init) (p))
65 {
66 log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed");
67 plugin_free(p);
68 return -1;
69 }
70 #if 0
71 log_error_write(srv, __FILE__, __LINE__, "ss", modules,
72 "plugin loaded");
73 #endif
74 plugins_register(srv, p);
75 }
76 return 0;
77 }
78 #endif //end of #ifdef LIGHTTPD_STATIC
2 int plugins_load(server * srv)
3 {
4 plugin *p;
5 #define PLUGIN_INIT(x)\
6 p = plugin_init(); \
7 if (x ## _plugin_init(p)) { \
8 log_error_write(srv, __FILE__, __LINE__, "ss",#x, "plugin init failed" ); \
9 plugin_free(p); \
10 return -1;\
11 }\
12 plugins_register(srv, p);
13 #include "plugin-static.h"
14 return 0;
15 }
16 #else
17 //动态链接
18 int plugins_load(server * srv)
19 {
20 plugin *p;
21 int (*init) (plugin * pl);
22 const char *error;
23 size_t i;
24 for (i = 0; i < srv->srvconf.modules->used; i++)
25 {
26 //获得动态链接库的名称。
27 data_string *d = (data_string *) srv->srvconf.modules->data[i];
28 char *modules = d->value->ptr;
29 //库所在目录
30 buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);
31
32 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/"));
33 //拼接库的名称。
34 buffer_append_string(srv->tmp_buf, modules);
35 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(".so"));
36 p = plugin_init();
37 //linux调用函数dlopen加载动态库
38 if (NULL == ( p->lib = dlopen(srv->tmp_buf->ptr, RTLD_NOW | RTLD_GLOBAL)))
39 {
40 log_error_write(srv, __FILE__, __LINE__, "sbs", "dlopen() failed for:", srv->tmp_buf, dlerror());
41 plugin_free(p);
42 return -1;
43 }
44 //调用动态库中的XXX_plugin_init函数。
45 //XXX是库的名称
46 buffer_reset(srv->tmp_buf);
47 buffer_copy_string(srv->tmp_buf, modules);
48 buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("_plugin_init"));
49 #if 1
50 //调用dlsym函数获得XXX_plugin_init函数的地址。
51 init = (int (*)(plugin *)) (intptr_t) dlsym(p->lib, srv->tmp_buf->ptr);
52 #else
53 //这句没有用
54 *(void **) (&init) = dlsym(p->lib, srv->tmp_buf->ptr);
55 #endif
56 if ((error = dlerror()) != NULL)
57 {
58 log_error_write(srv, __FILE__, __LINE__, "s", error);
59 plugin_free(p);
60 return -1;
61 }
62 //初始化插件
63 //在初始化的过程中,模块将自己所有的对外接口函数的入口地址都存入到p中。
64 if ((*init) (p))
65 {
66 log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed");
67 plugin_free(p);
68 return -1;
69 }
70 #if 0
71 log_error_write(srv, __FILE__, __LINE__, "ss", modules,
72 "plugin loaded");
73 #endif
74 plugins_register(srv, p);
75 }
76 return 0;
77 }
78 #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模块中:
1 int mod_cgi_plugin_init(plugin * p)
2 {
3 p->version = LIGHTTPD_VERSION_ID;
4 p->name = buffer_init_string("cgi");
5 p->connection_reset = cgi_connection_close_callback;
6 p->handle_subrequest_start = cgi_is_handled;
7 p->handle_subrequest = mod_cgi_handle_subrequest;
8 #if 0
9 p->handle_fdevent = cgi_handle_fdevent;
10 #endif
11 p->handle_trigger = cgi_trigger;
12 p->init = mod_cgi_init;
13 p->cleanup = mod_cgi_free;
14 p->set_defaults = mod_fastcgi_set_defaults;
15 p->data = NULL;
16 return 0;
17 }
2 {
3 p->version = LIGHTTPD_VERSION_ID;
4 p->name = buffer_init_string("cgi");
5 p->connection_reset = cgi_connection_close_callback;
6 p->handle_subrequest_start = cgi_is_handled;
7 p->handle_subrequest = mod_cgi_handle_subrequest;
8 #if 0
9 p->handle_fdevent = cgi_handle_fdevent;
10 #endif
11 p->handle_trigger = cgi_trigger;
12 p->init = mod_cgi_init;
13 p->cleanup = mod_cgi_free;
14 p->set_defaults = mod_fastcgi_set_defaults;
15 p->data = NULL;
16 return 0;
17 }
plugins_register函数是将plugin结构体的指针存放在server结构体的plugins数组中。
插件加载完毕之后,main函数有处理了一些初始化的工作,然后,调用plugins_call_init函数对所有插件进行初始化:
1 if (HANDLER_GO_ON != plugins_call_init(srv))
2 {
3 log_error_write(srv, __FILE__, __LINE__, "s",
4 "Initialization of plugins failed. Going down.");
5 plugins_free(srv);
6 network_close(srv);
7 server_free(srv);
8 return -1;
9 }
2 {
3 log_error_write(srv, __FILE__, __LINE__, "s",
4 "Initialization of plugins failed. Going down.");
5 plugins_free(srv);
6 network_close(srv);
7 server_free(srv);
8 return -1;
9 }
plugins_call_init函数在plugin.c文件中:
1 handler_t plugins_call_init(server * srv)
2 {
3 size_t i;
4 plugin **ps;
5 ps = srv->plugins.ptr;
6 /*
7 * fill slots
8 */
9 srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
10 for (i = 0; i < srv->plugins.used; i++)
11 {
12 size_t j;
13 /*
14 * check which calls are supported
15 */
16 plugin *p = ps[i];
17 /**
18 * 对所有的plugin进行登记造册。这个宏在后文中着重讲解。
19 */
20 #define PLUGIN_TO_SLOT(x, y) \
21 if (p->y) { \
22 plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
23 if (!slot) { \
24 slot = calloc(srv->plugins.used, sizeof(*slot));\
25 ((plugin ***)(srv->plugin_slots))[x] = slot; \
26 } \
27 for (j = 0; j < srv->plugins.used; j++) { \
28 if (slot[j]) continue;\
29 slot[j] = p;\
30 break;\
31 }\
32 }
33 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
34 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
35 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
36 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close);
37 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
38 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
39 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
40 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
41 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
42 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
43 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical);
44 PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
45 PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
46 PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
47 #undef PLUGIN_TO_SLOT
48 //对插件进行初始化,调用其初始化函数
49 if (p->init)
50 {
51 if (NULL == (p->data = p->init()))
52 {
53 log_error_write(srv, __FILE__, __LINE__, "sb", "plugin-init failed for module", p->name);
54 return HANDLER_ERROR;
55 }
56 /*
57 * used for con->mode,DIRECT==0,plugins above that
58 */
59 ((plugin_data *) (p->data))->id = i + 1;
60 //这里检测插件的版本是否和当前服务器的版本相同。
61 //这里保证如果以后插件的接口发生了改变,不会造成服务器崩溃。
62 if (p->version != LIGHTTPD_VERSION_ID)
63 {
64 log_error_write(srv, __FILE__, __LINE__, "sb","plugin-version doesn't match lighttpd-version for", p->name);
65 return HANDLER_ERROR;
66 }
67 }
68 else
69 {
70 p->data = NULL;
71 }
72 }
73 return HANDLER_GO_ON;
74 }
2 {
3 size_t i;
4 plugin **ps;
5 ps = srv->plugins.ptr;
6 /*
7 * fill slots
8 */
9 srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
10 for (i = 0; i < srv->plugins.used; i++)
11 {
12 size_t j;
13 /*
14 * check which calls are supported
15 */
16 plugin *p = ps[i];
17 /**
18 * 对所有的plugin进行登记造册。这个宏在后文中着重讲解。
19 */
20 #define PLUGIN_TO_SLOT(x, y) \
21 if (p->y) { \
22 plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
23 if (!slot) { \
24 slot = calloc(srv->plugins.used, sizeof(*slot));\
25 ((plugin ***)(srv->plugin_slots))[x] = slot; \
26 } \
27 for (j = 0; j < srv->plugins.used; j++) { \
28 if (slot[j]) continue;\
29 slot[j] = p;\
30 break;\
31 }\
32 }
33 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
34 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
35 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
36 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close);
37 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
38 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
39 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
40 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
41 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
42 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
43 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical);
44 PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
45 PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
46 PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
47 #undef PLUGIN_TO_SLOT
48 //对插件进行初始化,调用其初始化函数
49 if (p->init)
50 {
51 if (NULL == (p->data = p->init()))
52 {
53 log_error_write(srv, __FILE__, __LINE__, "sb", "plugin-init failed for module", p->name);
54 return HANDLER_ERROR;
55 }
56 /*
57 * used for con->mode,DIRECT==0,plugins above that
58 */
59 ((plugin_data *) (p->data))->id = i + 1;
60 //这里检测插件的版本是否和当前服务器的版本相同。
61 //这里保证如果以后插件的接口发生了改变,不会造成服务器崩溃。
62 if (p->version != LIGHTTPD_VERSION_ID)
63 {
64 log_error_write(srv, __FILE__, __LINE__, "sb","plugin-version doesn't match lighttpd-version for", p->name);
65 return HANDLER_ERROR;
66 }
67 }
68 else
69 {
70 p->data = NULL;
71 }
72 }
73 return HANDLER_GO_ON;
74 }
整个函数中,这个宏是重点:
1 #define PLUGIN_TO_SLOT(x, y) \
2 if (p->y) { \
3 plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
4 if (!slot) { \
5 slot = calloc(srv->plugins.used, sizeof(*slot));\
6 ((plugin ***)(srv->plugin_slots))[x] = slot; \
7 } \
8 for (j = 0; j < srv->plugins.used; j++) { \
9 if (slot[j]) continue;\
10 slot[j] = p;\
11 break;\
12 }\
13 }
2 if (p->y) { \
3 plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
4 if (!slot) { \
5 slot = calloc(srv->plugins.used, sizeof(*slot));\
6 ((plugin ***)(srv->plugin_slots))[x] = slot; \
7 } \
8 for (j = 0; j < srv->plugins.used; j++) { \
9 if (slot[j]) continue;\
10 slot[j] = p;\
11 break;\
12 }\
13 }
在结构体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,它定义在下面的枚举结构中:
1 typedef enum
2 {
3 PLUGIN_FUNC_UNSET,
4 PLUGIN_FUNC_HANDLE_URI_CLEAN,
5 PLUGIN_FUNC_HANDLE_URI_RAW,
6 PLUGIN_FUNC_HANDLE_REQUEST_DONE,
7 PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
8 PLUGIN_FUNC_HANDLE_TRIGGER,
9 PLUGIN_FUNC_HANDLE_SIGHUP,
10 PLUGIN_FUNC_HANDLE_SUBREQUEST,
11 PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
12 PLUGIN_FUNC_HANDLE_JOBLIST,
13 PLUGIN_FUNC_HANDLE_DOCROOT,
14 PLUGIN_FUNC_HANDLE_PHYSICAL,
15 PLUGIN_FUNC_CONNECTION_RESET,
16 PLUGIN_FUNC_INIT,
17 PLUGIN_FUNC_CLEANUP,
18 PLUGIN_FUNC_SET_DEFAULTS,
19
20 PLUGIN_FUNC_SIZEOF
21 } plugin_t;
2 {
3 PLUGIN_FUNC_UNSET,
4 PLUGIN_FUNC_HANDLE_URI_CLEAN,
5 PLUGIN_FUNC_HANDLE_URI_RAW,
6 PLUGIN_FUNC_HANDLE_REQUEST_DONE,
7 PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
8 PLUGIN_FUNC_HANDLE_TRIGGER,
9 PLUGIN_FUNC_HANDLE_SIGHUP,
10 PLUGIN_FUNC_HANDLE_SUBREQUEST,
11 PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
12 PLUGIN_FUNC_HANDLE_JOBLIST,
13 PLUGIN_FUNC_HANDLE_DOCROOT,
14 PLUGIN_FUNC_HANDLE_PHYSICAL,
15 PLUGIN_FUNC_CONNECTION_RESET,
16 PLUGIN_FUNC_INIT,
17 PLUGIN_FUNC_CLEANUP,
18 PLUGIN_FUNC_SET_DEFAULTS,
19
20 PLUGIN_FUNC_SIZEOF
21 } 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,在执行完后面那些宏调用之后,会形成一个如下的表:
从表中可以看出,插件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函数定义为,
1 INIT_FUNC(mod_cgi_init)
2 {
3 plugin_data *p;
4 p = calloc(1, sizeof(*p));
5 assert(p);
6 p->tmp_buf = buffer_init();
7 p->parse_response = buffer_init();
8 return p;
9 }
2 {
3 plugin_data *p;
4 p = calloc(1, sizeof(*p));
5 assert(p);
6 p->tmp_buf = buffer_init();
7 p->parse_response = buffer_init();
8 return p;
9 }
返回的指针存放在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。