mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	 dd40c5b4c9
			
		
	
	
		dd40c5b4c9
		
	
	
	
	
		
			
			Signed-off-by: Nicholas Piggin <npiggin@gmail.com> [mpe: Add SPDX, and fixup formatting] Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
		
			
				
	
	
		
			326 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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;
 | |
| }
 |