use Getopt::Long qw(:config bundling);
# Command-line options.
+our ($start_time) = time ();
our ($sim); # Simulator: bochs, qemu, or gsx.
our ($debug) = "none"; # Debugger: none, monitor, or gdb.
our ($mem) = 4; # Physical RAM in MB.
Timing options: (Bochs only)
-j SEED Randomize timer interrupts
-r, --realtime Use realistic, not reproducible, timings
- -T, --timeout=N Time out and kill Pintos after N seconds
+ -T, --timeout=N Kill Pintos after N seconds CPU time or N*load_avg
+ seconds wall-clock time (whichever comes first)
Configuration options:
-m, --mem=N Give Pintos N MB physical RAM (default: 4)
File system commands (for `run' command):
die "fork: $!\n";
} elsif (!$pid) {
# Running in child process.
- exec (@_);
- exit (1);
+ exec_setitimer (@_);
} else {
# Running in parent process.
local $SIG{ALRM} = sub { timeout ($pid); };
local $SIG{INT} = sub { relay_signal ($pid, "INT"); };
local $SIG{TERM} = sub { relay_signal ($pid, "TERM"); };
- alarm ($timeout) if defined ($timeout);
+ alarm ($timeout * get_load_average () + 1) if defined ($timeout);
waitpid ($pid, 0);
alarm (0);
+
+ if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) {
+ seek (STDOUT, 0, 2);
+ print "\nTIMEOUT after $timeout seconds of host CPU time\n";
+ exit 0;
+ }
+
return $?;
}
}
waitpid ($pid, 0);
seek (STDOUT, 0, 2);
my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
- print "\nTIMEOUT after $timeout seconds";
+ print "\nTIMEOUT after ", time () - $start_time,
+ " seconds of wall-clock time";
print " - $load_avg" if defined $load_avg;
print "\n";
exit 0;
}
+
+# Returns the system load average over the last minute.
+# If the load average is less than 1.0 or cannot be determined, returns 1.0.
+sub get_load_average {
+ my ($avg) = `uptime` =~ /load average:\s*([^,]+),/;
+ return $avg >= 1.0 ? $avg : 1.0;
+}
+
+# Calls setitimer to set a timeout, then execs what was passed to us.
+sub exec_setitimer {
+ if (defined $timeout) {
+ if ($\16 ge 5.8.0) {
+ eval "
+ use Time::HiRes qw(setitimer ITIMER_VIRTUAL);
+ setitimer (ITIMER_VIRTUAL, $timeout, 0);
+ ";
+ } else {
+ { exec ("setitimer-helper", $timeout, @_); };
+ exit 1 if !$!{ENOENT};
+ print STDERR "warning: setitimer-helper is not installed, so ",
+ "CPU time limit will not be enforced\n";
+ }
+ }
+ exec (@_);
+ exit (1);
+}
+
+sub SIGVTALRM {
+ use Config;
+ my $i = 0;
+ foreach my $name (split(' ', $Config{sig_name})) {
+ return $i if $name eq 'VTALRM';
+ $i++;
+ }
+ return 0;
+}
--- /dev/null
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int
+main (int argc, char *argv[])
+{
+ const char *program_name = argv[0];
+ double timeout;
+
+ if (argc < 3)
+ {
+ fprintf (stderr,
+ "setitimer-helper: runs a program with a virtual CPU limit\n"
+ "usage: %s TIMEOUT PROGRAM [ARG...]\n"
+ " where TIMEOUT is the virtual CPU limit, in seconds,\n"
+ " and remaining arguments specify the program to run\n"
+ " and its argument.\n",
+ program_name);
+ return EXIT_FAILURE;
+ }
+
+ timeout = strtod (argv[1], NULL);
+ if (timeout >= 0.0 && timeout < LONG_MAX)
+ {
+ struct itimerval it;
+
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = timeout;
+ it.it_value.tv_usec = (timeout - floor (timeout)) * 1000000;
+ if (setitimer (ITIMER_VIRTUAL, &it, NULL) < 0)
+ fprintf (stderr, "%s: setitimer: %s\n",
+ program_name, strerror (errno));
+ }
+ else
+ fprintf (stderr, "%s: invalid timeout value \"%s\"\n",
+ program_name, argv[1]);
+
+ execvp (argv[2], &argv[2]);
+ fprintf (stderr, "%s: couldn't exec \"%s\": %s\n",
+ program_name, argv[2], strerror (errno));
+ return EXIT_FAILURE;
+}