zabbix源码分析之基础篇

zabbix_aggentd 源码分析之基础篇

zabbix 源码组织结构

zabbix是采用Automake方式构建的开源项目,服务和工具是通过C语言实现,实现来跨平台的能力,目前zabbix_server还不支持Windows系统。
主要的目录是:
src为C代码的源码目录,include为C代码的头文件,frontends为前端php代码。
src目录libs,modules和各功能主程序的目录,zabbix_agent就是本文需要分析的主目录。

zabbix编码规范和宏定义的解释

zabbix的代码中为了跨平台和开发的简洁采用了大量的宏定义,其中很多对于阅读代码而言又是一种煎熬:)

MAIN_ZABBIX_ENTRY

int MAIN_ZABBIX_ENTRY(int flags)

MAIN_ZABBIX_ENTRY定义来功能程序的开始入口,在这个入口之前,由真正的main函数调用,并且在调用之前需要检查参数,并对无需进入程序主功能的参数直接执行并处理。所以定义了MAIN_ZABBIX_ENTRY的主程序文件(例如:zabbix_agent.c),就是实现zabbix代理功能的主入口,命令行参数的检测,help信息的输出,服务的安装,等等都由具体的程序模块实现。
通过这个宏,就相当于定义了实现业务功能的main,无论是通过服务(Windows),还是通过deamon(*nix下的守护进程)启动业务功能,都可以直接调用MAIN_ZABBIX_ENTRY(0)启动业务功能。

ZBX_THREAD_ENTRY

#if defined(_WINDOWS)
    #define ZBX_THREAD_ENTRY(entry_name, arg_name)  \
        unsigned __stdcall entry_name(void *arg_name)
#else   /* not _WINDOWS */
    #define ZBX_THREAD_ENTRY(entry_name, arg_name)  \
        unsigned entry_name(void *arg_name)
#endif

ZBX_THREAD_ENTRY定义了功能线程的开始入口,如果需要调用一个开始线程,调用如下:
zbx_thread_start(collector_thread, thread_args);
而collector_thread的函数声明如下:
ZBX_THREAD_ENTRY(collector_thread, args)
这样通过宏,实际就是定义了函数collector_thread。直接定义函数不好吗,非要通过宏来定义一个函数的名称吗?对于跨平台系统而言,是必须的,如果不通过宏定义,那么对于windows上的stdcall的声明就需要特殊定义了,或者定义一个STDAPI的宏,实现Windows和*nix系统调用方式不同的生命方式(Windows系统定义为__stdcall,而*nix系统定义为空宏),相比较而言,zabbix采取的方法更好。

Zabbix的内存处理相关辅助函数

内存分配

zaibbx的内存实现代码在libs/common/misc.c
内存函数共4个:

#define zbx_calloc(old, nmemb, size)    zbx_calloc2(__FILE__, __LINE__, old, nmemb, size)
#define zbx_malloc(old, size)       zbx_malloc2(__FILE__, __LINE__, old, size)
#define zbx_realloc(src, size)      zbx_realloc2(__FILE__, __LINE__, src, size)
#define zbx_strdup(old, str)        zbx_strdup2(__FILE__, __LINE__, old, str)

实际的实现函数后面有数字2(利用宏实现对用户调用文件和行号进行跟踪,具体实现在misc.c中。
这里,咋们通过看一段代码就知道zabbix设计得有意思的地方:

void    *zbx_malloc2(const char *filename, int line, void *old, size_t size)
{
    int max_attempts;
    void    *ptr = NULL;

    /* old pointer must be NULL */
    if (NULL != old)
    {
        zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_malloc: allocating already allocated memory. "
                "Please report this to Zabbix developers.",
                filename, line);
    }

    for (
        max_attempts = 10, size = MAX(size, 1);
        0 < max_attempts && NULL == ptr;
        ptr = malloc(size), max_attempts--
    );

    if (NULL != ptr)
        return ptr;

    zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_malloc: out of memory. Requested " ZBX_FS_SIZE_T " bytes.",
            filename, line, (zbx_fs_size_t)size);

    exit(EXIT_FAILURE);
}

