|
@@ -0,0 +1,325 @@
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
+
|
|
|
+/*
|
|
|
+ * Context switch microbenchmark.
|
|
|
+ *
|
|
|
+ * Copyright 2018, Anton Blanchard, IBM Corp.
|
|
|
+ */
|
|
|
+
|
|
|
+#define _GNU_SOURCE
|
|
|
+#include <assert.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <getopt.h>
|
|
|
+#include <limits.h>
|
|
|
+#include <linux/futex.h>
|
|
|
+#include <pthread.h>
|
|
|
+#include <sched.h>
|
|
|
+#include <signal.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+#include <sys/shm.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/time.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/wait.h>
|
|
|
+#include <unistd.h>
|
|
|
+
|
|
|
+static unsigned int timeout = 30;
|
|
|
+
|
|
|
+static void set_cpu(int cpu)
|
|
|
+{
|
|
|
+ cpu_set_t cpuset;
|
|
|
+
|
|
|
+ if (cpu == -1)
|
|
|
+ return;
|
|
|
+
|
|
|
+ CPU_ZERO(&cpuset);
|
|
|
+ CPU_SET(cpu, &cpuset);
|
|
|
+
|
|
|
+ if (sched_setaffinity(0, sizeof(cpuset), &cpuset)) {
|
|
|
+ perror("sched_setaffinity");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void start_process_on(void *(*fn)(void *), void *arg, int cpu)
|
|
|
+{
|
|
|
+ int pid;
|
|
|
+
|
|
|
+ pid = fork();
|
|
|
+ if (pid == -1) {
|
|
|
+ perror("fork");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pid)
|
|
|
+ return;
|
|
|
+
|
|
|
+ set_cpu(cpu);
|
|
|
+
|
|
|
+ fn(arg);
|
|
|
+
|
|
|
+ exit(0);
|
|
|
+}
|
|
|
+
|
|
|
+static int cpu;
|
|
|
+static int do_fork = 0;
|
|
|
+static int do_vfork = 0;
|
|
|
+static int do_exec = 0;
|
|
|
+static char *exec_file;
|
|
|
+static int exec_target = 0;
|
|
|
+static unsigned long iterations;
|
|
|
+static unsigned long iterations_prev;
|
|
|
+
|
|
|
+static void run_exec(void)
|
|
|
+{
|
|
|
+ char *const argv[] = { "./exec_target", NULL };
|
|
|
+
|
|
|
+ if (execve("./exec_target", argv, NULL) == -1) {
|
|
|
+ perror("execve");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void bench_fork(void)
|
|
|
+{
|
|
|
+ while (1) {
|
|
|
+ pid_t pid = fork();
|
|
|
+ if (pid == -1) {
|
|
|
+ perror("fork");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ if (pid == 0) {
|
|
|
+ if (do_exec)
|
|
|
+ run_exec();
|
|
|
+ _exit(0);
|
|
|
+ }
|
|
|
+ pid = waitpid(pid, NULL, 0);
|
|
|
+ if (pid == -1) {
|
|
|
+ perror("waitpid");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ iterations++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void bench_vfork(void)
|
|
|
+{
|
|
|
+ while (1) {
|
|
|
+ pid_t pid = vfork();
|
|
|
+ if (pid == -1) {
|
|
|
+ perror("fork");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ if (pid == 0) {
|
|
|
+ if (do_exec)
|
|
|
+ run_exec();
|
|
|
+ _exit(0);
|
|
|
+ }
|
|
|
+ pid = waitpid(pid, NULL, 0);
|
|
|
+ if (pid == -1) {
|
|
|
+ perror("waitpid");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ iterations++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void *null_fn(void *arg)
|
|
|
+{
|
|
|
+ pthread_exit(NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static void bench_thread(void)
|
|
|
+{
|
|
|
+ pthread_t tid;
|
|
|
+ cpu_set_t cpuset;
|
|
|
+ pthread_attr_t attr;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ rc = pthread_attr_init(&attr);
|
|
|
+ if (rc) {
|
|
|
+ errno = rc;
|
|
|
+ perror("pthread_attr_init");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cpu != -1) {
|
|
|
+ CPU_ZERO(&cpuset);
|
|
|
+ CPU_SET(cpu, &cpuset);
|
|
|
+
|
|
|
+ rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
|
|
|
+ if (rc) {
|
|
|
+ errno = rc;
|
|
|
+ perror("pthread_attr_setaffinity_np");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ rc = pthread_create(&tid, &attr, null_fn, NULL);
|
|
|
+ if (rc) {
|
|
|
+ errno = rc;
|
|
|
+ perror("pthread_create");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ rc = pthread_join(tid, NULL);
|
|
|
+ if (rc) {
|
|
|
+ errno = rc;
|
|
|
+ perror("pthread_join");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ iterations++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void sigalrm_handler(int junk)
|
|
|
+{
|
|
|
+ unsigned long i = iterations;
|
|
|
+
|
|
|
+ printf("%ld\n", i - iterations_prev);
|
|
|
+ iterations_prev = i;
|
|
|
+
|
|
|
+ if (--timeout == 0)
|
|
|
+ kill(0, SIGUSR1);
|
|
|
+
|
|
|
+ alarm(1);
|
|
|
+}
|
|
|
+
|
|
|
+static void sigusr1_handler(int junk)
|
|
|
+{
|
|
|
+ exit(0);
|
|
|
+}
|
|
|
+
|
|
|
+static void *bench_proc(void *arg)
|
|
|
+{
|
|
|
+ signal(SIGALRM, sigalrm_handler);
|
|
|
+ alarm(1);
|
|
|
+
|
|
|
+ if (do_fork)
|
|
|
+ bench_fork();
|
|
|
+ else if (do_vfork)
|
|
|
+ bench_vfork();
|
|
|
+ else
|
|
|
+ bench_thread();
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static struct option options[] = {
|
|
|
+ { "fork", no_argument, &do_fork, 1 },
|
|
|
+ { "vfork", no_argument, &do_vfork, 1 },
|
|
|
+ { "exec", no_argument, &do_exec, 1 },
|
|
|
+ { "timeout", required_argument, 0, 's' },
|
|
|
+ { "exec-target", no_argument, &exec_target, 1 },
|
|
|
+ { NULL },
|
|
|
+};
|
|
|
+
|
|
|
+static void usage(void)
|
|
|
+{
|
|
|
+ fprintf(stderr, "Usage: fork <options> CPU\n\n");
|
|
|
+ fprintf(stderr, "\t\t--fork\tUse fork() (default threads)\n");
|
|
|
+ fprintf(stderr, "\t\t--vfork\tUse vfork() (default threads)\n");
|
|
|
+ fprintf(stderr, "\t\t--exec\tAlso exec() (default no exec)\n");
|
|
|
+ fprintf(stderr, "\t\t--timeout=X\tDuration in seconds to run (default 30)\n");
|
|
|
+ fprintf(stderr, "\t\t--exec-target\tInternal option for exec workload\n");
|
|
|
+}
|
|
|
+
|
|
|
+int main(int argc, char *argv[])
|
|
|
+{
|
|
|
+ signed char c;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ int option_index = 0;
|
|
|
+
|
|
|
+ c = getopt_long(argc, argv, "", options, &option_index);
|
|
|
+
|
|
|
+ if (c == -1)
|
|
|
+ break;
|
|
|
+
|
|
|
+ switch (c) {
|
|
|
+ case 0:
|
|
|
+ if (options[option_index].flag != 0)
|
|
|
+ break;
|
|
|
+
|
|
|
+ usage();
|
|
|
+ exit(1);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 's':
|
|
|
+ timeout = atoi(optarg);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ usage();
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (do_fork && do_vfork) {
|
|
|
+ usage();
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ if (do_exec && !do_fork && !do_vfork) {
|
|
|
+ usage();
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (do_exec) {
|
|
|
+ char *dirname = strdup(argv[0]);
|
|
|
+ int i;
|
|
|
+ i = strlen(dirname) - 1;
|
|
|
+ while (i) {
|
|
|
+ if (dirname[i] == '/') {
|
|
|
+ dirname[i] = '\0';
|
|
|
+ if (chdir(dirname) == -1) {
|
|
|
+ perror("chdir");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ i--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exec_target) {
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (((argc - optind) != 1)) {
|
|
|
+ cpu = -1;
|
|
|
+ } else {
|
|
|
+ cpu = atoi(argv[optind++]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (do_exec)
|
|
|
+ exec_file = argv[0];
|
|
|
+
|
|
|
+ set_cpu(cpu);
|
|
|
+
|
|
|
+ printf("Using ");
|
|
|
+ if (do_fork)
|
|
|
+ printf("fork");
|
|
|
+ else if (do_vfork)
|
|
|
+ printf("vfork");
|
|
|
+ else
|
|
|
+ printf("clone");
|
|
|
+
|
|
|
+ if (do_exec)
|
|
|
+ printf(" + exec");
|
|
|
+
|
|
|
+ printf(" on cpu %d\n", cpu);
|
|
|
+
|
|
|
+ /* Create a new process group so we can signal everyone for exit */
|
|
|
+ setpgid(getpid(), getpid());
|
|
|
+
|
|
|
+ signal(SIGUSR1, sigusr1_handler);
|
|
|
+
|
|
|
+ start_process_on(bench_proc, NULL, cpu);
|
|
|
+
|
|
|
+ while (1)
|
|
|
+ sleep(3600);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|