分享
 
 
 

Apache源代码分析——模块的加载

王朝system·作者佚名  2008-05-18
窄屏简体版  字體: |||超大  

tingya | 18 一月, 2005 22:50

本文分析了Apache中关于模块的加载过程

/////////////////////////////////////////////////////////////

//Apache源代码分析——配置命令执行过程

//张中庆于西安交通大学软件所

//tingya$stu,xjtu,edu,cn,将$换成@,,换成.防止地址被收集

//转载请保留出处

//最初出自西安交通大学兵马俑Linux版

//////////////////////////////////////////////////////////////

阅读本文之前,请先阅读Apache源代码分析——关于模块结构的几个重要概念一文

///////////////////////////////////////////////////////////////////////////////////////

在了解了上面的四个重要的概念后,我们现在来看看Apache中是如何进行模块加载的。

Apache中对模块的装载处理的相关函数主要集中于mod_so.c中,其本身也是一个子模块,该子模块用来动态状态其余的模块。在mod_so.c文件的开始位置有很长的一段文件描述,将该描述翻译成中文如下:

该模块(指Mod_so)主要用来在运行的时候动态装入Apache模块,这意味着对服务器可以进行功能扩展而不需要重新对源代码进行编译,甚至根本不需要停止服务器。我们所需要做的仅仅是给服务器发送信号HUP或者AP_SIG_GRACEFUL通知服务器重新载入模块。

当然,在动态加载之前,你首先必须将你的模块编译成为动态链接库,然后更新你所指定的配置文件,通常情况下是httpd.conf。这样Apache核心就可以在启动的时候调用你的模块了。

将模块编译成为共享受库的最简单的方法就是在配置中使用ShareModule命令,而不是使用AddModule命令,而且你必须将文件的扩展名称从‘.o’改变为‘.so’。比如如果我们想将status模块变为共享库,在配置文件中我们只需要将

AddModule modules/standard/mod_status.o

更改为

SharedModule modules/standard/mod_status.so

一旦更改完毕,运行配置文件同时进行编译。现在Apache的httpd的二进制文件中并没有包含mod_status模块,。。。。

为了使用共享模块,将.so文件拷贝到适当的目录中。你可能需要在服务器根目录下创建一个名称为“modules”的目录,比如“/usr/local/httpd/modules”。

下面的事情就是编辑你的conf/httpd.conf文件,同时增加一行“LoadModule”命令。比如:

LoadModule status_module modules/mod_status.so

该命令的第一个参数是模块的名称,名称可以在module_source的最后找到。第二个选项是模块所处的路径,这个路径是相对于服务器路径而言。

如果服务器还在运行的时候,你就编辑LoadModule命令,那么你可以通过发送信号HUP或者AP_SIG_GRACEFUL给服务器,一旦接受到该信号,Apache将重新装载模块,而不需要重新启动服务器。

下面我们将来看看Apache是如何动态装载模块的。在分析每个模块之前,我们首先需要分析的就是模块的数据结构以及该模块能够处理的命令表。对于mod_so模块也不例外。mod_so模块的结构和其中的命令表格如下所示:

static const command_rec so_cmds[] = {

AP_INIT_TAKE2("LoadModule", load_module, NULL, RSRC_CONF | EXEC_ON_READ,

"a module name and the name of a shared object file to load it from"),

AP_INIT_ITERATE("LoadFile", load_file, NULL, RSRC_CONF | EXEC_ON_READ,

"shared object file or library to load into the server at runtime"),

{ NULL }

};

module AP_MODULE_DECLARE_DATA so_module = {

STANDARD20_MODULE_STUFF,

NULL, /* create per-dir config */

NULL, /* merge per-dir config */

so_sconf_create, /* server config */

NULL, /* merge server config */

so_cmds, /* command apr_table_t */

NULL /* register hooks */

};

模块中能够处理的所有命令都保存在so_cmds中,从命令表中可以看出,mod_so模块可以处理的命令只有“LoadModule”和“LoadFile”,相应的处理函数分别为load_module和loadfile。下面我们首先来看load_module函数的实现,该函数也是模块装载处理的入口。

static const char *load_module(cmd_parms *cmd, void *dummy,

const char *modname, const char *filename)

该函数用来将共享对象载入到服务器的地址空间中。其中,cmd和dummy是所有的命令处理程序都必须具有的,其用于Apache核心给模块传递相应的信息。modname是需要状态的模块的路径名称。

函数首先要做的事情就是在现有的所有的模块中查找是否已经存在需要载入的模块,如果该模块已经载入,则什么都不处理,否则则对其进行装载。遍历的模块包括动态装载的和静态编译两种。

