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