在学习Nginx中的过滤模块开发时, 遇到了使用全局变量和静态全局变量构成的单向函数链表, 对于我这种新手来说, 有些无法理解.
首先, 关于全局变量与静态全局变量, 下面贴出一些注意的地方:
从分配内存空间看:
全局变量、静态局部变量、静态全局变量都在静态存储区分配空间,而局部变量在栈分配空间。
区别:
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上没有什么不同。区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
所以, 自己按照Nginx中构建链表的方式也写了一个小小的实验程序如下.
整个框架目的:
目的是为了能实现一系列相同类型的函数(即可以以同用一个函数指针指向的众多函数)
如果想要添加能执行的函数(这里我们称为模块), 只需要编写新的文件而不需要改动原有的代码
框架结构体:
首先, 这是头文件. 用于制定每个模块需要的一些信息.
只是这里因为程序太小太简陋, 所以不需要什么信息. 只需要每个模块的初始化函数. 初始化函数的用途会在下面讲
//from "conf.h"
#include <stdio.h>
#ifndef CONF_H
#define CONF_H
//形成这个函数链表的每个(函数)元素的类型
typedef int (*function_init)();
//每个模块真正工作的函数的指针
typedef void (*function_job)();
typedef struct{//每个模块结构体当前只包含必须的初始化函数
function_init init;
}module_t;
#endif
框架自带的模块.
//from "module_A.c"
#include "conf.h"
//这个function_top_job函数是整个框架用来遍历模块函数的入口, 换句话说, 就是函数链表的入口
extern function_job function_top_job;
//这个必须是静态全局变量, 才能让每个模块具有相同的变量名字但指向不同的函数
static function_job function_next_job;
static void module_A_job();
static int module_A_init();
//对于模块A的定义.
module_t module_A = {
module_A_init
};
//模块A的真正工作函数, 其中要包含其调用下一个函数元素的代码
static void
module_A_job(){
printf("I am from module A\n");
//看是否执行到链表尾部. 如果还有则继续执行
if(function_next_job)
function_next_job();
}
//模块A的初始化函数. 从这里明显可以看出和Nginx中相同的用法
//将每个模块插入在链表头部, 所以后初始化的函数会被优先执行
static int
module_A_init(){
//静态变量指向原来的整个框架的链表首部
function_next_job = function_top_job;
//整个框架的链表首部被重新赋值
function_top_job = module_A_job;
}
模块整合
我们知道, 每个模块都需要执行它的初始化函数之后才会被放到链表函数中去, 那么我们不可能在每次添加新模块的时候都
到main函数中去添加新模块的初始化函数调用代码, 所以就需要使用下面这个数组
在Nginx中, 类似的文件叫做 /nginx/obj/ngx_modules.c, 这个文件是由configure文件生成的. 因为我还不会编写这个东西...所以每次添加模块的话, 还
需要到这里来做一些小小的修改. 不过这个是单独的文件, 所以改起来比较清晰方便.
//from "modules.c"
#include "conf.h"
//其他文件的全局变量
extern module_t module_A;
//将其他文件的模块结构体放到这个模块数组中来
module_t *modules[] = {
&module_A,
NULL //放一个NULL在最后也就方便遍历, 不需要知道到底有多少元素在这个modules数组中
};
最后, 整个框架需要一个main函数:
//from "main.c"
#include "conf.h"
//定义框架的函数链表入口
function_job function_top_job = NULL;
//所有模块都被放在这个模块指针数组中
extern module_t *modules[];
int main()
{
int i;
//执行每个模块的初始化函数. 形成函数链表
for(i=0; modules[i]; i++)
modules[i]->init();
//从入口开始执行, 遍历每个函数元素
function_top_job();
return 0;
}
编译执行:
好了, 基本的一个框架已经完成了. 如果在编译后执行, 那么输出的内容就是:
$ gcc -o exe conf.h module_A.c modules.c main.c
$ ./exe
I am from module A
添加新模块
现在来添加新的模块. 添加新的模块在原本的设计下也就显得十分简单了.
比如, 现在想要添加模块B和模块C
//from "module_B.c"
#include "conf.h"
extern function_job function_top_job;
static function_job function_next_job;
static void module_B_job();
static int module_B_init();
module_t module_B = {
module_B_init
};
static void
module_B_job(){
printf("I am from module B\n");
if(function_next_job)
function_next_job();
}
static int
module_B_init(){
function_next_job = function_top_job;
function_top_job = module_B_job;
}
//from "module_C.c"
#include "conf.h"
extern function_job function_top_job;
static function_job function_next_job;
static void module_C_job();
static int module_C_init();
module_t module_C = {
module_C_init
};
static void
module_C_job(){
printf("I am from module C\n");
if(function_next_job)
function_next_job();
}
static int
module_C_init(){
function_next_job = function_top_job;
function_top_job = module_C_job;
}
除了添加这两个源文件外, 还需要去modules.c文件中进行简单的修改, 改成下面这个样子:
#include "conf.h"
extern module_t module_A;
extern module_t module_B;
extern module_t module_C;
module_t *modules[] = {
&module_A,
&module_B,
&module_C,
NULL
};
最后编译执行:
$ gcc -o exe conf.h module_A.c modules.c main.c module_B.c module_C.c
$ ./exe
I am from module C
I am from module B
I am from module A
可以发现, 函数的执行顺序与插入操作是相符合的.
好了, 可以发现, 添加的东西与原来模块的格式相同, 这里内容虽然也差不多, 但完全可以改成完全不同的操作. 只是格式必须要遵守.
另外需要修改的modules.c 也不是很困难...