如何编写一个多进程性能测试程序

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

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

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

本文源码已开源Github

选项和参数的处理

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

点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#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]\n\n", name);
printf(" Options:\n");
printf(" -p/--proc Number of processes (default: 1)\n");
printf(" -d/--duration Duration of test (unit: s, default: 10)\n");
printf(" -h/--help Show the help info\n");
printf("\n");
printf(" Example:\n");
printf(" %s -p 4 -d 30\n", name);
printf("\n");
}

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\n", 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\n\n",
optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (procs <= 0) {
fprintf(stderr, "The number of processes must be > 0\n\n");
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\n\n",
optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (procs <= 0) {
fprintf(stderr, "The duration of test must be > 0\n\n");
ShowHelpInfo(argv[0]);
return -1;
}
break;
case '?':
fprintf (stderr, "Unknown option -%c\n\n", optopt);
ShowHelpInfo(argv[0]);
return -1;
case ':':
fprintf (stderr, "Option -%c requires an argument\n\n", optopt);
ShowHelpInfo(argv[0]);
return -1;
default:
abort();
}
}
printf("processes: %ld\n", procs);
printf("duration: %lds\n", duration);
printf("\n-----------------------------Start Testing----------------------"
"--------\n\n");

printf("Hello world\n");
return 0;

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

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

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

效果如下

1
2
3
4
5
6
7
8
9
10
11
^_^$ 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

选项或参数错误时

1
2
3
4
5
6
7
8
9
10
11
12
^_^$ ./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设置一个定时器,然后进行业务测试。

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

点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#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\n", 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("\n-----------------------------Start Testing----------------------"
"--------\n\n");


/* COMMON INIT */
/* COMMON INIT */

while(isParent && i < opt.procs) {
pid = fork();
if(pid == -1) { /* error */
fprintf(stderr, "fork failed %d\n", 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\n", 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\n");
return -1;
}
//signal(SIGALRM, handle_signal_child);
alarm(opt.duration); // 设置测试时长
doTest(&param);
return 0; /* child finished work */
}

printf("Hello World!\n");
return 0;
}

效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
^_^$ ./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 */处添加实际的测试逻辑即可。

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

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

点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#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\n",
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, "\n-----------------------------Start Testing-------------"
"-----------------\n\n");


/* COMMON INIT */
shmid = shmget(IPC_PRIVATE, sizeof(sizeof(Result) * opt.procs), 0666);
if (-1 == shmid) {
fprintf(stderr, "shmget() failed\n");
return -1;
}
fprintf(stderr, "shmid = %d\n", shmid);
shm = (Result*)shmat(shmid, 0, 0);
if ((void *) -1 == shm) {
fprintf(stderr, "shmat() failed\n");
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\n", 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\n");
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\n", errno);
return -1;
}
fprintf(stderr, "process [pid = %6d] exit\n", pid);
fprintf(stderr, "process [pid = %6u] count %12llu in %lus, "
"average %12.0lf/s\n", 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\n",
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\n");
return -1;
}
//signal(SIGALRM, handle_signal_child);
alarm(opt.duration); // 设置测试时长
doTest(&param);
return 0; /* child finished work */
}

return 0;
}

测试效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
^_^$ ./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造成内存泄漏了哈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@@ -88,12 +88,14 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "shmget() failed\n");
return -1;
}
fprintf(stderr, "shmid = %d\n", shmid);
shm = (Result*)shmat(shmid, 0, 0);
if ((void *) -1 == shm) {
fprintf(stderr, "shmat() failed\n");
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\n",
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()也是先判断条件,条件成立则打印日志,执行传入的清理函数。然后退出进程。

点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
#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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#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]\n\n", name);
mylog(" Options:\n");
mylog(" -p/--proc Number of processes (default: 1)\n");
mylog(" -d/--duration Duration of test (unit: s, default: 10)\n");
mylog(" -i/--interval Interval of statisics (unit: s, default: 1)\n");
mylog(" -t/--test Test case (hash|sign|verify|enc|dec, default: hash)\n");
mylog(" -l/--len Hash data len (unit: byte, default: 1024)\n");
mylog(" -k/--key PEM Key file path (default: ./key.pem)\n");
mylog(" -c/--cert PEM Cert file path (default: ./cert.pem)\n");
mylog(" -o/--loglevel Engine log level (0-9, default: 0)\n");
mylog(" -h/--help Show the help info\n");
mylog("\n");
mylog(" Example:\n");
mylog(" %s -p 1 -d 30 -i 1 -t sign -k key.pem -c cert.pem\n", name);
mylog("\n");
}

