1// SPDX-License-Identifier: GPL-2.0+
2
3/*
4 * Context switch microbenchmark.
5 *
6 * Copyright 2018, Anton Blanchard, IBM Corp.
7 */
8
9#define _GNU_SOURCE
10#include <assert.h>
11#include <errno.h>
12#include <getopt.h>
13#include <limits.h>
14#include <linux/futex.h>
15#include <pthread.h>
16#include <sched.h>
17#include <signal.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <sys/shm.h>
22#include <sys/syscall.h>
23#include <sys/time.h>
24#include <sys/types.h>
25#include <sys/wait.h>
26#include <unistd.h>
27
28static unsigned int timeout = 30;
29
30static void set_cpu(int cpu)
31{
32	cpu_set_t cpuset;
33
34	if (cpu == -1)
35		return;
36
37	CPU_ZERO(&cpuset);
38	CPU_SET(cpu, &cpuset);
39
40	if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) {
41		perror("sched_setaffinity");
42		exit(1);
43	}
44}
45
46static void start_process_on(void *(*fn)(void *), void *arg, int cpu)
47{
48	int pid;
49
50	pid = fork();
51	if (pid == -1) {
52		perror("fork");
53		exit(1);
54	}
55
56	if (pid)
57		return;
58
59	set_cpu(cpu);
60
61	fn(arg);
62
63	exit(0);
64}
65
66static int cpu;
67static int do_fork = 0;
68static int do_vfork = 0;
69static int do_exec = 0;
70static char *exec_file;
71static int exec_target = 0;
72static unsigned long iterations;
73static unsigned long iterations_prev;
74
75static void run_exec(void)
76{
77	char *const argv[] = { "./exec_target", NULL };
78
79	if (execve("./exec_target", argv, NULL) == -1) {
80		perror("execve");
81		exit(1);
82	}
83}
84
85static void bench_fork(void)
86{
87	while (1) {
88		pid_t pid = fork();
89		if (pid == -1) {
90			perror("fork");
91			exit(1);
92		}
93		if (pid == 0) {
94			if (do_exec)
95				run_exec();
96			_exit(0);
97		}
98		pid = waitpid(pid, NULL, 0);
99		if (pid == -1) {
100			perror("waitpid");
101			exit(1);
102		}
103		iterations++;
104	}
105}
106
107static void bench_vfork(void)
108{
109	while (1) {
110		pid_t pid = vfork();
111		if (pid == -1) {
112			perror("fork");
113			exit(1);
114		}
115		if (pid == 0) {
116			if (do_exec)
117				run_exec();
118			_exit(0);
119		}
120		pid = waitpid(pid, NULL, 0);
121		if (pid == -1) {
122			perror("waitpid");
123			exit(1);
124		}
125		iterations++;
126	}
127}
128
129static void *null_fn(void *arg)
130{
131	pthread_exit(NULL);
132}
133
134static void bench_thread(void)
135{
136	pthread_t tid;
137	cpu_set_t cpuset;
138	pthread_attr_t attr;
139	int rc;
140
141	rc = pthread_attr_init(&attr);
142	if (rc) {
143		errno = rc;
144		perror("pthread_attr_init");
145		exit(1);
146	}
147
148	if (cpu != -1) {
149		CPU_ZERO(&cpuset);
150		CPU_SET(cpu, &cpuset);
151
152		rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
153		if (rc) {
154			errno = rc;
155			perror("pthread_attr_setaffinity_np");
156			exit(1);
157		}
158	}
159
160	while (1) {
161		rc = pthread_create(&tid, &attr, null_fn, NULL);
162		if (rc) {
163			errno = rc;
164			perror("pthread_create");
165			exit(1);
166		}
167		rc = pthread_join(tid, NULL);
168		if (rc) {
169			errno = rc;
170			perror("pthread_join");
171			exit(1);
172		}
173		iterations++;
174	}
175}
176
177static void sigalrm_handler(int junk)
178{
179	unsigned long i = iterations;
180
181	printf("%ld\n", i - iterations_prev);
182	iterations_prev = i;
183
184	if (--timeout == 0)
185		kill(0, SIGUSR1);
186
187	alarm(1);
188}
189
190static void sigusr1_handler(int junk)
191{
192	exit(0);
193}
194
195static void *bench_proc(void *arg)
196{
197	signal(SIGALRM, sigalrm_handler);
198	alarm(1);
199
200	if (do_fork)
201		bench_fork();
202	else if (do_vfork)
203		bench_vfork();
204	else
205		bench_thread();
206
207	return NULL;
208}
209
210static struct option options[] = {
211	{ "fork", no_argument, &do_fork, 1 },
212	{ "vfork", no_argument, &do_vfork, 1 },
213	{ "exec", no_argument, &do_exec, 1 },
214	{ "timeout", required_argument, 0, 's' },
215	{ "exec-target", no_argument, &exec_target, 1 },
216	{ NULL },
217};
218
219static void usage(void)
220{
221	fprintf(stderr, "Usage: fork <options> CPU\n\n");
222	fprintf(stderr, "\t\t--fork\tUse fork() (default threads)\n");
223	fprintf(stderr, "\t\t--vfork\tUse vfork() (default threads)\n");
224	fprintf(stderr, "\t\t--exec\tAlso exec() (default no exec)\n");
225	fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
226	fprintf(stderr, "\t\t--exec-target\tInternal option for exec workload\n");
227}
228
229int main(int argc, char *argv[])
230{
231	signed char c;
232
233	while (1) {
234		int option_index = 0;
235
236		c = getopt_long(argc, argv, "", options, &option_index);
237
238		if (c == -1)
239			break;
240
241		switch (c) {
242		case 0:
243			if (options[option_index].flag != 0)
244				break;
245
246			usage();
247			exit(1);
248			break;
249
250		case 's':
251			timeout = atoi(optarg);
252			break;
253
254		default:
255			usage();
256			exit(1);
257		}
258	}
259
260	if (do_fork && do_vfork) {
261		usage();
262		exit(1);
263	}
264	if (do_exec && !do_fork && !do_vfork) {
265		usage();
266		exit(1);
267	}
268
269	if (do_exec) {
270		char *dirname = strdup(argv[0]);
271		int i;
272		i = strlen(dirname) - 1;
273		while (i) {
274			if (dirname[i] == '/') {
275				dirname[i] = '\0';
276				if (chdir(dirname) == -1) {
277					perror("chdir");
278					exit(1);
279				}
280				break;
281			}
282			i--;
283		}
284	}
285
286	if (exec_target) {
287		exit(0);
288	}
289
290	if (((argc - optind) != 1)) {
291		cpu = -1;
292	} else {
293		cpu = atoi(argv[optind++]);
294	}
295
296	if (do_exec)
297		exec_file = argv[0];
298
299	set_cpu(cpu);
300
301	printf("Using ");
302	if (do_fork)
303		printf("fork");
304	else if (do_vfork)
305		printf("vfork");
306	else
307		printf("clone");
308
309	if (do_exec)
310		printf(" + exec");
311
312	printf(" on cpu %d\n", cpu);
313
314	/* Create a new process group so we can signal everyone for exit */
315	setpgid(getpid(), getpid());
316
317	signal(SIGUSR1, sigusr1_handler);
318
319	start_process_on(bench_proc, NULL, cpu);
320
321	while (1)
322		sleep(3600);
323
324	return 0;
325}
326