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