对于动态载入模块,函数首先得到模块so_module中的模块配置信息,这个通过宏ap_get_module_config来实现。

在server_rec结构中,成员module_config是一个一维向量结构,专门用来存储各个模块针对本服务器的配置信息,向量结构中的每一个元素对应存储一个模块针对本服务器的所有配置信息,结构如下所示:

从上面的结构图中可以看出,如果要获取第i个即索引为i的模块针对本服务器的配置信息的话,可以通过下面的表示式来获取:module_config[i],事实上模块的索引由模块的module_index决定,因此式子展开为:module_config[(m)->module_index]。这实际上真是宏ap_get_module_config展开后的结果,在Apache中,该宏定义为

#define ap_get_module_config(v,m)

(((void **)(v))[(m)->module_index])

根据上面的描述,我们不难理解该宏的含义。不过通过该宏获取的信息各个模块是千差万别,因此返回的类型只能是void**,因此如果需要使用最好进行转换。就so_module模块而言,其每个元素都是so_server_conf类型的。该类型非常简单,其内部就是一个简单的数组:

typedef struct so_server_conf {

apr_array_header_t *loaded_modules;

} so_server_conf;

模块对应的配置信息实际上都保存在数组loaded_modules中。数组中的每个元素是moduleinfo结构,因此函数需要做的实质上就是遍历查找loaded_modules数组,判断其中是否存在给定的模块,比较只需要通过模块名称进行。下面的代码完成的就是查找动态加载模块:

sconf = (so_server_conf *)ap_get_module_config(cmd->server->module_config,

&so_module);

modie = (moduleinfo *)sconf->loaded_modules->elts;

for (i = 0; i < sconf->loaded_modules->nelts; i++) {

modi = &modie[i];

if (modi->name != NULL && strcmp(modi->name, modname) == 0) {

ap_log_perror(APLOG_MARK, APLOG_WARNING, 0,

cmd->pool, "module %s is already loaded, skipping",

modname);

return NULL;

}

}

对动态加载模块检查完毕后,Apache将检查静态链接模块数组ap_preloaded_modules。在ap_preloaded_modules数组中查找指定模块相对简单。对于每一个模块,Apache必须保证其文件名是以“mod_”开始的,比如mod_so.c、mod_alias.c等等,如果命名格式不是这样,函数将认为模块不是合法的模块。函数的名称最终通过宏STANDARD20_MODULE_STUFF以__FILE__反映到module结构的name属性中,因此函数只需要判断name是否合法就可以了。

如果需要载入的模块没有被载入,则函数首先在动态载入模块数组sconf->loaded_modules中压入一个新的module元素,同时将该结构的名称置为modname,既而函数调用apr_dso_load将文件载入到Apache的地址空间中,同时调用apr_dso_sym获取动态链接库中的module结构,返回的结构保存在modsym中。一旦得到modsym,我们就相当于得到了该模块的module结构了,用modp表示,同时该模块内的动态加载句柄dynamic_load_handle设置为dlopen取得的句柄。

在真正的使用即激活加载模块之前,Apache必须确保加载的模块确实是Apache模块,为此Apache在模块结构中设定了magic字段,通过检查magic字段,apache确定加载的模块是否是apache模块。对于Apache2.0而言,该值为“AP2.0”。另外apache还可以通过结构中的version来判断模块的兼容性。如果加载的是合法的2.0模块,函数将立即调用ap_add_loaded_module将模块激活,所谓的激活无非就是将模块放入ap_top_modules链表中。

此外apache含需要在配置内存池pconf中注册cleanup处理函数。这样当我们重新启动或者关闭服务器的时候,cleanup函数将自动调用将共享模块卸载;最后我们需要做的就是为模块运行配置过程。

///////////////////////////////////////////////////////////////////////////////////////

APR_DECLARE(apr_status_t) apr_dso_load(apr_dso_handle_t **res_handle,

const char *path, apr_pool_t *ctx);

该函数用来载入DSO动态共享库。res_handle_Location用来存储新的DSO的处理句柄;path则是DSO库的路径位置;

apr_dso_load的实现分为七种平台:AIX、beos、netware、OS2、OS390、Unix以及Win32。我们首先来看看Linux平台下的实现。为此我们首先了解一下Linux下对动态链接库是如何的操作。

dso.c中的大部分函数都是对dl_xx实现了简单的封装而已。

现在我们再来看apr_dso_load的实现。函数首先调用dlopen对模块进行加载,返回加载后的句柄os_handle,同时将句柄保存在apr_dso_handle_t类型变量res_handle的handle中。res_handle所需要的所有资源来自内存池ctx。最后调用apr_pool_cleanup_register函数注册内存池ctx的清除函数dso_cleanup。