/* 处理参数 */
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\n", 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\n\n", optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (procs <= 0) {
mylog("The number of processes must be > 0\n\n");
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\n\n", optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (duration <= 0) {
mylog("The duration of test must be > 0\n\n");
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\n\n",
optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (interval <= 0) {
mylog("The interval of statistics must be > 0\n\n");
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\n\n");
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\n\n",
optarg);
ShowHelpInfo(argv[0]);
return -1;
}
else if (len <= 0) {
mylog("The len of hash data must be > 0\n\n");
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\n\n", optopt);
ShowHelpInfo(argv[0]);
return -1;
case ':':
mylog("Option -%c requires an argument\n\n", optopt);
ShowHelpInfo(argv[0]);
return -1;
default:
exit(1);
}
}
mylog("processes: %ld\n", procs);
mylog("duration: %lds\n", duration);
mylog("interval: %lds\n", interval);
mylog("test: #%d\n", test);
mylog("len: %ld bytes\n", len);
mylog("key: %s\n", key);
mylog("cert: %s\n", cert);
mylog("loglevel: %s\n", 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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
#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\n");
}
if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", so_path, 0)) {
fail("ENGINE_ctrl_cmd_string(\"SO_PATH\") fail, so_path = %s\n",
so_path);
}
if (!ENGINE_ctrl_cmd_string(e, "LIST_ADD", "1", 0)) {
fail("ENGINE_ctrl_cmd_string(\"LIST_ADD\") fail\n");
}
if (!ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
fail("ENGINE_ctrl_cmd_string(\"LOAD\") fail\n");
}
if (!ENGINE_init(e)) {
fail("ENGINE_init() fail\n");
}
if (!ENGINE_ctrl_cmd_string( e, "ENGINE_SET_LOGLEVEL", opt->loglevel, 0)) {
fail("ENGINE_ctrl_cmd_string(\"ENGINE_SET_LOGLEVEL\") fail\n");
}
if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
fail("ENGINE_set_default() fail\n");
}
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\n");
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\n");
EVP_MD_CTX_init(p->ctx);

p->data = OPENSSL_malloc(opt->len);
fail_clean_if(!p->data, hash_clean, (void *)p, "OPENSSL_malloc() fail\n");
fail_clean_if(RAND_bytes(p->data, sizeof(opt->len)) <= 0, hash_clean, (void *)p,
"RAND_bytes() fail\n");
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\n");
fail_clean_if(!EVP_DigestInit_ex(p->ctx, md, NULL),
hash_clean, param, "EVP_DigestInit_ex() fail\n");
fail_clean_if(!EVP_DigestUpdate(p->ctx, p->data, p->data_len),
hash_clean, param, "EVP_DigestUpdate() fail\n");
fail_clean_if(!EVP_DigestFinal_ex(p->ctx, p->md, &p->md_len),
hash_clean, param, "EVP_DigestFinal_ex() fail\n");
}

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\n");
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\n", 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\n");
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\n");
}
fail_clean_if(!EVP_PKEY_sign_init(p->ctx), sign_clean, (void *)p,
"EVP_PKEY_sign_init() fail\n");

fail_clean_if(RAND_bytes(p->data, sizeof(p->data)) <= 0, sign_clean, (void *)p,
"RAND_bytes() fail\n");
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\n");
}

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\n");
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\n", 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\n");
pkey = X509_get_pubkey(x);
X509_free(x);
fail_clean_if(!pkey, verify_clean, (void *)p, "X509_get_pubkey() fail\n");
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\n");
}
fail_clean_if(!EVP_PKEY_verify_init(p->ctx), verify_clean, (void *)p,
"EVP_PKEY_verify_init() fail\n");

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\n");
}

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\n");
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\n", 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\n");
pkey = X509_get_pubkey(x);
X509_free(x);
fail_clean_if(!pkey, encrypt_clean, (void *)p, "X509_get_pubkey() fail\n");
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\n");
}
fail_clean_if(!EVP_PKEY_encrypt_init(p->ctx), encrypt_clean, (void *)p,
"EVP_PKEY_encrypt_init() fail\n");

fail_clean_if(RAND_bytes(p->data, sizeof(p->data)) <= 0, encrypt_clean, (void *)p,
"RAND_bytes() fail\n");
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\n");
}

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\n");
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\n", 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\n");
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\n");
}
fail_clean_if(!EVP_PKEY_decrypt_init(p->ctx), decrypt_clean, (void *)p,
"EVP_PKEY_decrypt_init() fail\n");

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\n");
}

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
点击展开代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#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\n",
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("\n-----------------------------Start Testing-----------------------"
"-------\n\n");


/* COMMON INIT */
shmid = shmget(IPC_PRIVATE, sizeof(sizeof(Result) * opt.procs), 0666);
fail_if(-1 == shmid, "shmget() failed\n");
mylog("shmid = %d\n", shmid);
shm = (Result*)shmat(shmid, 0, 0);
fail_if((void *) -1 == shm, "shmat() failed\n");

/* 这里直接进行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\n", 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\n");

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\n", errno);
mylog("process [pid = %6d] exit\n", pid);
mylog("process [pid = %6u] count %12llu in %lus,"
" average %12.0lf/s\n", 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\n",
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\n");
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。还有代码风格的问题,在函数命名和注释方式上不统一。但是考虑到这个是测试程序,就不计较这么多了(^。^)

-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道