1 /*
2  * Context switch microbenchmark.
3  *
4  * Copyright (C) 2015 Anton Blanchard <anton@au.ibm.com>, IBM
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  */
11 
12 #define _GNU_SOURCE
13 #include <sched.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <unistd.h>
17 #include <stdlib.h>
18 #include <getopt.h>
19 #include <signal.h>
20 #include <assert.h>
21 #include <pthread.h>
22 #include <limits.h>
23 #include <sys/time.h>
24 #include <sys/syscall.h>
25 #include <sys/types.h>
26 #include <sys/shm.h>
27 #include <linux/futex.h>
28 
29 #include "../utils.h"
30 
31 static unsigned int timeout = 30;
32 
33 static int touch_vdso;
34 struct timeval tv;
35 
36 static int touch_fp = 1;
37 double fp;
38 
39 static int touch_vector = 1;
40 typedef int v4si __attribute__ ((vector_size (16)));
41 v4si a, b, c;
42 
43 #ifdef __powerpc__
44 static int touch_altivec = 1;
45 
46 static void __attribute__((__target__("no-vsx"))) altivec_touch_fn(void)
47 {
48 	c = a + b;
49 }
50 #endif
51 
52 static void touch(void)
53 {
54 	if (touch_vdso)
55 		gettimeofday(&tv, NULL);
56 
57 	if (touch_fp)
58 		fp += 0.1;
59 
60 #ifdef __powerpc__
61 	if (touch_altivec)
62 		altivec_touch_fn();
63 #endif
64 
65 	if (touch_vector)
66 		c = a + b;
67 
68 	asm volatile("# %0 %1 %2": : "r"(&tv), "r"(&fp), "r"(&c));
69 }
70 
71 static void start_thread_on(void *(*fn)(void *), void *arg, unsigned long cpu)
72 {
73 	pthread_t tid;
74 	cpu_set_t cpuset;
75 	pthread_attr_t attr;
76 
77 	CPU_ZERO(&cpuset);
78 	CPU_SET(cpu, &cpuset);
79 
80 	pthread_attr_init(&attr);
81 
82 	if (pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset)) {
83 		perror("pthread_attr_setaffinity_np");
84 		exit(1);
85 	}
86 
87 	if (pthread_create(&tid, &attr, fn, arg)) {
88 		perror("pthread_create");
89 		exit(1);
90 	}
91 }
92 
93 static void start_process_on(void *(*fn)(void *), void *arg, unsigned long cpu)
94 {
95 	int pid;
96 	cpu_set_t cpuset;
97 
98 	pid = fork();
99 	if (pid == -1) {
100 		perror("fork");
101 		exit(1);
102 	}
103 
104 	if (pid)
105 		return;
106 
107 	CPU_ZERO(&cpuset);
108 	CPU_SET(cpu, &cpuset);
109 
110 	if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) {
111 		perror("sched_setaffinity");
112 		exit(1);
113 	}
114 
115 	fn(arg);
116 
117 	exit(0);
118 }
119 
120 static unsigned long iterations;
121 static unsigned long iterations_prev;
122 
123 static void sigalrm_handler(int junk)
124 {
125 	unsigned long i = iterations;
126 
127 	printf("%ld\n", i - iterations_prev);
128 	iterations_prev = i;
129 
130 	if (--timeout == 0)
131 		kill(0, SIGUSR1);
132 
133 	alarm(1);
134 }
135 
136 static void sigusr1_handler(int junk)
137 {
138 	exit(0);
139 }
140 
141 struct actions {
142 	void (*setup)(int, int);
143 	void *(*thread1)(void *);
144 	void *(*thread2)(void *);
145 };
146 
147 #define READ 0
148 #define WRITE 1
149 
150 static int pipe_fd1[2];
151 static int pipe_fd2[2];
152 
153 static void pipe_setup(int cpu1, int cpu2)
154 {
155 	if (pipe(pipe_fd1) || pipe(pipe_fd2))
156 		exit(1);
157 }
158 
159 static void *pipe_thread1(void *arg)
160 {
161 	signal(SIGALRM, sigalrm_handler);
162 	alarm(1);
163 
164 	while (1) {
165 		assert(read(pipe_fd1[READ], &c, 1) == 1);
166 		touch();
167 
168 		assert(write(pipe_fd2[WRITE], &c, 1) == 1);
169 		touch();
170 
171 		iterations += 2;
172 	}
173 
174 	return NULL;
175 }
176 
177 static void *pipe_thread2(void *arg)
178 {
179 	while (1) {
180 		assert(write(pipe_fd1[WRITE], &c, 1) == 1);
181 		touch();
182 
183 		assert(read(pipe_fd2[READ], &c, 1) == 1);
184 		touch();
185 	}
186 
187 	return NULL;
188 }
189 
190 static struct actions pipe_actions = {
191 	.setup = pipe_setup,
192 	.thread1 = pipe_thread1,
193 	.thread2 = pipe_thread2,
194 };
195 
196 static void yield_setup(int cpu1, int cpu2)
197 {
198 	if (cpu1 != cpu2) {
199 		fprintf(stderr, "Both threads must be on the same CPU for yield test\n");
200 		exit(1);
201 	}
202 }
203 
204 static void *yield_thread1(void *arg)
205 {
206 	signal(SIGALRM, sigalrm_handler);
207 	alarm(1);
208 
209 	while (1) {
210 		sched_yield();
211 		touch();
212 
213 		iterations += 2;
214 	}
215 
216 	return NULL;
217 }
218 
219 static void *yield_thread2(void *arg)
220 {
221 	while (1) {
222 		sched_yield();
223 		touch();
224 	}
225 
226 	return NULL;
227 }
228 
229 static struct actions yield_actions = {
230 	.setup = yield_setup,
231 	.thread1 = yield_thread1,
232 	.thread2 = yield_thread2,
233 };
234 
235 static long sys_futex(void *addr1, int op, int val1, struct timespec *timeout,
236 		      void *addr2, int val3)
237 {
238 	return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
239 }
240 
241 static unsigned long cmpxchg(unsigned long *p, unsigned long expected,
242 			     unsigned long desired)
243 {
244 	unsigned long exp = expected;
245 
246 	__atomic_compare_exchange_n(p, &exp, desired, 0,
247 				    __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
248 	return exp;
249 }
250 
251 static unsigned long xchg(unsigned long *p, unsigned long val)
252 {
253 	return __atomic_exchange_n(p, val, __ATOMIC_SEQ_CST);
254 }
255 
256 static int mutex_lock(unsigned long *m)
257 {
258 	int c;
259 
260 	c = cmpxchg(m, 0, 1);
261 	if (!c)
262 		return 0;
263 
264 	if (c == 1)
265 		c = xchg(m, 2);
266 
267 	while (c) {
268 		sys_futex(m, FUTEX_WAIT, 2, NULL, NULL, 0);
269 		c = xchg(m, 2);
270 	}
271 
272 	return 0;
273 }
274 
275 static int mutex_unlock(unsigned long *m)
276 {
277 	if (*m == 2)
278 		*m = 0;
279 	else if (xchg(m, 0) == 1)
280 		return 0;
281 
282 	sys_futex(m, FUTEX_WAKE, 1, NULL, NULL, 0);
283 
284 	return 0;
285 }
286 
287 static unsigned long *m1, *m2;
288 
289 static void futex_setup(int cpu1, int cpu2)
290 {
291 	int shmid;
292 	void *shmaddr;
293 
294 	shmid = shmget(IPC_PRIVATE, getpagesize(), SHM_R | SHM_W);
295 	if (shmid < 0) {
296 		perror("shmget");
297 		exit(1);
298 	}
299 
300 	shmaddr = shmat(shmid, NULL, 0);
301 	if (shmaddr == (char *)-1) {
302 		perror("shmat");
303 		shmctl(shmid, IPC_RMID, NULL);
304 		exit(1);
305 	}
306 
307 	shmctl(shmid, IPC_RMID, NULL);
308 
309 	m1 = shmaddr;
310 	m2 = shmaddr + sizeof(*m1);
311 
312 	*m1 = 0;
313 	*m2 = 0;
314 
315 	mutex_lock(m1);
316 	mutex_lock(m2);
317 }
318 
319 static void *futex_thread1(void *arg)
320 {
321 	signal(SIGALRM, sigalrm_handler);
322 	alarm(1);
323 
324 	while (1) {
325 		mutex_lock(m2);
326 		mutex_unlock(m1);
327 
328 		iterations += 2;
329 	}
330 
331 	return NULL;
332 }
333 
334 static void *futex_thread2(void *arg)
335 {
336 	while (1) {
337 		mutex_unlock(m2);
338 		mutex_lock(m1);
339 	}
340 
341 	return NULL;
342 }
343 
344 static struct actions futex_actions = {
345 	.setup = futex_setup,
346 	.thread1 = futex_thread1,
347 	.thread2 = futex_thread2,
348 };
349 
350 static int processes;
351 
352 static struct option options[] = {
353 	{ "test", required_argument, 0, 't' },
354 	{ "process", no_argument, &processes, 1 },
355 	{ "timeout", required_argument, 0, 's' },
356 	{ "vdso", no_argument, &touch_vdso, 1 },
357 	{ "no-fp", no_argument, &touch_fp, 0 },
358 #ifdef __powerpc__
359 	{ "no-altivec", no_argument, &touch_altivec, 0 },
360 #endif
361 	{ "no-vector", no_argument, &touch_vector, 0 },
362 	{ 0, },
363 };
364 
365 static void usage(void)
366 {
367 	fprintf(stderr, "Usage: context_switch2 <options> CPU1 CPU2\n\n");
368 	fprintf(stderr, "\t\t--test=X\tpipe, futex or yield (default)\n");
369 	fprintf(stderr, "\t\t--process\tUse processes (default threads)\n");
370 	fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
371 	fprintf(stderr, "\t\t--vdso\t\ttouch VDSO\n");
372 	fprintf(stderr, "\t\t--fp\t\ttouch FP\n");
373 #ifdef __powerpc__
374 	fprintf(stderr, "\t\t--altivec\ttouch altivec\n");
375 #endif
376 	fprintf(stderr, "\t\t--vector\ttouch vector\n");
377 }
378 
379 int main(int argc, char *argv[])
380 {
381 	signed char c;
382 	struct actions *actions = &yield_actions;
383 	int cpu1;
384 	int cpu2;
385 	static void (*start_fn)(void *(*fn)(void *), void *arg, unsigned long cpu);
386 
387 	while (1) {
388 		int option_index = 0;
389 
390 		c = getopt_long(argc, argv, "", options, &option_index);
391 
392 		if (c == -1)
393 			break;
394 
395 		switch (c) {
396 		case 0:
397 			if (options[option_index].flag != 0)
398 				break;
399 
400 			usage();
401 			exit(1);
402 			break;
403 
404 		case 't':
405 			if (!strcmp(optarg, "pipe")) {
406 				actions = &pipe_actions;
407 			} else if (!strcmp(optarg, "yield")) {
408 				actions = &yield_actions;
409 			} else if (!strcmp(optarg, "futex")) {
410 				actions = &futex_actions;
411 			} else {
412 				usage();
413 				exit(1);
414 			}
415 			break;
416 
417 		case 's':
418 			timeout = atoi(optarg);
419 			break;
420 
421 		default:
422 			usage();
423 			exit(1);
424 		}
425 	}
426 
427 	if (processes)
428 		start_fn = start_process_on;
429 	else
430 		start_fn = start_thread_on;
431 
432 	if (((argc - optind) != 2)) {
433 		cpu1 = cpu2 = pick_online_cpu();
434 	} else {
435 		cpu1 = atoi(argv[optind++]);
436 		cpu2 = atoi(argv[optind++]);
437 	}
438 
439 	printf("Using %s with ", processes ? "processes" : "threads");
440 
441 	if (actions == &pipe_actions)
442 		printf("pipe");
443 	else if (actions == &yield_actions)
444 		printf("yield");
445 	else
446 		printf("futex");
447 
448 	printf(" on cpus %d/%d touching FP:%s altivec:%s vector:%s vdso:%s\n",
449 	       cpu1, cpu2, touch_fp ?  "yes" : "no", touch_altivec ? "yes" : "no",
450 	       touch_vector ? "yes" : "no", touch_vdso ? "yes" : "no");
451 
452 	/* Create a new process group so we can signal everyone for exit */
453 	setpgid(getpid(), getpid());
454 
455 	signal(SIGUSR1, sigusr1_handler);
456 
457 	actions->setup(cpu1, cpu2);
458 
459 	start_fn(actions->thread1, NULL, cpu1);
460 	start_fn(actions->thread2, NULL, cpu2);
461 
462 	while (1)
463 		sleep(3600);
464 
465 	return 0;
466 }
467