apr_dso_unload函数则更简单,其只是调用了apr_pool_cleanup_run函数清除分配的内存池,同时调用dso_cleanup函数进行模块卸载。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(void) ap_add_loaded_module(module *mod, apr_pool_t *p)

该函数功能非常的简单,其实现了模块的动态加载和激活。所谓加载就是将模块假如ap_loaded_modules数组中;所谓的激活就是调用ap_add_module将模块放入ap_top_modules链表中。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(void) ap_add_module(module *m, apr_pool_t *p)

该函数用于在服务器中激活指定的模块m。通常其对应于httpd.conf文件中的AddModule命令。

在将模块加入服务器之前,Apache必须判断模块的版本是否与当前Apache服务器的版本相同。Apache2.0的所有模块的版本号统一为MODULE_MAGIC_NUMBER_MAJOR;min_version则为MODULE_MAGIC_NUMBER_MINOR。不过apache仅仅核对主版本号码,次版本号不同无所谓。

在Apache中,主版本号的变更意味着模块的兼容性出现了问题,包括模块数据结构的变化等等,次版本号则意味着模块的小范围的调整,兼容性没有出现问题。

所有的模块最终都被加入到全局模块列表ap_top_module中,所有新增加的模块都插入ap_top_module链表的起始位置。一旦插入完成,apache将对插入的模块进行索引标记。标记的方法很简单,无非就是将当前的总模块数作为标记索引;同时累加总模块数和动态载入的模块数,有一点需要注意的事,累加后的总模块数不能超过动态载入模块的最大数DYNAMIC_MODULE_LIMIT。

另外还有一个小小的实现细节必须考虑到。正如我们前面曾经说过,module结构中的name值最终实际上填充的是__FILE__,不过不同的C编译器对__FILE__支持不一样。有的C编译器其值仅仅是文件名称,而有的C编译器其值则是完整的文件路径名称。而我们需要的仅仅是个文件名而已,因此对于第二种情况,我们需要将名称提取出来,在DOS和Unix下实现代码如下:

if (ap_strrchr_c(m->name, '/'))

m->name = 1 + ap_strrchr_c(m->name, '/');

if (ap_strrchr_c(m->name, ''))

m->name = 1 + ap_strrchr_c(m->name, '');

至此函数需要做的最后一件事情就是调用ap_register_hook注册该模块的所有钩子。ap_register_hook的内部无非还是调用的模块本身的钩子注册函数句柄register_hooks。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(void) ap_remove_module(module *m)

该函数的功能与ap_add_module对应,其用于将指定的模块修改为非激活状态。在函数内部,apache遍历整个ap_top_modules链表查找模块m。查找分为三种情况:

(1)要查找的模块位于ap_top_modules的头部,此时仅仅需要修改ap_top_modules指向下一个结点。

(2)要查找的模块不在链表头部,函数将顺着next往后遍历,一旦找到模块m,则将指针定位于其前面的一个结点,修改其next指向模块m后面的结点,即modp->next = modp->next->next。

(3)如果查找到链表的末尾都没有找到,则意味着该模块不存在。

一旦将module被移除,就修改total_modules和dynamic_modules的值。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(void) ap_setup_prelinked_modules(process_rec *process)

该函数将Apache中的缺省模块加载并激活。该函数也是Apache中所有的模块处理的入口函数,最早出现在main.c中。

函数首先对ap_preloaded_modules进行索引标记工作,然后将其中的元素逐一拷贝到ap_loaded_modules数组中。加载后需要激活的模块都保存在ap_prelinked_modules中,为此函数将对该数组中的每一个元素调用ap_add_modules进行激活。

Apache2.0中ap_preloaded_modules和ap_prelinked_modules是完全相同的,该函数刻意分为两个数组进行处理实际上延续的还是老版本的。

一旦激活所有的模块,函数调用apr_hook_sort_all对所有模块内的钩子进行排序,以便于处理。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(const char *) ap_find_module_name(module *m)

函数获取模块m的名称

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(module *) ap_find_linked_module(const char *name)

函数在所有的激活模块中查找名称为name的模块,如果查到,返回该模块;否则返回NULL。

///////////////////////////////////////////////////////////////////////////////////////

AP_DECLARE(int) ap_add_named_module(const char *name, apr_pool_t *p)

该函数将已经装入即存在于ap_loaded_modules数组中的具有指定名称name的模块激活。成功时返回1;否则返回0。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有