/* * memtoy.c -- toy/tool for investigating Linux [Numa] VM behavior */ /* * Copyright (c) 2005 Hewlett-Packard, Inc * All rights reserved. */ /* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> #include "config.h" #include "tst_res_flags.h" #if HAVE_NUMA_H #include <numa.h> #endif #ifdef HAVE_NUMA_V2 #include <sys/types.h> #include <sys/time.h> #include <sys/mman.h> #include <libgen.h> #include <errno.h> #include <numa.h> #include <signal.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "memtoy.h" /* * global context */ glctx_t glctx; /* global context */ /* * command line options: * * -v = verbose * -V = display version * -h|x = display help. */ #define OPTIONS "Vhvx" /* * usage/help message */ char *USAGE = "\nUsage: %s [-v] [-V] [-{h|x}]\n\n\ Where:\n\ \t-v enable verbosity\n\ \t-V display version info\n\ \t-h|x show this usage/help message\n\ \n\ More info - TODO\n\ "; /* * die() - emit error message and exit w/ specified return code. * if exit_code < 0, save current errno, and fetch associated * error string. Print error string after app error message. * Then exit with abs(exit_code). */ void die(int exit_code, char *format, ...) { va_list ap; char *errstr; int saverrno; va_start(ap, format); if (exit_code < 0) { saverrno = errno; errstr = strerror(errno); } (void)vfprintf(stderr, format, ap); va_end(ap); if (exit_code < 0) fprintf(stderr, "Error = (%d) %s\n", saverrno, errstr); exit(abs(exit_code)); } void usage(char *mesg) { if (mesg != NULL) { fprintf(stderr, "%s\n", mesg); } fprintf(stderr, USAGE, glctx.program_name); exit(1); } #ifdef _DEBUG /* * This function is a wrapper around "fprintf(stderr, ...)" so that we * can use the DPRINTF(<flag>, (<[f]printf arguments>)) macro for debug * prints. See the definition of DPRINTF in XXX.h */ int _dvprintf(char *format, ...) { va_list ap; int retval; va_start(ap, format); retval = vfprintf(stderr, format, ap); va_end(ap); fflush(stderr); return (retval); } #endif void vprint(char *format, ...) { va_list ap; glctx_t *gcp = &glctx; va_start(ap, format); if (!is_option(VERBOSE)) goto out; (void)vfprintf(stderr, format, ap); fflush(stderr); out: va_end(ap); return; } /* * ========================================================================= */ static int signals_to_handle[] = { SIGINT, SIGQUIT, SIGSEGV, SIGBUS, SIGUSR1, SIGUSR2, 0 }; static char *sig_names[] = { "SIGINT", "SIGQUIT", "SIGSEGV", "SIGBUS", "SIGUSR1", "SIGUSR2", "unknown", 0 }; /* * signal_handler() * * save siginfo and name in global context */ void signal_handler(int sig, siginfo_t * info, void *vcontext) { glctx_t *gcp = &glctx; int isig = 0, *sigp = signals_to_handle; static siginfo_t infocopy; /* * static copy of signal info. * Note, additional signals, before use, can overwrite */ infocopy = *info; gcp->siginfo = &infocopy; while (*sigp) { if (*sigp == sig) break; ++isig; ++sigp; } gcp->signame = sig_names[isig]; vprint("signal hander entered for sig %s\n", gcp->signame); switch (sig) { case SIGSEGV: case SIGBUS: if (gcp->sigjmp) { gcp->sigjmp = false; siglongjmp(gcp->sigjmp_env, 1); } die(8, "\n%s: signal %s, but siglongjmp not armed\n", gcp->program_name, gcp->signame); break; case SIGINT: case SIGQUIT: break; default: die(8, "\n%s: Unexpected signal: %d\n", gcp->program_name, sig); break; } } /* * set_signals() * * Setup signal dispositions to catch selected signals */ void set_signals() { glctx_t *gcp = &glctx; int *sigp = signals_to_handle; char **namep = sig_names; struct sigaction act = { .sa_sigaction = signal_handler, .sa_flags = SA_SIGINFO }; (void)sigfillset(&(act.sa_mask)); while (*sigp) { char *sig_name = *(namep++); int sig = *(sigp++); if (0 != sigaction(sig, &act, NULL)) { die(-1, "%s: Failed to set sigaction for %s\n", gcp->program_name, sig_name); } else #if 0 vprint("%s: established handler for %s\n", gcp->program_name, sig_name) #endif ; } return; } void reset_signal(void) { //TODO: free siginfo if/when malloc'd glctx.siginfo = NULL; glctx.sigjmp = false; } void wait_for_signal(const char *mesg) { printf("%s ... ", mesg); fflush(stdout); pause(); vprint("%s: wakened by signal %s\n", __FUNCTION__, glctx.signame); reset_signal(); printf("\n"); fflush(stdout); } void show_siginfo() { glctx_t *gcp = &glctx; siginfo_t *info = gcp->siginfo; void *badaddr = info->si_addr; char *sigcode; switch (info->si_signo) { case SIGSEGV: switch (info->si_code) { case SEGV_MAPERR: sigcode = "address not mapped"; break; case SEGV_ACCERR: sigcode = "invalid access error"; break; default: sigcode = "unknown"; break; } break; case SIGBUS: switch (info->si_code) { case BUS_ADRALN: sigcode = "invalid address alignment"; break; case BUS_ADRERR: sigcode = "non-existent physical address"; break; default: sigcode = "unknown"; break; } break; default: /* * ignore SIGINT/SIGQUIT */ return; } printf("Signal %s @ 0x%lx - %s\n", gcp->signame, badaddr, sigcode); } /* * ========================================================================= */ void touch_memory(bool rw, unsigned long *memp, size_t memlen) { glctx_t *gcp = &glctx; unsigned long *memend, *pp, sink; unsigned long longs_in_page = gcp->pagesize / sizeof(unsigned long); memend = memp + memlen / sizeof(unsigned long); vprint("!!!%s from 0x%lx thru 0x%lx\n", rw ? "Writing" : "Reading", memp, memend); for (pp = memp; pp < memend; pp += longs_in_page) { // vprint("%s: touching 0x%lx\n", __FUNCTION__, pp); if (!sigsetjmp(gcp->sigjmp_env, true)) { gcp->sigjmp = true; /* * Mah-ahm! He's touching me! */ if (rw) *pp = (unsigned long)pp; else sink = *pp; gcp->sigjmp = false; } else { show_siginfo(); reset_signal(); break; } /* * Any [handled] signal breaks the loop */ if (gcp->siginfo != NULL) { reset_signal(); break; } } } /* * ========================================================================= */ void init_glctx(glctx_t * gcp) { bzero(gcp, sizeof(glctx_t)); gcp->pagesize = (size_t) sysconf(_SC_PAGESIZE); if (numa_available() >= 0) { gcp->numa_max_node = numa_max_node(); } else gcp->numa_max_node = -1; segment_init(gcp); if (isatty(fileno(stdin))) set_option(INTERACTIVE); } /* * cleanup() - at exit cleanup routine */ static void cleanup() { glctx_t *gcp = &glctx; segment_cleanup(gcp); } /* cleanup() */ int parse_command_line_args(int argc, char *argv[]) { extern int optind; extern char *optarg; glctx_t *gcp = &glctx; int argval; int error = 0; char c; gcp->program_name = basename(argv[0]); /* * process command line options. */ while ((c = getopt(argc, argv, OPTIONS)) != (char)EOF) { char *next; switch (c) { case 'v': set_option(VERBOSE); break; case 'h': case 'x': usage(NULL); break; case 'V': printf("memtoy " MEMTOY_VERSION " built " __DATE__ " @ " __TIME__ "\n"); exit(0); break; #ifdef _DEBUG case '0': argval = strtoul(optarg, &next, 0); if (*next != '\0') { fprintf(stderr, "-D <debug-mask> must be unsigned hex/decimal integer\n"); ++error; } else gcp->debug = argval; break; #endif default: error = 1; break; } } done: return (error); } int main(int argc, char *argv[]) { glctx_t *gcp = &glctx; bool user_is_super; int error; init_glctx(gcp); if (!is_option(INTERACTIVE)) setbuf(stdout, NULL); /* * Register cleanup handler */ if (atexit(cleanup) != 0) { die(-1, "%s: atexit(cleanup) registration failed\n", argv[0]); } user_is_super = (geteuid() == 0); error = parse_command_line_args(argc, argv); if (error /* || argc==1 */ ) { usage(NULL); } /* * actual program logic starts here */ printf("memtoy pid: %d\n", getpid()); vprint("%s: pagesize = %d\n", gcp->program_name, gcp->pagesize); if (gcp->numa_max_node >= 0) vprint("%s: NUMA available - max node: %d\n", gcp->program_name, gcp->numa_max_node); set_signals(); process_commands(); return 0; } #else int main(void) { fprintf(stderr, "test requires libnuma >= 2 and it's development packages\n"); return TCONF; } #endif