首先,他需要提供申请空间的指针,用于检测是否重复分配内存。调用函数是通过zbx_malloc调用实现内存分配,例如下面代码:

static ZBX_METRIC   *commands = NULL;
//...
commands = zbx_malloc(commands, sizeof(ZBX_METRIC));

commands是一个内存变量指针。
并且,在申请内存时尝试调用malloc函数10次,这样是否有实际意义?不得而知!
如果分配失败,直接退出进程,结束程序运行。

在代码中,还可以看到很多zbx_realloc的调用,用于动态扩展需要分配的空间。
例如下面代码添加一个需要采集的指标到运行时采集列表:

int add_metric(ZBX_METRIC *metric, char *error, size_t max_error_len)
{
    int i = 0;

    while (NULL != commands[i].key)
    {
        if (0 == strcmp(commands[i].key, metric->key))
        {
            zbx_snprintf(error, max_error_len, "key \"%s\" already exists", metric->key);
            return FAIL;    /* metric already exists */
        }
        i++;
    }

    commands[i].key = zbx_strdup(NULL, metric->key);
    commands[i].flags = metric->flags;
    commands[i].function = metric->function;
    commands[i].test_param = (NULL == metric->test_param ? NULL : zbx_strdup(NULL, metric->test_param));

    commands = zbx_realloc(commands, (i + 2) * sizeof(ZBX_METRIC));
    memset(&commands[i + 1], 0, sizeof(ZBX_METRIC));

    return SUCCEED;
}

这里用到了zbx_realloc来实现内存扩展,也就是,内存不是一次性分配好的,而是不断申请和分配的。内存分配开始是总是保留一个空位,数据开始填充在空位,然后重新分配一个空间(使用realloc实现内存复制),并设置最后一个空位为0值。
这种分配内存的模式在zabbix源码中很常见,熟悉后,阅读起来就会省不少时间。
这里再说一下zbx_free的实现

#define zbx_free(ptr)       \
                \
do              \
{               \
    if (ptr)        \
    {           \
        free(ptr);  \
        ptr = NULL; \
    }           \
}               \
while (0)

zbx_free就是通过C语言的free实现的,这里为什么需要采用一个do-while的写法呢?
这是在C中实现多行宏定义的一个通用方法,防止语句在宏扩张是产生语义错误,例如如果宏用在如下代码时:

#define zbx_free(p) \
    free(p);  \
    p = NULL;

int status = do_somthing();
if (status == 0) zbx_free(ptr);
else{
    //using p pointer to do somthing...
}

如果zbx_free没有采用do-while的写法,那么就会出现错误的宏扩展:

if (status == 0) free(p);
ptr = NULL;
else{
    // using p pointer to do somthing...
}

以上代码就会产生编译错误,最不好的情况是,直接出现运行时错误。原因就是宏使用时没有采用{}来包裹导致的问题,而采用do-while(0)时,该问题就可以避免。

字符串的处理

zabbix处理字符串也是采用封装的方式进行,主要包括如下几个函数:

#define zbx_strdup(old, str)        zbx_strdup2(__FILE__, __LINE__, old, str)

#define ZBX_STRDUP(var, str)    (var = zbx_strdup(var, str))

一般采用zbx_strdup函数实现字符串的复制。
例如前面的指标名称复制就是调用该函数实现复制的:

commands[i].key = zbx_strdup(NULL, metric->key);

zbx_strdup的实现任采用C库的strdup实现,不过处理的原指针的释放。

char    *zbx_strdup2(const char *filename, int line, char *old, const char *str)
{
    int retry;
    char    *ptr = NULL;

    zbx_free(old);

    for (retry = 10; 0 < retry && NULL == ptr; ptr = strdup(str), retry--)
        ;

    if (NULL != ptr)
        return ptr;

    zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] zbx_strdup: out of memory. Requested " ZBX_FS_SIZE_T " bytes.",
            filename, line, (zbx_fs_size_t)(strlen(str) + 1));

    exit(EXIT_FAILURE);
}

