zoukankan      html  css  js  c++  java
  • 如何编写一个多进程性能测试程序

    在工作中经常碰到需要写一些多进程/多线程的测试程序,用来测试接口的性能。本文将会从零开始一点点增加代码,最终完成一个简易的多进程测试程序编写。该程序支持实时打印测试进结果和最终测试结果的统计。

    同时,本文还涵盖了以下知识点,可以作为学习参考:

    • 使用getopt_long()处理命令行选项和参数
    • 使用fork()wait()处理多进程
    • 使用sigaction()配合alarm()处理定时信号SIGALRM
    • 使用shmget()shmat()shmdt()shmctl()等通过共享内存进行进程间通信
    • 使用sigaction()捕获SIGINTSIGQUIT信号,在程序终止前做共享内存清理工作

    本博客已经迁移至CatBro's Blog,那里是我自己搭建的个人博客,页面效果比这边更好,支持站内搜索,评论回复还支持邮件提醒,欢迎关注。这边只会在有时间的时候不定期搬运一下。

    本篇文章链接

    本文源码已开源Github

    选项和参数的处理

    为了使测试程序更高的可用性,我们getopt来处理选项和参数。

    #include <stdio.h>      // printf
    #include <getopt.h>     // getopt_long
    #include <stdlib.h>     // strtol, abort
    #include <limits.h>     // LONG_MIN, LONG_MAX
    
    void ShowHelpInfo(char *name) {
        printf("Usage: %s [options]
    
    ", name);
        printf("  Options:
    ");
        printf("    -p/--proc         Number of processes (default: 1)
    ");
        printf("    -d/--duration     Duration of test (unit: s, default: 10)
    ");
        printf("    -h/--help         Show the help info
    ");
        printf("
    ");
        printf("  Example:
    ");
        printf("    %s -p 4 -d 30
    ", name);
        printf("
    ");
    }
    
    int main(int argc, char *argv[]) {
        int c = 0;
        int option_index = 0;
        long procs = 1;
        long duration = 10;
    
        /**
         *  定义命令行参数列表,option结构的含义如下(详见 man 3 getopt):
         *  struct option {
         *      const char *name;       // 参数的完整名称,对应命令中的 --xxx
         *      int  has_arg;           // 该参数是否带有一个值,如 –-config xxx.conf
         *      int *flag;              // 一般设置为NULL
         *      int  val;               // 解析到该参数后getopt_long函数的返回值,
         *                      // 为了方便维护,一般对应getopt_long调用时第三个参数
         *  };
         */
        static struct option arg_options[] =
        {
            {"proc", 1, NULL, 'p'},
            {"duration", 1, NULL, 'd'},
            {"help", 0, NULL, 'h'},
            {NULL, 0, NULL, 0}
        };
    
        /**
         *  注意:传递给getopt_long的第三个参数对应了命令行参数的缩写形式,如-h等,
         *  如果字符后面带有冒号":",则说明该参数后跟一个值,如-c xxxxxx
         *  如果开头有冒号":",则当一个选项缺少参数时,返回":",否则,返回"?"
         */
        while ((c = getopt_long(argc, argv, ":p:d:h", arg_options, &option_index)
                ) != -1) {
            switch (c) {
            case 'h':
                ShowHelpInfo(argv[0]);
                //fprintf(stdout,"option is -%c, optarv is %s
    ", c, optarg);
                return 0;
            case 'p':
                procs = strtol(optarg, NULL, 0);
                if (procs == LONG_MIN || procs == LONG_MAX) {
                    fprintf(stderr, "The number of processes (%s) is overflow
    
    ",
                            optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (procs <= 0) {
                    fprintf(stderr, "The number of processes must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 'd':
                duration = strtol(optarg, NULL, 0);
                if (duration == LONG_MIN || duration == LONG_MAX) {
                    fprintf(stderr, "The duration of test (%s) is overflow
    
    ",
                            optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (procs <= 0) {
                    fprintf(stderr, "The duration of test must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case '?':
                fprintf (stderr, "Unknown option -%c
    
    ", optopt);
                ShowHelpInfo(argv[0]);
                return -1;
            case ':':
               fprintf (stderr, "Option -%c requires an argument
    
    ", optopt);
               ShowHelpInfo(argv[0]);
               return -1;
            default:
                abort();
            }
        }
        printf("processes:  %ld
    ", procs);
        printf("duration:   %lds
    ", duration);
        printf("
    -----------------------------Start Testing----------------------"
               "--------
    
    ");
    
        printf("Hello world
    ");
        return 0;
    

    注意:传递给getopt_long的第三个参数对应了命令行参数的缩写形式,如-h, -v, -c等。

    如果字符后面带有冒号":",则说明该参数后跟一个值,如-c xxxxxx

    如果开头有冒号":",则当一个选项缺少参数时,返回":",否则,返回"?"

    效果如下

    ^_^$ make
    gcc "-g" -c multi-process.c -o multi-process.o
    gcc  -o test multi-process.o
    
    ^_^$ ./test
    processes:  1
    duration:   10s
    
    -----------------------------Start Testing------------------------------
    
    Hello world
    

    选项或参数错误时

    ^_^$ ./test -p
    Option -p requires an argument
    
    Usage: ./test [options]
    
      Options:
        -p/--proc         Number of processes (default: 1)
        -d/--duration     Duration of test (unit: s, default: 10)
        -h/--help         Show the help info
    
      Example:
        ./test -p 4 -d 30
    

    增加多进程的支持

    主进程fork出n个子进程后wait子进程,子进程则通过sigactionalarm设置一个定时器,然后进行业务测试。

    为了简洁,已经把选项参数处理的部分独立出去了。

    #include <stdio.h>      // printf, fprintf
    #include <sys/wait.h>   // wait
    #include <sys/types.h>  // getpid, wait
    #include <signal.h>     // sigaction, SIGLARM
    #include <limits.h>     // LONG_MIN, LONG_MAX, ULLONG_MAX
    #include <unistd.h>     // getpid
    #include <string.h>     // memset
    #include "multi-process.h"
    
    int isStop = 0;             // 用于标记测试终止
    
    typedef struct param_st {   // 自定义测试参数
        long index;
    } Param;
    
    void handle_signal_child(int sigNum)
    {
        if (sigNum == SIGALRM) {
            isStop = 1;
        }
    }
    
    /* 实际业务测试函数 */
    void doTest(void *param) {
        unsigned long long i = 0;
        Param *pa = (Param *)param;
        for(; i < ULLONG_MAX && !isStop; ++i) {
            /* DO YOUR WORK */
            /* DO YOUR WORK */
        }
        printf("process [pid = %6u] result: %llu
    ", getpid(), i);
    }
    
    int main(int argc, char *argv[]) {
        int rv = 0;
        long i = 0;
        int proc_index = 0;
        Options opt;
        int isParent = 1;
        int wstatus = 0;
        pid_t pid = 0;
        struct sigaction act_child;
    
        rv = process_options(argc, argv, &opt);
        if (rv) {
            return -1;
        }
    
        printf("
    -----------------------------Start Testing----------------------"
               "--------
    
    ");
    
    
        /* COMMON INIT */
        /* COMMON INIT */
        
        while(isParent && i < opt.procs) {
            pid =  fork();
            if(pid == -1) {         /* error */
                fprintf(stderr, "fork failed %d
    ", pid);
                return -1;
            }
            else if(pid == 0) {     /* child */
                isParent = 0;
                proc_index = i;     // 记录进程索引
            }
            else {                  /* parent */
            }
            ++i;
        }
        if(isParent) {
            /* PARENT INIT */
            /* PARENT INIT */
            for(i =0 ; i < opt.procs; ++i) {
                pid = wait(&wstatus);                       // 等待子进程结束
                printf("process [pid = %6d] exit
    ", pid);
            }
        }
        else {
            /* CHILD INIT */
            Param param;
            memset(&param, 0, sizeof(Param));
            param.index = proc_index;
            /* CHILD INIT */
    
            act_child.sa_handler = handle_signal_child;
            sigemptyset(&act_child.sa_mask);
            act_child.sa_flags = SA_RESETHAND;
            /* 用于测试时间到时,通知子进程结束测试 */
            rv = sigaction(SIGALRM, &act_child, NULL);
            if (rv) {
                fprintf(stderr, "sigaction() failed
    ");
                return -1;
            }
            //signal(SIGALRM, handle_signal_child);
            alarm(opt.duration);                            // 设置测试时长
            doTest(&param);
            return 0;       /* child finished work */
        }
    
        printf("Hello World!
    ");
        return 0;
    }
    

    效果如下

    ^_^$ ./test -p 4 -d 2
    processes:  4
    duration:   2s
    
    -----------------------------Start Testing------------------------------
    
    process [pid =  11942] result: 446930553
    process [pid =  11942] exit
    process [pid =  11939] result: 434385097
    process [pid =  11939] exit
    process [pid =  11940] result: 442246977
    process [pid =  11940] exit
    process [pid =  11941] result: 442418811
    process [pid =  11941] exit
    

    这样已经可以实现简单的多进程测试,简单起见,示例代码里只是简单地进行了计数操作。读者如果想要进行自己特定的测试,只要在Param中增加需要的测试参数,接着在/* CHILD INIT */处进行参数初始化,然后在/* DO YOUR WORK */处添加实际的测试逻辑即可。

    增加实时的结果统计及最终的结果汇总

    为了使测试程序更加人性化,使其可以实时统计测试结果,结束时自动计算总的结果。这就需要引入父子进程间通信,我们选用共享内存的方式来实现。为了避免进程间同步对测试带来的影响,在共享内存中为每个子进程开辟了一个空间,每个子进程根据索引在自己的空间里写数据,由父进程进行结果的汇总。

    #include <stdio.h>      // printf, fprintf
    #include <sys/wait.h>   // wait
    #include <sys/types.h>  // getpid, wait
    #include <sys/ipc.h>    // shmget, shmat, shmctl, shmdt
    #include <sys/shm.h>    // shmget, shmat, shmctl, shmdt
    #include <signal.h>     // sigaction, SIGLARM
    #include <limits.h>     // LONG_MIN, LONG_MAX, ULLONG_MAX
    #include <errno.h>      // errno
    #include <unistd.h>     // getpid
    #include <string.h>     // memset
    #include "multi-process.h"
    
    typedef struct param_st {   // 自定义测试参数
        long index;
    } Param;
    
    typedef struct result_st {   // 自定义测试结果
        unsigned long long count;
    } Result;
    
    int isStop = 0;             // 用于标记测试终止
    Options opt;                // 命令行选项
    int shmid;                  // 共享内存id
    Result *shm = NULL;         // 共享内存地址,用于存放测试结果
    Result res_total;
    Result res_last;
    
    void handle_signal_child(int sigNum)
    {
        if (sigNum == SIGALRM) {
            isStop = 1;
        }
    }
    
    void handle_signal_parent(int sigNum)
    {
        if (sigNum == SIGALRM) {
            /* DO REAL-TIME STATISTICS */
            memset(&res_total, 0, sizeof(Result));
            for (long i = 0; i < opt.procs; ++i) {
                res_total.count += shm[i].count;
            }
            fprintf(stderr, "total count %12llu,  average %12.0lf/s
    ",
                    res_total.count, (res_total.count - res_last.count)
                    / (double)opt.interval);
            memcpy(&res_last, &res_total, sizeof(Result));
            /* DO REAL-TIME STATISTICS */
            alarm(opt.interval);
        }
    }
    
    /* 实际业务测试函数 */
    void doTest(void *param) {
        unsigned long long i = 0;
        Param *pa = (Param *)param;
        for (; i < ULLONG_MAX && !isStop; ++i) {
            /* DO YOUR WORK */
            ++shm[pa->index].count;
            /* DO YOUR WORK */
        }
    }
    
    int main(int argc, char *argv[]) {
        int rv = 0;
        long i = 0;
        int proc_index = 0;
        int isParent = 1;
        int wstatus = 0;
        pid_t pid = 0;
        struct sigaction act_child;
        struct sigaction act_parent;
    
        rv = process_options(argc, argv, &opt);
        if (rv) {
            return -1;
        }
    
        fprintf(stderr, "
    -----------------------------Start Testing-------------"
                "-----------------
    
    ");
    
    
        /* COMMON INIT */
        shmid = shmget(IPC_PRIVATE, sizeof(sizeof(Result) * opt.procs), 0666);
        if (-1 == shmid) {
            fprintf(stderr, "shmget() failed
    ");
            return -1;
        }
        fprintf(stderr, "shmid = %d
    ", shmid);
        shm = (Result*)shmat(shmid, 0, 0);
        if ((void *) -1 == shm) {
            fprintf(stderr, "shmat() failed
    ");
            return -1;
        }
        memset(shm, 0, sizeof(sizeof(Result) * opt.procs));
        /* COMMON INIT */
    
        while(isParent && i < opt.procs) {
            pid =  fork();
            if(pid == -1) {         /* error */
                fprintf(stderr, "fork failed %d
    ", pid);
                return -1;
            }
            else if(pid == 0) {     /* child */
                isParent = 0;
                proc_index = i;     // 记录进程索引
            }
            else {                  /* parent */
            }
            ++i;
        }
        if(isParent) {
            /* PARENT INIT */
            memset(&act_parent, 0, sizeof(act_parent));
            act_parent.sa_handler = handle_signal_parent;
            /* 使wait被中断时可以自动恢复 */
            act_parent.sa_flags = SA_RESTART;
            rv = sigaction(SIGALRM, &act_parent, NULL);     // 用于定时统计结果
            //signal(SIGALRM, handle_signal_parent);
            if (rv) {
                fprintf(stderr, "sigaction() failed
    ");
                return -1;
            }
            memset(&res_last, 0, sizeof(Result));
            alarm(opt.interval);
            /* PARENT INIT */
            /* DO FINAL STATISTICS */
            Result final;
            memset(&final, 0, sizeof(Result));
            for(i =0 ; i < opt.procs; ++i) {
                pid = wait(&wstatus);                       // 等待子进程结束
                alarm(0);                                   // 终止定时器
                if(pid == -1) {
                    fprintf(stderr, "wait() failed, errno=%d
    ", errno);
                    return -1;
                }
                fprintf(stderr, "process [pid = %6d] exit
    ", pid);
                fprintf(stderr, "process [pid = %6u] count %12llu in %lus,  "
                        "average %12.0lf/s
    ", pid, shm[i].count, opt.duration, 
                        shm[i].count / (double)opt.duration);
                final.count += shm[i].count;
            }
            fprintf(stderr, "total count %12llu in %lus,  average %12.0lf/s
    ",
                   final.count, opt.duration, final.count / (double)opt.duration);
            /* DO FINAL STATISTICS */
            shmdt((void*)shm);
            /* 子进程退出之后自动detach了, 所以这里不需要通过IPC_STAT进行判断 */
            shmctl(shmid, IPC_RMID, 0);
        }
        else {
            /* CHILD INIT */
            Param param;
            memset(&param, 0, sizeof(Param));
            param.index = proc_index;
            /* CHILD INIT */
    
            act_child.sa_handler = handle_signal_child;
            sigemptyset(&act_child.sa_mask);
            //sigaddset(&act_child.sa_mask, SIGQUIT);
            //sigaddset(&act_child.sa_mask, SIGTERM);
            act_child.sa_flags = SA_RESETHAND;
            /* 用于测试时间到时,通知子进程结束测试 */
            rv = sigaction(SIGALRM, &act_child, NULL);
            if (rv) {
                fprintf(stderr, "sigaction() failed
    ");
                return -1;
            }
            //signal(SIGALRM, handle_signal_child);
            alarm(opt.duration);                            // 设置测试时长
            doTest(&param);
            return 0;       /* child finished work */
        }
    
        return 0;
    }
    

    测试效果如下

    ^_^$ ./test -p 4 -d 8 -i 1
    processes:  4
    duration:   8s
    interval:   1s
    
    -----------------------------Start Testing------------------------------
    
    shmid = 2654220
    total count    344235932,  average    344235932/s
    total count    679573681,  average    335337749/s
    total count   1026283924,  average    346710243/s
    total count   1368302354,  average    342018430/s
    total count   1708471662,  average    340169308/s
    total count   2057211138,  average    348739476/s
    total count   2398403059,  average    341191921/s
    process [pid =  25124] exit
    process [pid =  25124] count    688504473 in 8s,  average     86063059/s
    process [pid =  25123] exit
    process [pid =  25123] count    682379115 in 8s,  average     85297389/s
    process [pid =  25125] exit
    process [pid =  25125] count    682467102 in 8s,  average     85308388/s
    process [pid =  25126] exit
    process [pid =  25126] count    688159459 in 8s,  average     86019932/s
    total count   2741510149 in 8s,  average    342688769/s
    

    这里需要特别提一下wait()sigaction()系统调用,默认情况下wait()会阻塞直到有任意一个子进程改变了其状态,或者有一个信号处理函数中断了wait()调用。所以我们程序中的wait()调用就会被自己的SIGALRM信号中断,返回-1同时errnoEINTR。这样我们就需要在wait()外面加一层循环来处理wait()被信号中断的情况。

    通过在sigaction()时增加SA_RESTART标志,被中断的系统调用可以自动重开,也就省去了那个外层循环。另外,signal()封装了sigaction(),它里面默认就是设置了SA_RESTART,不过除非你有确切的理由,不然不建议使用signal()了。

    增加SIGINT和SIGQUIT信号捕获

    截止目前,我们已经完成了多进程的测试及结果统计。但其实还有一个潜在的问题,在实际测试中,我们经常会在测试还没完成时就手动^C终止程序执行。这样我们在程序中申请的共享内存就会得不到释放,造成内存泄漏。所以需要增加对SIGINTSIGQUIT信号的处理函数,在里面做清理工作,释放共享内存。

    如果已经不小心造成了共享内存的泄漏,可以通过如下命令手动进行删除。ipcrm shm <id>,如果是显式指定key的话也可以通过ipcrm -M <key>来进行删除。

    今天,突然想到了,其实有一种更加简单的方法,即在shmat()之后立即进行shmctl(shmid, IPC_RMID, 0);。这样不仅简单,而且中间的空窗期也更短,cool!这样我们再也不用担心,^C造成内存泄漏了哈。

    @@ -88,12 +88,14 @@ int main(int argc, char *argv[]) {
             fprintf(stderr, "shmget() failed
    ");
             return -1;
         }
         fprintf(stderr, "shmid = %d
    ", shmid);
         shm = (Result*)shmat(shmid, 0, 0);
         if ((void *) -1 == shm) {
             fprintf(stderr, "shmat() failed
    ");
             return -1;
         }
    +    /* 这里直接进行IPC_RMID操作,进程退出后会自动detach了, 从而释放共享内存 */
    +    shmctl(shmid, IPC_RMID, 0);
         memset(shm, 0, sizeof(sizeof(Result) * opt.procs));
         /* COMMON INIT */
    
    @@ -156,10 +135,6 @@ int main(int argc, char *argv[]) {
             fprintf(stderr, "total count %12llu in %lus,  average %12.0lf/s
    ",
                    final.count, opt.duration, final.count / (double)opt.duration);
             /* DO FINAL STATISTICS */
    -
    -        shmdt((void*)shm);
    -        /* 子进程退出之后自动detach了, 所以这里不需要通过IPC_STAT进行判断 */
    -        shmctl(shmid, IPC_RMID, 0);
         }
         else {
             /* CHILD INIT */
    

    封装错误判断函数

    为了使代码看起来更加简洁,避免每个函数调用后面跟着一个if(){}判断块,我们对错误判断及日志打印函数进行了一个简单的封装。封装的函数如下:

    其中mylog()单纯打印日志,fail()打印日志后退出进程,fail_if()先判断条件,如果成立打印日志退出,fail_clean_if()也是先判断条件,条件成立则打印日志,执行传入的清理函数。然后退出进程。

    #include "common.h"
    
    #include <stdio.h>      // stderr
    #include <stdarg.h>     // va_start, vfprintf, va_end
    #include <stdlib.h>     // exit
    
    void fail_if(bool condition, const char *fmt, ...) {
        if (condition) {
            va_list args;
            va_start(args, fmt);
            vfprintf(stderr, fmt, args);
            va_end(args);
            exit(1);
        }
    }
    
    void fail(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        exit(1);
    }
    
    void mylog(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
    }
    
    void fail_clean_if(bool condition, cleanup clean, void *param, const char *fmt, ...) {
        if (condition) {
            va_list args;
            va_start(args, fmt);
            vfprintf(stderr, fmt, args);
            va_end(args);
            clean(param);
            exit(1);
        }
    }
    

    添加实际测试用例

    接下来我们来添加实际有意义的测试用例,这里以OpenSSL引擎的性能测试为例来进行说明。为了我们的代码更清晰,已经将具体测试相关的代码独立为一个源文件。common.c封装错误函数,opt.c处理命令行选项,work.c处理具体测试,multi-process.c则负责测试的主控。完整的代码如下:

    • common.h
    #ifndef HEADER_COMMON_H
    #define HEADER_COMMON_H
    #include <stdbool.h>    // bool, true, false
    
    typedef void (*cleanup) (void *);
    void fail_if(bool condition, const char *fmt, ...);
    void fail(const char *fmt, ...);
    void mylog(const char *fmt, ...);
    void fail_clean_if(bool condition, cleanup clean, void *param, const char *fmt, ...);
    
    
    #endif /* HEADER_COMMON_H */
    
    • common.c
    #include "common.h"
    
    #include <stdio.h>      // stderr
    #include <stdarg.h>     // va_start, vfprintf, va_end
    #include <stdlib.h>     // exit
    
    void fail_if(bool condition, const char *fmt, ...) {
        if (condition) {
            va_list args;
            va_start(args, fmt);
            vfprintf(stderr, fmt, args);
            va_end(args);
            exit(1);
        }
    }
    
    void fail(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        exit(1);
    }
    
    void mylog(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
    }
    
    void fail_clean_if(bool condition, cleanup clean, void *param, const char *fmt, ...) {
        if (condition) {
            va_list args;
            va_start(args, fmt);
            vfprintf(stderr, fmt, args);
            va_end(args);
            clean(param);
            exit(1);
        }
    }
    
    • opt.h
    #ifndef HEADER_OPT_H
    #define HEADER_OPT_H
    
    typedef enum test{
        hash, sign, verify, enc, dec
    } Test;
    
    typedef struct options_st {
        long procs;                 // 进程数
        long duration;              // 测试时间
        long interval;              // 统计间隔
        Test test;                  // 测试类型
        long len;                   // 摘要原文长度
        const char *key;            // 密钥文件路径
        const char *cert;           // 证书文件路径
        const char *loglevel;       // 日志等级
    } Options;
    
    /* 处理参数 */
    int process_options(int argc, char *argv[], Options *opt);
    
    #endif /* HEADER_OPT_H */
    
    • opt.c
    #include "opt.h"
    
    #include <limits.h>     // LONG_MIN, LONG_MAX, ULLONG_MAX
    #include <string.h>     // memset
    #include <stdlib.h>     // strtol, abort
    #include <getopt.h>     // geropt_long
    
    #include "common.h"     // mylog
    
    static void ShowHelpInfo(char *name) {
        mylog("Usage: %s [options]
    
    ", name);
        mylog("  Options:
    ");
        mylog("    -p/--proc         Number of processes (default: 1)
    ");
        mylog("    -d/--duration     Duration of test (unit: s, default: 10)
    ");
        mylog("    -i/--interval     Interval of statisics (unit: s, default: 1)
    ");
        mylog("    -t/--test         Test case (hash|sign|verify|enc|dec, default: hash)
    ");
        mylog("    -l/--len          Hash data len (unit: byte, default: 1024)
    ");
        mylog("    -k/--key          PEM Key file path (default: ./key.pem)
    ");
        mylog("    -c/--cert         PEM Cert file path (default: ./cert.pem)
    ");
        mylog("    -o/--loglevel     Engine log level (0-9, default: 0)
    ");
        mylog("    -h/--help         Show the help info
    ");
        mylog("
    ");
        mylog("  Example:
    ");
        mylog("    %s -p 1 -d 30 -i 1 -t sign -k key.pem -c cert.pem
    ", name);
        mylog("
    ");
    }
    
    /* 处理参数 */
    int process_options(int argc, char *argv[], Options *opt) {
        int c = 0;
        int option_index = 0;
        long procs = 1;
        long duration = 10;
        long interval = 1;
        Test test = hash;
        long len = 1024;
        const char *key = "./key.pem";
        const char *cert = "./cert.pem";
        const char *loglevel = "0";
        /**
         *  定义命令行参数列表,option结构的含义如下(详见 man 3 getopt):
         *  struct option {
         *      const char *name;       // 参数的完整名称,对应命令中的 --xxx
         *      int  has_arg;           // 该参数是否带有一个值,如 –config xxx.conf
         *      int *flag;              // 一般设置为NULL
         *      int  val;               // 解析到该参数后getopt_long函数的返回值,
         *                      // 为了方便维护,一般对应getopt_long调用时第三个参数
         *  };
         */
        static struct option arg_options[] =
        {
            {"proc", 1, NULL, 'p'},
            {"duration", 1, NULL, 'd'},
            {"interval", 1, NULL, 'i'},
            {"test", 1, NULL, 't'},
            {"len", 1, NULL, 'l'},
            {"key", 1, NULL, 'k'},
            {"cert", 1, NULL, 'c'},
            {"log", 1, NULL, 'g'},
            {"help", 0, NULL, 'h'},
            {NULL, 0, NULL, 0}
        };
    
        /**
         *  注意:传递给getopt_long的第三个参数对应了命令行参数的缩写形式,如-h等,
         *  如果字符后面带有冒号":",则说明该参数后跟一个值,如-c xxxxxx
         *  如果开头有冒号":",则当一个选项缺少参数时,返回":",否则,返回"?"
         */
        while ((c = getopt_long(argc, argv, ":p:d:i:t:l:k:c:g:h", arg_options, &option_index)
                ) != -1) {
            switch (c) {
            case 'h':
                ShowHelpInfo(argv[0]);
                //fprintf(stderr,"option is -%c, optarv is %s
    ", c, optarg);
                exit(0);
            case 'p':
                procs = strtol(optarg, NULL, 0);
                if (procs == LONG_MIN || procs == LONG_MAX) {
                    mylog("The number of processes (%s) is overflow
    
    ", optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (procs <= 0) {
                    mylog("The number of processes must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 'd':
                duration = strtol(optarg, NULL, 0);
                if (duration == LONG_MIN || duration == LONG_MAX) {
                    mylog("The duration of test (%s) is overflow
    
    ", optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (duration <= 0) {
                    mylog("The duration of test must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 'i':
                interval = strtol(optarg, NULL, 0);
                if (interval == LONG_MIN || interval == LONG_MAX) {
                    mylog("The interval of statistics (%s) is overflow
    
    ",
                          optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (interval <= 0) {
                    mylog("The interval of statistics must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 't':
                if(!strcasecmp("hash", optarg)) {
                    test = hash;
                }
                else if(!strcasecmp("sign", optarg)) {
                    test = sign;
                }
                else if(!strcasecmp("verify", optarg)) {
                    test = verify;
                }
                else if(!strcasecmp("enc", optarg)) {
                    test = enc;
                }
                else if(!strcasecmp("dec", optarg)) {
                    test = dec;
                }
                else {
                    mylog("Unknown test case type
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 'l':
                len = strtol(optarg, NULL, 0);
                if (len == LONG_MIN || len == LONG_MAX) {
                    mylog("The len of hash data (%s) is overflow
    
    ",
                          optarg);
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                else if (len <= 0) {
                    mylog("The len of hash data must be > 0
    
    ");
                    ShowHelpInfo(argv[0]);
                    return -1;
                }
                break;
            case 'k':
                key = optarg;
                break;
            case 'c':
                cert = optarg;
                break;
            case 'g':
                loglevel = optarg;
                break;
            case '?':
                mylog("Unknown option -%c
    
    ", optopt);
                ShowHelpInfo(argv[0]);
                return -1;
            case ':':
                mylog("Option -%c requires an argument
    
    ", optopt);
                ShowHelpInfo(argv[0]);
                return -1;
            default:
                exit(1);
            }
        }
        mylog("processes:  %ld
    ", procs);
        mylog("duration:   %lds
    ", duration);
        mylog("interval:   %lds
    ", interval);
        mylog("test:       #%d
    ", test);
        mylog("len:        %ld bytes
    ", len);
        mylog("key:        %s
    ", key);
        mylog("cert:       %s
    ", cert);
        mylog("loglevel:   %s
    ", loglevel);
        memset(opt, 0, sizeof(Options));
        opt->procs = procs;
        opt->duration = duration;
        opt->interval = interval;
        opt->test = test;
        opt->len = len;
        opt->key = key;
        opt->cert = cert;
        opt->loglevel = loglevel;
    
        return 0;
    }
    
    • work.h
    #ifndef HEADER_WORK_H
    #define HEADER_WORK_H
    #include <openssl/evp.h>
    #include <openssl/bio.h>
    #include "opt.h"
    
    /* 自定义测试参数 */
    typedef struct hash_param_st {
        long index;
        unsigned char *data;
        unsigned int data_len;
        unsigned char md[128];
        unsigned int md_len;
        EVP_MD_CTX *ctx;
    } HashParam;
    
    typedef struct sign_param_st {
        long index;
        unsigned char data[48];
        size_t data_len;
        unsigned char sig[256];
        size_t sig_len;
        EVP_PKEY_CTX *ctx;
        BIO *in;
    } SignParam;
    
    typedef SignParam VerifyParam;
    
    typedef struct enc_param_st {
        long index;
        unsigned char data[48];
        size_t data_len;
        unsigned char enc[256];
        size_t enc_len;
        EVP_PKEY_CTX *ctx;
        BIO *in;
    } EncParam;
    
    typedef EncParam DecParam;
    
    /* 自定义测试结果 */
    typedef struct result_st {
        unsigned long long count;
    } Result;
    
    typedef void (*init_fn) (Options *opt, void **param, long proc_index);
    typedef void (*work_fn) (void *param);
    typedef void (*clean_fn) (void *param);
    
    extern init_fn test_init;
    extern work_fn test_work;
    extern clean_fn test_clean;
    
    void global_init(Options *opt);
    
    void global_clean();
    
    #endif /* HEADER_WORK_H */
    
    
    • work.c
    #include "work.h"
    #include <string.h>
    #include <openssl/engine.h>
    #include <openssl/pem.h>
    #include "common.h"
    
    const char* so_path = "/usr/local/ssl/lib/myengine.so";
    
    init_fn test_init = NULL;
    work_fn test_work = NULL;
    clean_fn test_clean = NULL;
    
    static void hash_init(Options *opt, void **param, long proc_index);
    static void hash_work(void *param);
    static void hash_clean(void *param);
    static void sign_init(Options *opt, void **param, long proc_index);
    static void sign_work(void *param);
    static void sign_clean(void *param);
    static void verify_init(Options *opt, void **param, long proc_index);
    static void verify_work(void *param);
    static void verify_clean(void *param);
    static void encrypt_init(Options *opt, void **param, long proc_index);
    static void encrypt_work(void *param);
    static void encrypt_clean(void *param);
    static void decrypt_init(Options *opt, void **param, long proc_index);
    static void decrypt_work(void *param);
    static void decrypt_clean(void *param);
    
    
    void global_init(Options *opt) {
        ENGINE *e = NULL;
        if (opt->test == hash) {
            test_init = hash_init;
            test_work = hash_work;
            test_clean = hash_clean;
        }
        else if (opt->test == sign) {
            test_init = sign_init;
            test_work = sign_work;
            test_clean = sign_clean;
        }
        else if (opt->test == verify) {
            test_init = verify_init;
            test_work = verify_work;
            test_clean = verify_clean;
        }
        else if (opt->test == enc) {
            test_init = encrypt_init;
            test_work = encrypt_work;
            test_clean = encrypt_clean;
        }
        else if (opt->test == dec) {
            test_init = decrypt_init;
            test_work = decrypt_work;
            test_clean = decrypt_clean;
        }
        OpenSSL_add_all_algorithms();
        /* ENGINE INIT */
        ENGINE_load_dynamic();
        if (!(e = ENGINE_by_id("dynamic"))) {
            fail("ENGINE_by_id("dynamic") fail
    ");
        }
        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", so_path, 0)) {
            fail("ENGINE_ctrl_cmd_string("SO_PATH") fail, so_path = %s
    ", 
                 so_path);
        }
        if (!ENGINE_ctrl_cmd_string(e, "LIST_ADD", "1", 0)) {
            fail("ENGINE_ctrl_cmd_string("LIST_ADD") fail
    ");
        }
        if (!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
            fail("ENGINE_ctrl_cmd_string("LOAD") fail
    ");
        }
        if (!ENGINE_init(e)) {
            fail("ENGINE_init() fail
    ");
        }
        if (!ENGINE_ctrl_cmd_string( e, "ENGINE_SET_LOGLEVEL", opt->loglevel, 0)) {
            fail("ENGINE_ctrl_cmd_string("ENGINE_SET_LOGLEVEL") fail
    ");
        }
        if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
            fail("ENGINE_set_default() fail
    ");
        }
        ENGINE_free(e);
        /* ENGINE INIT */
    }
    
    void global_clean() {
        EVP_cleanup();
    }
    
    static void hash_init(Options *opt, void **param, long proc_index) {
        HashParam *p;
        p = OPENSSL_malloc(sizeof(HashParam));
        fail_if(!p, "OPENSSL_malloc() fail
    ");
        memset(p, 0, sizeof(HashParam));
    
        p->ctx = EVP_MD_CTX_create();
        fail_clean_if(!p->ctx, hash_clean, (void *)p, "EVP_MD_CTX_create() fail
    ");
        EVP_MD_CTX_init(p->ctx);
    
        p->data = OPENSSL_malloc(opt->len);
        fail_clean_if(!p->data, hash_clean, (void *)p, "OPENSSL_malloc() fail
    ");
        fail_clean_if(RAND_bytes(p->data, sizeof(opt->len)) <= 0, hash_clean, (void *)p,
                      "RAND_bytes() fail
    ");
        p->data_len = opt->len;
    
        p->index = proc_index;
    
        *param = p;
    }
    
    static void hash_work(void *param) {
        HashParam *p = (HashParam *)param;
        const EVP_MD *md = EVP_get_digestbyname("SHASH");
        fail_clean_if(!md, hash_clean, param, 
                      "EVP_get_digestbyname("SHASH") fail
    ");
        fail_clean_if(!EVP_DigestInit_ex(p->ctx, md, NULL), 
                      hash_clean, param, "EVP_DigestInit_ex() fail
    ");
        fail_clean_if(!EVP_DigestUpdate(p->ctx, p->data, p->data_len),
                      hash_clean, param, "EVP_DigestUpdate() fail
    ");
        fail_clean_if(!EVP_DigestFinal_ex(p->ctx, p->md, &p->md_len),
                      hash_clean, param, "EVP_DigestFinal_ex() fail
    ");
    }
    
    static void hash_clean(void *param) {
        HashParam *p = (HashParam *)param;
        if (p->data)
            OPENSSL_free(p->data);
        if (p->ctx)
            EVP_MD_CTX_destroy(p->ctx);
        OPENSSL_free(p);
    }
    
    static void sign_init(Options *opt, void **param, long proc_index) {
        SignParam *p;
        EVP_PKEY *pkey;
        p = OPENSSL_malloc(sizeof(SignParam));
        fail_if(!p, "OPENSSL_malloc() fail
    ");
        memset(p, 0, sizeof(SignParam));
    
        p->in = BIO_new_file(opt->key, "r");
        fail_clean_if(!p->in, sign_clean, (void *)p, 
                      "BIO_new_file(%s) fail
    ", opt->key);
        pkey = PEM_read_bio_PrivateKey(p->in, NULL, NULL, NULL);
        fail_clean_if(!pkey, sign_clean, (void *)p, 
                      "PEM_read_bio_PrivateKey() fail
    ");
        p->ctx = EVP_PKEY_CTX_new(pkey, NULL);
        if (!p->ctx) {
            EVP_PKEY_free(pkey);
            fail_clean_if(1, sign_clean, (void *)p, "EVP_PKEY_CTX_new() fail
    ");
        }
        fail_clean_if(!EVP_PKEY_sign_init(p->ctx), sign_clean, (void *)p, 
                      "EVP_PKEY_sign_init() fail
    ");
    
        fail_clean_if(RAND_bytes(p->data, sizeof(p->data)) <= 0, sign_clean, (void *)p,
                      "RAND_bytes() fail
    ");
        p->data_len = sizeof(p->data);
        p->sig_len = sizeof(p->sig);
    
        p->index = proc_index;
        *param = p;
    }
    
    static void sign_work(void *param) {
        SignParam *p = (SignParam *)param;
        fail_clean_if(!EVP_PKEY_sign(p->ctx, p->sig, &p->sig_len, p->data, 
                                     p->data_len), sign_clean, param,
                      "EVP_PKEY_sign() fail
    ");
    }
    
    static void sign_clean(void *param) {
        SignParam *p = (SignParam *)param;
        if (p->ctx)
            EVP_PKEY_CTX_free(p->ctx);
        if (p->in)
            BIO_free(p->in);
        OPENSSL_free(p);
    }
    
    static void verify_init(Options *opt, void **param, long proc_index) {
        VerifyParam *p;
        EVP_PKEY *pkey;
        X509 *x;
        unsigned char data[48];
        size_t data_len;
        unsigned char sig[256];
        size_t sig_len;
        /* 先签名一次,获取数据 */
        SignParam *sp;
        sign_init(opt, (void **)&sp, (long)0);
        sign_work(sp);
        memcpy(data, sp->data, sp->data_len);
        data_len = sp->data_len;
        memcpy(sig, sp->sig, sp->sig_len);
        sig_len = sp->sig_len;
        sign_clean(sp);
    
        p = OPENSSL_malloc(sizeof(VerifyParam));
        fail_if(!p, "OPENSSL_malloc() fail
    ");
        memset(p, 0, sizeof(VerifyParam));
        
        p->in = BIO_new_file(opt->cert, "r");
        fail_clean_if(!p->in, verify_clean, (void *)p, 
                      "BIO_new_file(%s) fail
    ", opt->cert);
        x = PEM_read_bio_X509(p->in, NULL, 0, NULL);
        fail_clean_if(!x, verify_clean, (void *)p, 
                      "PEM_read_bio_X509() fail
    ");
        pkey = X509_get_pubkey(x);
        X509_free(x);
        fail_clean_if(!pkey, verify_clean, (void *)p, "X509_get_pubkey() fail
    ");
        p->ctx = EVP_PKEY_CTX_new(pkey, NULL);
        if (!p->ctx) {
            EVP_PKEY_free(pkey);
            fail_clean_if(1, verify_clean, (void *)p, "EVP_PKEY_CTX_new() fail
    ");
        }
        fail_clean_if(!EVP_PKEY_verify_init(p->ctx), verify_clean, (void *)p, 
                      "EVP_PKEY_verify_init() fail
    ");
    
        memcpy(p->data, data, data_len);
        p->data_len = data_len;
        memcpy(p->sig, sig, sig_len);
        p->sig_len = sig_len;
    
        p->index = proc_index;
        *param = p;
    }
    
    static void verify_work(void *param) {
        VerifyParam *p = (VerifyParam *)param;
        fail_clean_if(!EVP_PKEY_verify(p->ctx, p->sig, p->sig_len, p->data, 
                                     p->data_len), verify_clean, param,
                      "EVP_PKEY_verify() fail
    ");
    }
    
    static void verify_clean(void *param) {
        VerifyParam *p = (VerifyParam *)param;
        if (p->ctx)
            EVP_PKEY_CTX_free(p->ctx);
        if (p->in)
            BIO_free(p->in);
        OPENSSL_free(p);
    }
    
    static void encrypt_init(Options *opt, void **param, long proc_index) {
        EncParam *p;
        EVP_PKEY *pkey;
        X509 *x;
        p = OPENSSL_malloc(sizeof(EncParam));
        fail_if(!p, "OPENSSL_malloc() fail
    ");
        memset(p, 0, sizeof(EncParam));
    
        p->in = BIO_new_file(opt->cert, "r");
        fail_clean_if(!p->in, encrypt_clean, (void *)p, 
                      "BIO_new_file(%s) fail
    ", opt->cert);
        x = PEM_read_bio_X509(p->in, NULL, 0, NULL);
        fail_clean_if(!x, encrypt_clean, (void *)p, 
                      "PEM_read_bio_X509() fail
    ");
        pkey = X509_get_pubkey(x);
        X509_free(x);
        fail_clean_if(!pkey, encrypt_clean, (void *)p, "X509_get_pubkey() fail
    ");
        p->ctx = EVP_PKEY_CTX_new(pkey, NULL);
        if (!p->ctx) {
            EVP_PKEY_free(pkey);
            fail_clean_if(1, encrypt_clean, (void *)p, "EVP_PKEY_CTX_new() fail
    ");
        }
        fail_clean_if(!EVP_PKEY_encrypt_init(p->ctx), encrypt_clean, (void *)p, 
                      "EVP_PKEY_encrypt_init() fail
    ");
    
        fail_clean_if(RAND_bytes(p->data, sizeof(p->data)) <= 0, encrypt_clean, (void *)p,
                      "RAND_bytes() fail
    ");
        p->data_len = sizeof(p->data);
        p->enc_len = sizeof(p->enc);
    
        p->index = proc_index;
        *param = p;
    }
    
    static void encrypt_work(void *param) {
        EncParam *p = (EncParam *)param;
        fail_clean_if(!EVP_PKEY_encrypt(p->ctx, p->enc, &p->enc_len, p->data, 
                                     p->data_len), encrypt_clean, param,
                      "EVP_PKEY_encrypt() fail
    ");
    }
    
    static void encrypt_clean(void *param) {
        EncParam *p = (EncParam *)param;
        if (p->ctx)
            EVP_PKEY_CTX_free(p->ctx);
        if (p->in)
            BIO_free(p->in);
        OPENSSL_free(p);
    }
    
    static void decrypt_init(Options *opt, void **param, long proc_index) {
        DecParam *p;
        EVP_PKEY *pkey;
        unsigned char data[48];
        size_t data_len;
        unsigned char enc[256];
        size_t enc_len;
        /* 先加密一次,获取数据 */
        EncParam *ep;
        encrypt_init(opt, (void **)&ep, (long)0);
        encrypt_work(ep);
        memcpy(data, ep->data, ep->data_len);
        data_len = ep->data_len;
        memcpy(enc, ep->enc, ep->enc_len);
        enc_len = ep->enc_len;
        encrypt_clean(ep);
    
        p = OPENSSL_malloc(sizeof(DecParam));
        fail_if(!p, "OPENSSL_malloc() fail
    ");
        memset(p, 0, sizeof(DecParam));
        
        p->in = BIO_new_file(opt->key, "r");
        fail_clean_if(!p->in, decrypt_clean, (void *)p, 
                      "BIO_new_file(%s) fail
    ", opt->key);
        pkey = PEM_read_bio_PrivateKey(p->in, NULL, NULL, PEM_AUTO_KEYPASS);
        fail_clean_if(!pkey, decrypt_clean, (void *)p, 
                      "PEM_read_bio_PrivateKey() fail
    ");
        p->ctx = EVP_PKEY_CTX_new(pkey, NULL);
        if (!p->ctx) {
            EVP_PKEY_free(pkey);
            fail_clean_if(1, decrypt_clean, (void *)p, "EVP_PKEY_CTX_new() fail
    ");
        }
        fail_clean_if(!EVP_PKEY_decrypt_init(p->ctx), decrypt_clean, (void *)p, 
                      "EVP_PKEY_decrypt_init() fail
    ");
    
        memcpy(p->data, data, data_len);
        p->data_len = data_len;
        memcpy(p->enc, enc, enc_len);
        p->enc_len = enc_len;
    
        p->index = proc_index;
        *param = p;
    }
    
    static void decrypt_work(void *param) {
        DecParam *p = (DecParam *)param;
        fail_clean_if(!EVP_PKEY_decrypt(p->ctx, p->data, &p->data_len, p->enc, 
                                     p->enc_len), decrypt_clean, param,
                      "EVP_PKEY_decrypt() fail
    ");
    }
    
    static void decrypt_clean(void *param) {
        DecParam *p = (DecParam *)param;
        if (p->ctx)
            EVP_PKEY_CTX_free(p->ctx);
        if (p->in)
            BIO_free(p->in);
        OPENSSL_free(p);
    }
    
    • multi-process.c
    #include <sys/wait.h>   // wait
    #include <sys/types.h>  // getpid, wait
    #include <sys/ipc.h>    // shmget, shmat, shmctl, shmdt
    #include <sys/shm.h>    // shmget, shmat, shmctl, shmdt
    #include <signal.h>     // sigaction, SIGLARM
    #include <limits.h>     // LONG_MIN, LONG_MAX, ULLONG_MAX
    #include <errno.h>      // errno
    #include <unistd.h>     // getpid
    #include <string.h>     // memset
    #include "common.h"
    #include "opt.h"
    #include "work.h"
    
    int isStop = 0;             // 用于标记测试终止
    Options opt;                // 命令行选项
    int shmid;                  // 共享内存id
    Result *shm = NULL;         // 共享内存地址,用于存放测试结果
    Result res_total;
    Result res_last;
    
    void handle_signal_child(int sigNum)
    {
        if (sigNum == SIGALRM) {
            isStop = 1;
        }
    }
    
    void handle_signal_parent(int sigNum)
    {
        if (sigNum == SIGALRM) {
            /* DO REAL-TIME STATISTICS */
            memset(&res_total, 0, sizeof(Result));
            long i = 0;
            for (; i < opt.procs; ++i) {
                res_total.count += shm[i].count;
            }
            mylog("total count %12llu,  average %12.0lf/s
    ",
                    res_total.count, (res_total.count - res_last.count)
                    / (double)opt.interval);
            memcpy(&res_last, &res_total, sizeof(Result));
            /* DO REAL-TIME STATISTICS */
            alarm(opt.interval);
        }
    }
    
    /* 执行测试主循环函数 */
    void doTest(void *param) {
        unsigned long long i = 0;
        HashParam *pa = (HashParam *)param;
        for (; i < ULLONG_MAX && !isStop; ++i) {
            /* DO YOUR WORK */
            test_work(param);
            ++shm[pa->index].count;
            /* DO YOUR WORK */
        }
    }
    
    int main(int argc, char *argv[]) {
        int rv = 0;
        long i = 0;
        int proc_index = 0;
        int isParent = 1;
        int wstatus = 0;
        pid_t pid = 0;
        struct sigaction act_child;
        struct sigaction act_parent;
    
        rv = process_options(argc, argv, &opt);
        if (rv) {
            return -1;
        }
    
        mylog("
    -----------------------------Start Testing-----------------------"
              "-------
    
    ");
    
    
        /* COMMON INIT */
        shmid = shmget(IPC_PRIVATE, sizeof(sizeof(Result) * opt.procs), 0666);
        fail_if(-1 == shmid, "shmget() failed
    ");
        mylog("shmid = %d
    ", shmid);
        shm = (Result*)shmat(shmid, 0, 0);
        fail_if((void *) -1 == shm, "shmat() failed
    ");
    
        /* 这里直接进行IPC_RMID操作,进程退出之后会自动detach了, 从而释放共享内存 */
        shmctl(shmid, IPC_RMID, 0);
        memset(shm, 0, sizeof(sizeof(Result) * opt.procs));
    
        global_init(&opt);
        /* COMMON INIT */
    
        while(isParent && i < opt.procs) {
            pid =  fork();
            fail_if(-1 == pid, "fork failed %d
    ", pid);    /* error */
            if(pid == 0) {                                  /* child */
                isParent = 0;
                proc_index = i;                             // 记录进程索引
            }
            else {                                          /* parent */
            }
            ++i;
        }
        if(isParent) {
            /* PARENT INIT */
            memset(&act_parent, 0, sizeof(act_parent));
            act_parent.sa_handler = handle_signal_parent;
            /* 使wait被中断时可以自动恢复 */
            act_parent.sa_flags = SA_RESTART;
            rv = sigaction(SIGALRM, &act_parent, NULL);     // 用于定时统计结果
            fail_if(rv, "sigaction() failed
    ");
    
            memset(&res_last, 0, sizeof(Result));
            alarm(opt.interval);
            /* PARENT INIT */
    
            /* DO FINAL STATISTICS */
            Result final;
            memset(&final, 0, sizeof(Result));
            for(i =0 ; i < opt.procs; ++i) {
                pid = wait(&wstatus);                       // 等待子进程结束
                alarm(0);                                   // 终止定时器
                fail_if(-1 == pid, "wait() failed, errno=%d
    ", errno);
                mylog("process [pid = %6d] exit
    ", pid);
                mylog("process [pid = %6u] count %12llu in %lus,"
                      "  average %12.0lf/s
    ", pid, shm[i].count, opt.duration, 
                      shm[i].count / (double)opt.duration);
                final.count += shm[i].count;
            }
            mylog("total count %12llu in %lus,  average %12.0lf/s
    ", 
                   final.count, opt.duration, final.count / (double)opt.duration);
            /* DO FINAL STATISTICS */
    
            /* PARENT CLEANUP */
            global_clean();
            /* PARENT CLEANUP */
        }
        else {
            /* CHILD INIT */
            void *param;
            test_init(&opt, &param, proc_index);
            /* CHILD INIT */
    
            act_child.sa_handler = handle_signal_child;
            sigemptyset(&act_child.sa_mask);
            act_child.sa_flags = SA_RESETHAND;
            /* 用于测试时间到时,通知子进程结束测试 */
            rv = sigaction(SIGALRM, &act_child, NULL);
            fail_if(rv, "sigaction() failed
    ");
            alarm(opt.duration);                            // 设置测试时长
            doTest(param);
    
            /* CHILD CLEANUP */
            test_clean(param);
            global_clean();
            /* CHILD CLEANUP */
            return 0;       /* child finished work */
        }
    
        return 0;
    }
    

    总结

    至此,一个多进程的测试程序就算完成了。读者可以根据自身测试需要,按如下步骤修改以进行自定义的测试。

    1. opt.copt.h中,添加需要的命令行选项
    2. work.cwork.h中,global_init()global_clean()内进行全局的初始化及全局的清理工作
    3. work.cwork.h中,添加自定义的测试函数,以及相应的测试参数和测试结果
    4. 主控multi-process.c通过test_init()test_work()test_clean()调用测试相关的初始化、执行及清理工作
    5. 主控multi-process.c中,修改测试结果的更新与统计操作。

    主控multi-process.c中:

    • COMMON INITPARENT INITCHILD INIT代码块处分别进行公共的初始化工作和父子进程特定的初始化工作。
    • PARENT CLEANUPCHILD CLEANUP代码块处则分别进行对应的清理工作。
    • DO YOUR WORK处执行具体的测试,DO REAL-TIME STATISTICSDO FINAL STATISTICS处进行测试结果的统计。

    当然本文还有一些不足之处,比如当前是用的OpenSSL1.0.2接口,没有兼容不同版本的OpenSSL。还有代码风格的问题,在函数命名和注释方式上不统一。但是考虑到这个是测试程序,就不计较这么多了(

  • 相关阅读:
    maven build和push image中遇到的坑(学习过程记录)
    jmeter中beanshell postprocessor结合fastjson库提取不确定个数的json参数
    (转)细说linux挂载
    《软件性能测试从零开始》要点摘录
    《软件测试经验与教训》内容摘录
    关于敏捷的一点感受
    xpath定位中starts-with、contains、text()的用法
    python中的threading模块使用说明
    (转)Linux下用户组、文件权限详解
    LeetCode——树
  • 原文地址:https://www.cnblogs.com/logchen/p/15145460.html
Copyright © 2011-2022 走看看