zabbix源码分析之基础篇-程序员宅基地

技术标签: zabbix  开源项目  c语言  源码  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 <stdlib.h>
#include <stdio.h>

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。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yin138/article/details/78380442

智能推荐

计算机毕业设计Java疫情防控医用品管理(系统+源码+mysql数据库+Lw文档)_疫情防护用品销售管理系统 论文-程序员宅基地

文章浏览阅读467次。计算机毕业设计Java疫情防控医用品管理(系统+源码+mysql数据库+Lw文档)springboot基于SpringBoot的婚庆策划系统的设计与实现。JSP健身俱乐部网站设计与实现sqlserver和mysql。JSP网上测试系统的研究与设计sqlserver。ssm基于SpringMvC的流浪狗领养系统。ssm基于Vue.js的音乐播放器设计与实现。ssm校园流浪猫图鉴管理系统的设计与实现。_疫情防护用品销售管理系统 论文

android插件化开发打包,Android项目开发如何设计整体架构-程序员宅基地

文章浏览阅读988次,点赞28次,收藏28次。最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!这里附上我整理的几十套腾讯、字节跳动,京东,小米,头条、阿里、美团等公司19年的Android面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。由于篇幅有限,这里以图片的形式给大家展示一小部分。

基于单片机数码管秒表控制系统设计-程序员宅基地

文章浏览阅读600次,点赞11次,收藏6次。*单片机设计介绍,基于单片机数码管秒表控制系统设计。

Python小程序之验证码图片生成_小程序图片验证码后端生成-程序员宅基地

文章浏览阅读235次。python小程序之验证码图片的生成定义随机字母的生成函数定义随机颜色生成函数,采用RGB格式,生成一个元组调用Image,生成画布,填充底色为白色调用画笔函数Draw,传入画布对象填充画布的每一个色块,作为背景在画布上控制间距,填上每一个字在最后的图上进行模糊操作代码# 生成一个随机的二维码小程序from PIL import Image,ImageDraw,ImageF..._小程序图片验证码后端生成

思科自防御网络安全方案典型配置_思科设备怎么ranga)服务器区域独立防护;-程序员宅基地

文章浏览阅读2.2k次。 1. 用户需求分析客户规模:客户有一个总部,具有一定规模的园区网络; 一个分支机构,约有20-50名员工; 用户有很多移动办公用户 客户需求:组建安全可靠的总部和分支LAN和WAN; 总部和分支的终端需要提供安全防护,并实现网络准入控制,未来实现对VPN用户的网络准入检查; 需要提供IPSEC/SSLVPN接入; 在内部各主要部门间,及内外网络间进_思科设备怎么ranga)服务器区域独立防护;

苹果账号迁移流程_apple 账号迁移-程序员宅基地

文章浏览阅读445次。4、转移账号生成的 p8 文件(证书文件)1、转移苹果账号的 teamID。2、接受苹果账号的 teamID。5、接受账号生成的 p8 文件。3、转移应用的 AppID。_apple 账号迁移

随便推点

深度学习中优化方法之动量——momentum、Nesterov Momentum、AdaGrad、Adadelta、RMSprop、Adam_momentum seg-程序员宅基地

文章浏览阅读1k次。https://blog.csdn.net/u012328159/article/details/80311892_momentum seg

动态数据生成静态html页_监听数据变更自动生成静态html-程序员宅基地

文章浏览阅读816次。主要的原理就是替换模板里的特殊字符。 1、静态模板页面 template.html,主要是定义了一些特殊字符,用来被替换。 HTML code DOCTYPE HT_监听数据变更自动生成静态html

预防按钮的多次点击 恶意刷新-程序员宅基地

文章浏览阅读494次。 今日在做一个新闻系统的评论时. 想到了预防"提交"按钮的多次点击的问提 (prevent multiple clicks of a submit button in ASP.NET). 以前碰到此类问提总是用重定位页面来解决. 这次我想找到一个一劳永逸的办法. 通过查讯Google,找到了一些代码,挑选一些较好的修改了一下。public void pa

sokcs5软件dante配置指南_dante 代理 配置pam用户名密码 模式-程序员宅基地

文章浏览阅读4.7k次。近来公司业务有需要做socks5代理的需求,研究了一下,主要的开源实现有2个:dante http://www.inet.no/dante/ss5 http://ss5.sourceforge.net/比较了一下,还是比较倾向于dante,因为看到有人这样评价ss5:Project has an incredibly poor source code quality. Th_dante 代理 配置pam用户名密码 模式

Excel vba 求助。_vba countifs 源码-程序员宅基地

文章浏览阅读809次。在excel vba 中用到countifs 函数,但用来统计带有特殊符号* 时总是统计chu_vba countifs 源码

web前端基础——实现动画效果_web前端实现图片动画效果-程序员宅基地

文章浏览阅读2.6k次。当两个效果之间变换时,可以使用transition过渡属性,但是有多个效果来回变换时,就需要使用动画效果,且动画过程可控(重复播放,画面暂停,最终画面等)文章目录1、简介2、实现步骤3、复合属性animation4、动画属性1、简介动画的本质是快速切换大量图片在人脑中形成的具有连续性的画面构成动画的最小单元:帧或者动画帧2、实现步骤定义动画@keyframes 动画名称{ from{} to{}}@keyframes 动画名称{ 0%{} 10%{} 20%{} 50._web前端实现图片动画效果

推荐文章

热门文章

相关标签