| /* |
| * 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 |