You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
14 KiB
521 lines
14 KiB
7 months ago
|
/* time-schedule.c
|
||
|
|
||
|
Programme to test how long a context switch takes.
|
||
|
|
||
|
Copyright (C) 1998 Richard Gooch
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 2 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
|
|
||
|
Richard Gooch may be reached by email at rgooch@atnf.csiro.au
|
||
|
The postal address is:
|
||
|
Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
This programme will determine the context switch (scheduling) overhead on
|
||
|
a system. It takes into account SMP machines. True context switches are
|
||
|
measured.
|
||
|
|
||
|
Written by Richard Gooch 15-SEP-1998
|
||
|
|
||
|
Last updated by Richard Gooch 25-SEP-1998
|
||
|
|
||
|
*/
|
||
|
#include <unistd.h>
|
||
|
#ifdef _POSIX_THREADS
|
||
|
#ifndef _REENTRANT
|
||
|
#define _REENTRANT
|
||
|
#endif
|
||
|
#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
|
||
|
#define _POSIX_THREAD_SAFE_FUNCTIONS
|
||
|
#endif
|
||
|
#include <pthread.h>
|
||
|
#endif
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <math.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
#include <signal.h>
|
||
|
#include <sched.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <dirent.h>
|
||
|
#include <errno.h>
|
||
|
|
||
|
#ifndef __KARMA__
|
||
|
#define mt_num_processors() 1 /* Set to the number of processors */
|
||
|
#define ERRSTRING strerror(errno)
|
||
|
#define FALSE 0
|
||
|
#define TRUE 1
|
||
|
#else
|
||
|
#include <karma.h>
|
||
|
#include <karma_mt.h>
|
||
|
#endif
|
||
|
|
||
|
#define MAX_ITERATIONS 1000
|
||
|
|
||
|
static unsigned int hog_other_cpus();
|
||
|
static void run_yielder(int use_threads, int read_fd);
|
||
|
static void *yielder_main(void *arg);
|
||
|
static void s_term_handler();
|
||
|
static void run_low_priority(unsigned int num, int read_fd);
|
||
|
static unsigned long compute_median(unsigned long values[MAX_ITERATIONS],
|
||
|
unsigned long max_value);
|
||
|
static unsigned int get_run_queue_size();
|
||
|
static unsigned long get_num_switches();
|
||
|
static void use_fpu_value(double val);
|
||
|
|
||
|
static volatile unsigned int sched_count = 0;
|
||
|
/* For yielder */
|
||
|
static int pipe_read_fd = -1;
|
||
|
static int pipe_write_fd = -1;
|
||
|
static pid_t child = -1;
|
||
|
|
||
|
int main(int argc, char **argv)
|
||
|
{
|
||
|
int use_threads = FALSE;
|
||
|
int use_pipe = FALSE;
|
||
|
int no_test = FALSE;
|
||
|
int frob_fpu = FALSE;
|
||
|
int read_fd = -1;
|
||
|
int write_fd = -1;
|
||
|
int num_low_priority = -1;
|
||
|
int i, j;
|
||
|
int fds[2];
|
||
|
unsigned int count, num_yields, run_queue_size1, run_queue_size2,
|
||
|
num_hogs;
|
||
|
unsigned long median, switches1, num_switches, num_overhead_switches;
|
||
|
signed long overhead, total_diffs;
|
||
|
signed long min_diff = 1000000000;
|
||
|
signed long max_diff = -1000000000;
|
||
|
double dcount = 0.0;
|
||
|
unsigned long diffs[MAX_ITERATIONS];
|
||
|
struct timeval before, after;
|
||
|
sigset_t set;
|
||
|
static char *usage =
|
||
|
"time-schedule [-h] [-thread] [-notest] [-pipe] [-fpu] [num_running]";
|
||
|
|
||
|
setpgrp();
|
||
|
/* First create pipe used to sychronise low priority processes */
|
||
|
if (pipe(fds) != 0) {
|
||
|
fprintf(stderr, "Error creating pipe\t%s\n", ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
read_fd = fds[0];
|
||
|
pipe_write_fd = fds[1];
|
||
|
for (count = 1; count < argc; ++count) {
|
||
|
if (strcmp(argv[count], "-thread") == 0) {
|
||
|
#ifdef _POSIX_THREADS
|
||
|
use_threads = TRUE;
|
||
|
#else
|
||
|
fprintf(stderr, "POSIX threads not available\n");
|
||
|
#endif
|
||
|
} else if (strcmp(argv[count], "-pipe") == 0)
|
||
|
use_pipe = TRUE;
|
||
|
else if (strcmp(argv[count], "-notest") == 0)
|
||
|
no_test = TRUE;
|
||
|
else if (strcmp(argv[count], "-fpu") == 0)
|
||
|
frob_fpu = TRUE;
|
||
|
else if (isdigit(argv[count][0]))
|
||
|
num_low_priority = atoi(argv[count]);
|
||
|
else {
|
||
|
fprintf(stderr,
|
||
|
"Programme to time context switches (schedules)\n");
|
||
|
fprintf(stderr,
|
||
|
"(C) 1998 Richard Gooch <rgooch@atnf.csiro.au>\n");
|
||
|
fprintf(stderr, "Usage:\t%s\n", usage);
|
||
|
fprintf(stderr,
|
||
|
"\t-thread\t\tswitch threads not processes\n");
|
||
|
fprintf(stderr,
|
||
|
"\t-pipe\t\tuse pipes not sched_yield()\n");
|
||
|
fprintf(stderr,
|
||
|
"\t-fpu\t\tpollute the FPU after each switch in main\n");
|
||
|
fprintf(stderr,
|
||
|
"\tnum_running\tnumber of extra processes\n");
|
||
|
exit(0);
|
||
|
}
|
||
|
}
|
||
|
if (no_test) {
|
||
|
if (num_low_priority > 0)
|
||
|
run_low_priority(num_low_priority, read_fd);
|
||
|
while (TRUE)
|
||
|
pause();
|
||
|
}
|
||
|
if (geteuid() == 0) {
|
||
|
struct sched_param sp;
|
||
|
|
||
|
memset(&sp, 0, sizeof sp);
|
||
|
sp.sched_priority = 10;
|
||
|
if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
|
||
|
fprintf(stderr, "Error changing to RT class\t%s\n",
|
||
|
ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
|
||
|
fprintf(stderr, "Error locking pages\t%s\n", ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
} else
|
||
|
fprintf(stderr, "Not running with RT priority!\n");
|
||
|
/* Give shell and login programme time to finish up and get off the run
|
||
|
queue */
|
||
|
usleep(200000);
|
||
|
if (use_pipe) {
|
||
|
if (pipe(fds) != 0) {
|
||
|
fprintf(stderr, "Error creating pipe\t%s\n", ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
pipe_read_fd = fds[0];
|
||
|
write_fd = fds[1];
|
||
|
}
|
||
|
num_hogs = hog_other_cpus();
|
||
|
/* Determine overhead. Do it in a loop=2. The first iteration should warm
|
||
|
the cache, the second will compute the overhead */
|
||
|
for (j = 0; j < 2; ++j) {
|
||
|
switches1 = get_num_switches();
|
||
|
gettimeofday(&before, NULL);
|
||
|
for (i = 0; i < 20; ++i) {
|
||
|
if (use_pipe) {
|
||
|
char ch = 0;
|
||
|
|
||
|
write(pipe_write_fd, &ch, 1);
|
||
|
read(read_fd, &ch, 1);
|
||
|
} else
|
||
|
sched_yield();
|
||
|
if (frob_fpu)
|
||
|
++dcount;
|
||
|
}
|
||
|
gettimeofday(&after, NULL);
|
||
|
num_overhead_switches = get_num_switches() - switches1;
|
||
|
overhead = 1000000 * (after.tv_sec - before.tv_sec);
|
||
|
overhead += after.tv_usec - before.tv_usec;
|
||
|
}
|
||
|
use_fpu_value(dcount);
|
||
|
if (num_low_priority > 0)
|
||
|
run_low_priority(num_low_priority, read_fd);
|
||
|
/* Set up for the benchmark */
|
||
|
run_yielder(use_threads, read_fd);
|
||
|
memset(diffs, 0, sizeof diffs);
|
||
|
run_queue_size1 = get_run_queue_size();
|
||
|
total_diffs = 0;
|
||
|
switches1 = get_num_switches();
|
||
|
/* Benchmark! */
|
||
|
for (count = 0; count < MAX_ITERATIONS; ++count) {
|
||
|
signed long diff;
|
||
|
|
||
|
gettimeofday(&before, NULL);
|
||
|
/* Generate 20 context switches */
|
||
|
for (i = 0; i < 10; ++i) {
|
||
|
if (use_pipe) {
|
||
|
char ch = 0;
|
||
|
|
||
|
write(write_fd, &ch, 1);
|
||
|
read(read_fd, &ch, 1);
|
||
|
} else
|
||
|
sched_yield();
|
||
|
if (frob_fpu)
|
||
|
dcount += 1.0;
|
||
|
}
|
||
|
gettimeofday(&after, NULL);
|
||
|
diff = 1000000 * (after.tv_sec - before.tv_sec);
|
||
|
diff += after.tv_usec - before.tv_usec;
|
||
|
diffs[count] = diff;
|
||
|
total_diffs += diff;
|
||
|
if (diff < min_diff)
|
||
|
min_diff = diff;
|
||
|
if (diff > max_diff)
|
||
|
max_diff = diff;
|
||
|
}
|
||
|
num_yields = sched_count;
|
||
|
run_queue_size2 = get_run_queue_size();
|
||
|
num_switches = get_num_switches() - switches1;
|
||
|
if (!use_threads)
|
||
|
kill(child, SIGTERM);
|
||
|
fprintf(stderr, "Started %u hog processes\n", num_hogs);
|
||
|
fprintf(stderr, "Syscall%s overhead: %.1f us\n", frob_fpu ? "/FPU" : "",
|
||
|
(double)overhead / 20.0);
|
||
|
if (switches1 > 0)
|
||
|
fprintf(stderr, "Num switches during overhead check: %lu\n",
|
||
|
num_overhead_switches);
|
||
|
fprintf(stderr, "Minimum scheduling latency: %.1f (%.1f) us\n",
|
||
|
(double)min_diff / 20.0, (double)(min_diff - overhead) / 20.0);
|
||
|
median = compute_median(diffs, max_diff);
|
||
|
fprintf(stderr, "Median scheduling latency: %.1f (%.1f) us\n",
|
||
|
(double)median / 20.0, (double)(median - overhead) / 20.0);
|
||
|
fprintf(stderr, "Average scheduling latency: %.1f (%.1f) us\n",
|
||
|
(double)total_diffs / (double)MAX_ITERATIONS / 20.0,
|
||
|
(double)(total_diffs - overhead * MAX_ITERATIONS) /
|
||
|
(double)MAX_ITERATIONS / 20.0);
|
||
|
fprintf(stderr, "Maximum scheduling latency: %.1f (%.1f) us\n",
|
||
|
(double)max_diff / 20.0, (double)(max_diff - overhead) / 20.0);
|
||
|
fprintf(stderr, "Run queue size: %u, %u\n",
|
||
|
run_queue_size1, run_queue_size2);
|
||
|
use_fpu_value(dcount);
|
||
|
if (use_threads)
|
||
|
fprintf(stderr, "Number of yields: %u\n", num_yields);
|
||
|
if (num_switches > 0)
|
||
|
fprintf(stderr, "Num switches: %lu\n", num_switches);
|
||
|
|
||
|
/* Terminate all child processes */
|
||
|
sigemptyset(&set);
|
||
|
sigaddset(&set, SIGTERM);
|
||
|
sigprocmask(SIG_BLOCK, &set, NULL);
|
||
|
|
||
|
kill(0, SIGTERM);
|
||
|
return (0);
|
||
|
} /* End Function main */
|
||
|
|
||
|
static unsigned int hog_other_cpus()
|
||
|
/* [SUMMARY] Hog other CPUs with a high-priority job.
|
||
|
[RETURNS] The number of hogged CPUs.
|
||
|
*/
|
||
|
{
|
||
|
unsigned int count;
|
||
|
|
||
|
for (count = mt_num_processors(); count > 1; --count) {
|
||
|
switch (fork()) {
|
||
|
case 0:
|
||
|
/* Child */
|
||
|
while (TRUE) ;
|
||
|
break;
|
||
|
case -1:
|
||
|
/* Error */
|
||
|
fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
|
||
|
kill(0, SIGTERM);
|
||
|
break;
|
||
|
default:
|
||
|
/* Parent */
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return mt_num_processors() - 1;
|
||
|
} /* End Function hog_other_cpus */
|
||
|
|
||
|
static void run_yielder(int use_threads, int read_fd)
|
||
|
/* [SUMMARY] Run other process which will continuously yield.
|
||
|
<use_threads> If TRUE, the yielding process is just a thread.
|
||
|
<read_fd> The pipe to read the synchronisation byte from.
|
||
|
[RETURNS] Nothing.
|
||
|
*/
|
||
|
{
|
||
|
char ch;
|
||
|
struct sigaction new_action;
|
||
|
#ifdef _POSIX_THREADS
|
||
|
pthread_t thread;
|
||
|
|
||
|
if (use_threads) {
|
||
|
if (pthread_create(&thread, NULL, yielder_main, NULL) != 0) {
|
||
|
fprintf(stderr, "Error creating thread\t%s\n",
|
||
|
ERRSTRING);
|
||
|
kill(0, SIGTERM);
|
||
|
}
|
||
|
read(read_fd, &ch, 1);
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
switch (child = fork()) {
|
||
|
case 0:
|
||
|
/* Child */
|
||
|
break;
|
||
|
case -1:
|
||
|
/* Error */
|
||
|
fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
|
||
|
kill(0, SIGTERM);
|
||
|
break;
|
||
|
default:
|
||
|
/* Parent */
|
||
|
read(read_fd, &ch, 1);
|
||
|
return;
|
||
|
/*break; */
|
||
|
}
|
||
|
memset(&new_action, 0, sizeof new_action);
|
||
|
sigemptyset(&new_action.sa_mask);
|
||
|
new_action.sa_handler = s_term_handler;
|
||
|
if (sigaction(SIGTERM, &new_action, NULL) != 0) {
|
||
|
fprintf(stderr, "Error setting SIGTERM handler\t%s\n",
|
||
|
ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
yielder_main(NULL);
|
||
|
} /* End Function run_yielder */
|
||
|
|
||
|
static void *yielder_main(void *arg)
|
||
|
/* [SUMMARY] Yielder function.
|
||
|
<arg> An arbirtrary pointer. Ignored.
|
||
|
[RETURNS] NULL.
|
||
|
*/
|
||
|
{
|
||
|
char ch = 0;
|
||
|
|
||
|
sched_count = 0;
|
||
|
write(pipe_write_fd, &ch, 1);
|
||
|
while (TRUE) {
|
||
|
if (pipe_read_fd >= 0) {
|
||
|
read(pipe_read_fd, &ch, 1);
|
||
|
write(pipe_write_fd, &ch, 1);
|
||
|
} else
|
||
|
sched_yield();
|
||
|
++sched_count;
|
||
|
}
|
||
|
return (NULL);
|
||
|
} /* End Function yielder_main */
|
||
|
|
||
|
static void s_term_handler()
|
||
|
{
|
||
|
fprintf(stderr, "Number of yields: %u\n", sched_count);
|
||
|
exit(0);
|
||
|
} /* End Function s_term_handler */
|
||
|
|
||
|
static void run_low_priority(unsigned int num, int read_fd)
|
||
|
/* [SUMMARY] Run low priority processes.
|
||
|
<num> Number of processes.
|
||
|
<read_fd> The pipe to read the synchronisation byte from.
|
||
|
[RETURNS] Nothing.
|
||
|
*/
|
||
|
{
|
||
|
char ch = 0;
|
||
|
|
||
|
for (; num > 0; --num) {
|
||
|
switch (fork()) {
|
||
|
case 0:
|
||
|
/* Child */
|
||
|
if (geteuid() == 0) {
|
||
|
struct sched_param sp;
|
||
|
|
||
|
memset(&sp, 0, sizeof sp);
|
||
|
sp.sched_priority = 0;
|
||
|
if (sched_setscheduler(0, SCHED_OTHER, &sp) !=
|
||
|
0) {
|
||
|
fprintf(stderr,
|
||
|
"Error changing to SCHED_OTHER class\t%s\n",
|
||
|
ERRSTRING);
|
||
|
exit(1);
|
||
|
}
|
||
|
}
|
||
|
if (nice(20) != 0) {
|
||
|
fprintf(stderr, "Error nicing\t%s\n",
|
||
|
ERRSTRING);
|
||
|
kill(0, SIGTERM);
|
||
|
}
|
||
|
write(pipe_write_fd, &ch, 1);
|
||
|
while (TRUE)
|
||
|
sched_yield();
|
||
|
break;
|
||
|
case -1:
|
||
|
/* Error */
|
||
|
fprintf(stderr, "Error forking\t%s\n", ERRSTRING);
|
||
|
kill(0, SIGTERM);
|
||
|
break;
|
||
|
default:
|
||
|
/* Parent */
|
||
|
read(read_fd, &ch, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} /* End Function run_low_priority */
|
||
|
|
||
|
static unsigned long compute_median(unsigned long values[MAX_ITERATIONS],
|
||
|
unsigned long max_value)
|
||
|
/* [SUMMARY] Compute the median from an array of values.
|
||
|
<values> The array of values.
|
||
|
<max_value> The maximum value in the array.
|
||
|
[RETURNS] The median value.
|
||
|
*/
|
||
|
{
|
||
|
unsigned long count;
|
||
|
unsigned long median = 0;
|
||
|
unsigned long peak = 0;
|
||
|
unsigned long *table;
|
||
|
|
||
|
/* Crude but effective */
|
||
|
if ((table = calloc(max_value + 1, sizeof *table)) == NULL) {
|
||
|
fprintf(stderr, "Error allocating median table\n");
|
||
|
exit(1);
|
||
|
}
|
||
|
for (count = 0; count < MAX_ITERATIONS; ++count) {
|
||
|
++table[values[count]];
|
||
|
}
|
||
|
/* Now search for peak. Position of peak is median */
|
||
|
for (count = 0; count < max_value + 1; ++count) {
|
||
|
if (table[count] < peak)
|
||
|
continue;
|
||
|
peak = table[count];
|
||
|
median = count;
|
||
|
}
|
||
|
free(table);
|
||
|
return (median);
|
||
|
} /* End Function compute_median */
|
||
|
|
||
|
static unsigned int get_run_queue_size()
|
||
|
/* [SUMMARY] Compute the current size of the run queue.
|
||
|
[RETURNS] The length of the run queue.
|
||
|
*/
|
||
|
{
|
||
|
int dummy_i;
|
||
|
unsigned int length = 0;
|
||
|
FILE *fp;
|
||
|
DIR *dp;
|
||
|
struct dirent *de;
|
||
|
char txt[64], dummy_str[64];
|
||
|
|
||
|
if ((dp = opendir("/proc")) == NULL)
|
||
|
return (0);
|
||
|
while ((de = readdir(dp)) != NULL) {
|
||
|
if (!isdigit(de->d_name[0]))
|
||
|
continue;
|
||
|
sprintf(txt, "/proc/%s/stat", de->d_name);
|
||
|
if ((fp = fopen(txt, "r")) == NULL)
|
||
|
return (length);
|
||
|
fscanf(fp, "%d %s %s", &dummy_i, dummy_str, txt);
|
||
|
if (txt[0] == 'R')
|
||
|
++length;
|
||
|
fclose(fp);
|
||
|
}
|
||
|
closedir(dp);
|
||
|
return (length);
|
||
|
} /* End Function get_run_queue_size */
|
||
|
|
||
|
static unsigned long get_num_switches()
|
||
|
/* [SUMMARY] Get the number of context switches.
|
||
|
[RETURNS] The number of context switches on success, else 0.
|
||
|
*/
|
||
|
{
|
||
|
unsigned long val;
|
||
|
FILE *fp;
|
||
|
char line[256], name[64];
|
||
|
|
||
|
if ((fp = fopen("/proc/stat", "r")) == NULL)
|
||
|
return (0);
|
||
|
while (fgets(line, sizeof line, fp) != NULL) {
|
||
|
sscanf(line, "%s %lu", name, &val);
|
||
|
if (strcasecmp(name, "ctxt") != 0)
|
||
|
continue;
|
||
|
fclose(fp);
|
||
|
return (val);
|
||
|
}
|
||
|
fclose(fp);
|
||
|
return (0);
|
||
|
} /* End Function get_num_switches */
|
||
|
|
||
|
static void use_fpu_value(double val)
|
||
|
/* [SUMMARY] Dummy function to consume FPU value. Fools compiler.
|
||
|
<val> The value.
|
||
|
[RETURNS] Nothing.
|
||
|
*/
|
||
|
{
|
||
|
} /* End Function use_fpu_value */
|