并且,仍然采用多次尝试的方法复制字符串。

动态字符数组(dynamic string array)

首先声明,这个地方有点复杂(^_^)

函数的声明是这样:

void    zbx_strarr_init(char ***arr)
{
    *arr = zbx_malloc(*arr, sizeof(char *));
    **arr = NULL;
}

三个星号,你看看,太过复杂吧!一般人不用他!谁叫他是zabbix呢?
代码创建一个二维指针空间:

p -> [x1, x2, x3, ...]
      |   |   |     |
      v   v   v     v
    char*    char*  null

这里参数为三个*,是为了把两个的变量通过指针传递给zbx_strarr_init,以便初始化。

为更好的理解二维数组,我编写了以下简要的代码:

#include 
#include 

void test_p(int **p)
{
    // 外部实际是一个指针(一维)
    // 通过**传递,是希望由该函数实现内存分配,并输出到外部的指针变量
    // 这里为了方便,使用了临时变量a,如果直接使用p实现赋值,
    // 就需要通过数组方式访问咯,例如*p[0], *p[1] *p[2]
    int *a = *p = malloc(sizeof(int)*3);
    *a++=1;
    *a++=2;
    *a++=3;
}

void test_pp(int ***p)
{
    // 二维指针的变量
    // 首先分配一维的指针变量
    // 然后对一维指针变量初始化数据内存
    int **a = *p = malloc(sizeof(int*) * 3);
    *a++ = malloc(sizeof(int) * 3);
    *a++ = malloc(sizeof(int) * 3);
    *a++ = malloc(sizeof(int) * 3);
    int **b = *p;
    printf("0x%lx\r\n", *b++);
    printf("0x%lx\r\n", *b++);
    printf("0x%lx\r\n", *b++);
    int **c = *p;
    int *c1 = *c++;
    int *c2 = *c++;
    int *c3 = *c++;
    *c1++ = 1; *c1++ = 2; *c1++ = 3;
    *c2++ = 4; *c2++ = 5; *c2++ = 6;
    *c3++ = 7; *c3++ = 8; *c3++ = 9;
}

void test_x(int *p){
        *p++=1;
        *p++=2;
        *p++=3;
}

int main(){
        printf("(1)*\r\n");
        int a[3] = {0};
        test_x(a);
        printf("%d %d %d\r\n", a[0], a[1], a[2]);

        printf("(2)**\r\n");
        int *b = 0;
        test_p(&b);
        printf("%d %d %d\r\n", b[0], b[1], b[2]);
        free(b);

        printf("(3) ***\r\n");
        int **c = 0;
        test_pp(&c);
        int *p;
        for (int i = 0; i < 3; i++){
                p = c[i];
                printf("addr is %lx\r\n", p);
                for (int j = 0; j < 3; j++){
                        printf("%d ", p[j]);
                }
                printf("\r\n");
                free(p);
        }
        free(c);
        return 0;
}

上面程序执行的结果是:

(1)*
1 2 3
(2)**
1 2 3
(3) ***
0x7fda91c025d0
0x7fda91c02600
0x7fda91c02610
addr is 7fda91c025d0
1 2 3
addr is 7fda91c02600
4 5 6
addr is 7fda91c02610
7 8 9

现在看zabbix的二维数组的初始化代码,实际上,只是分配咯一个元素的一位数组指针,并设置为空。

下面来看添加字符串实现:

void    zbx_strarr_add(char ***arr, const char *entry)
{
    int i;

    assert(entry);

    for (i = 0; NULL != (*arr)[i]; i++)
        ;

    *arr = zbx_realloc(*arr, sizeof(char *) * (i + 2));

    (*arr)[i] = zbx_strdup((*arr)[i], entry);
    (*arr)[++i] = NULL;
}

其实和前面的示例一样,不过这里通过zbx_realloc实现内存扩张。首先,寻找最后一个为null元素,i 表示元素个数-1,所以为了扩张一个元素,需要i+2个指针变量的区域。
然后,复制字符串到倒数一个指针位置,同时设置最后一个位置为null。

你可能感兴趣的:(Zabbix)