/* * memtoy: commands.c - command line interface * * A brute force/ad hoc command interpreter: * + parse commands [interactive or batch] * + convert/validate arguments * + some general/administrative commands herein * + actual segment management routines in segment.c */ /* * 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 "config.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 <ctype.h> #include <errno.h> #include <numa.h> #include <numaif.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> #include "memtoy.h" #include "test.h" #define CMD_SUCCESS 0 #define CMD_ERROR 1 #ifndef __NR_migrate_pages #define __NR_migrate_pages 0 #endif #ifndef MPOL_MF_WAIT #define MPOL_MF_WAIT (1<<2) /* Wait for existing pages to migrate */ #endif static inline int nodemask_isset(nodemask_t * mask, int node) { if ((unsigned)node >= NUMA_NUM_NODES) return 0; if (mask->n[node / (8 * sizeof(unsigned long))] & (1UL << (node % (8 * sizeof(unsigned long))))) return 1; return 0; } static inline void nodemask_set(nodemask_t * mask, int node) { mask->n[node / (8 * sizeof(unsigned long))] |= (1UL << (node % (8 * sizeof(unsigned long)))); } static char *whitespace = " \t"; /* * ========================================================================= */ static int help_me(char *); /* forward reference */ /* * required_arg -- check for a required argument; issue message if not there * * return true if arg [something] exists; else return false */ static bool required_arg(char *arg, char *arg_name) { glctx_t *gcp = &glctx; if (*arg != '\0') return true; fprintf(stderr, "%s: command '%s' missing required argument: %s\n\n", gcp->program_name, gcp->cmd_name, arg_name); help_me(gcp->cmd_name); return false; } /* * size_kmgp() -- convert ascii arg to numeric and scale as requested */ #define KILO_SHIFT 10 static size_t size_kmgp(char *arg) { size_t argval; char *next; argval = strtoul(arg, &next, 0); if (*next == '\0') return argval; switch (tolower(*next)) { case 'p': /* pages */ argval *= glctx.pagesize; break; case 'k': argval <<= KILO_SHIFT; break; case 'm': argval <<= KILO_SHIFT * 2; break; case 'g': argval <<= KILO_SHIFT * 3; break; default: return BOGUS_SIZE; /* bogus chars after number */ } return argval; } static size_t get_scaled_value(char *args, char *what) { glctx_t *gcp = &glctx; size_t size = size_kmgp(args); if (size == BOGUS_SIZE) { fprintf(stderr, "%s: segment %s must be numeric value" " followed by optional k, m, g or p [pages] scale factor.\n", gcp->program_name, what); } return size; } static int get_range(char *args, range_t * range, char **nextarg) { if (isdigit(*args)) { char *nextarg; args = strtok_r(args, whitespace, &nextarg); range->offset = get_scaled_value(args, "offset"); if (range->offset == BOGUS_SIZE) return CMD_ERROR; args = nextarg + strspn(nextarg, whitespace); /* * <length> ... only if offset specified */ if (*args != '\0') { args = strtok_r(args, whitespace, &nextarg); if (*args != '*') { range->length = get_scaled_value(args, "length"); if (range->length == BOGUS_SIZE) return CMD_ERROR; } else range->length = 0; /* map to end of file */ args = nextarg + strspn(nextarg, whitespace); } } *nextarg = args; return CMD_SUCCESS; } static int get_shared(char *args) { glctx_t *gcp = &glctx; int segflag = MAP_PRIVATE; if (!strcmp(args, "shared")) segflag = MAP_SHARED; else if (*args != '\0' && strcmp(args, "private")) { fprintf(stderr, "%s: anon seg access type must be one of: " "'private' or 'shared'\n", gcp->program_name); return -1; } return segflag; } /* * get_access() - check args for 'read'\'write' * return: * 1 = read * 2 = write * 0 = neither [error] */ static int get_access(char *args) { glctx_t *gcp = &glctx; int axcs = 1; int len = strlen(args); if (tolower(*args) == 'w') axcs = 2; else if (len != 0 && tolower(*args) != 'r') { fprintf(stderr, "%s: segment access must be 'r[ead]' or 'w[rite]'\n", gcp->program_name); return 0; } return axcs; } static bool numa_supported(void) { glctx_t *gcp = &glctx; if (gcp->numa_max_node <= 0) { fprintf(stderr, "%s: no NUMA support on this platform\n", gcp->program_name); return false; } return true; } static struct policies { char *pol_name; int pol_flag; } policies[] = { { "default", MPOL_DEFAULT}, { "preferred", MPOL_PREFERRED}, { "bind", MPOL_BIND}, { "interleaved", MPOL_INTERLEAVE}, { NULL, -1} }; /* * get_mbind_policy() - parse <policy> argument to mbind command * * format: <mpol>[+<flags>] * <mpol> is one of the policies[] above. * '+<flags>' = modifiers to mbind() call. parsed by get_mbind_flags() */ static int get_mbind_policy(char *args, char **nextarg) { glctx_t *gcp = &glctx; struct policies *polp; char *pol; pol = args; args += strcspn(args, " +"); for (polp = policies; polp->pol_name != NULL; ++polp) { size_t plen = args - pol; if (strncmp(pol, polp->pol_name, plen)) continue; *nextarg = args; return polp->pol_flag; } fprintf(stderr, "%s: unrecognized policy %s\n", gcp->program_name, pol); return CMD_ERROR; } /* * get_mbind_flags() - parse mbind(2) modifier flags * * format: +move[+wait] * 'move' specifies that currently allocated pages should be migrated. * => MPOL_MF_MOVE * 'wait' [only if 'move' specified] specifies that mbind(2) should not * return until all pages that can be migrated have been. * => MPOL_MF_WAIT * * returns flags on success; -1 on error */ static int get_mbind_flags(char *args, char **nextarg) { glctx_t *gcp = &glctx; char *arg; int flags = 0; arg = args; args += strcspn(args, " +"); if (strncmp(arg, "move", args - arg)) goto flags_err; flags = MPOL_MF_MOVE; if (*args == '+') { ++args; if (*args == '\0') { fprintf(stderr, "%s: expected 'wait' after '+'\n", gcp->program_name); return -1; } arg = strtok_r(args, " ", &args); if (strncmp(arg, "wait", strlen(arg))) goto flags_err; flags |= MPOL_MF_WAIT; } *nextarg = args; return flags; flags_err: fprintf(stderr, "%s: unrecognized mbind flag: %s\n", gcp->program_name, arg); return -1; } /* * get_nodemask() -- get nodemask from comma-separated list of node ids. * * N.B., caller must free returned nodemask */ static nodemask_t *get_nodemask(char *args) { glctx_t *gcp = &glctx; nodemask_t *nmp = (nodemask_t *) calloc(1, sizeof(nodemask_t)); char *next; int node; while (*args != '\0') { if (!isdigit(*args)) { fprintf(stderr, "%s: expected digit for <node/list>\n", gcp->program_name); goto out_err; } node = strtoul(args, &next, 10); if (node > gcp->numa_max_node) { fprintf(stderr, "%s: node ids must be <= %d\n", gcp->program_name, gcp->numa_max_node); goto out_err; } nodemask_set(nmp, node); if (*next == '\0') return nmp; if (*next != ',') { break; } args = next + 1; } out_err: free(nmp); return NULL; } /* * get_arg_nodeid_list() -- get list [array] of node ids from comma-separated list. * * on success, returns count of id's in list; on error -1 */ static int get_arg_nodeid_list(char *args, unsigned int *list) { glctx_t *gcp; char *next; nodemask_t my_allowed_nodes; int node, count = 0; gcp = &glctx; my_allowed_nodes = numa_get_membind_compat(); while (*args != '\0') { if (!isdigit(*args)) { fprintf(stderr, "%s: expected digit for <node/list>\n", gcp->program_name); return -1; } node = strtoul(args, &next, 10); if (node > gcp->numa_max_node) { fprintf(stderr, "%s: node ids must be <= %d\n", gcp->program_name, gcp->numa_max_node); return -1; } if (!nodemask_isset(&my_allowed_nodes, node)) { fprintf(stderr, "%s: node %d is not in my allowed node mask\n", gcp->program_name, node); return -1; } *(list + count++) = node; if (*next == '\0') return count; if (*next != ',') { break; } if (count >= gcp->numa_max_node) { fprintf(stderr, "%s: too many node ids in list\n", gcp->program_name); } args = next + 1; } return -1; } /* * get_current_nodeid_list() - fill arg array with nodes from * current thread's allowed node mask. return # of nodes in * mask. */ static int get_current_nodeid_list(unsigned int *fromids) { /* * FIXME (garrcoop): gcp is unitialized and shortly hereafter used in * an initialization statement..... UHHHHHHH... test writer fail? */ glctx_t *gcp; nodemask_t my_allowed_nodes; int nr_nodes = 0, max_node = gcp->numa_max_node; int node; gcp = &glctx; my_allowed_nodes = numa_get_membind_compat(); for (node = 0; node <= max_node; ++node) { if (nodemask_isset(&my_allowed_nodes, node)) *(fromids + nr_nodes++) = node; } /* * shouldn't happen, but let 'em know if it does */ if (nr_nodes == 0) fprintf(stderr, "%s: my allowed node mask is empty !!???\n", gcp->program_name); return nr_nodes; } /* * NOTE (garrcoop): Get rid of an -Wunused warning. This wasn't deleted because * I don't know what the original intent was for this code. */ #if 0 static void not_implemented() { glctx_t *gcp = &glctx; fprintf(stderr, "%s: %s not implemented yet\n", gcp->program_name, gcp->cmd_name); } #endif /* * ========================================================================= */ static int quit(char *args) { exit(0); /* let cleanup() do its thing */ } static int show_pid(char *args) { glctx_t *gcp = &glctx; printf("%s: pid = %d\n", gcp->program_name, getpid()); return CMD_SUCCESS; } static int pause_me(char *args) { // glctx_t *gcp = &glctx; pause(); reset_signal(); return CMD_SUCCESS; } static char *numa_header = " Node Total Mem[MB] Free Mem[MB]\n"; static int numa_info(char *args) { glctx_t *gcp = &glctx; unsigned int *nodeids; int nr_nodes, i; bool do_header = true; if (!numa_supported()) return CMD_ERROR; nodeids = calloc(gcp->numa_max_node, sizeof(*nodeids)); nr_nodes = get_current_nodeid_list(nodeids); if (nr_nodes < 0) return CMD_ERROR; for (i = 0; i < nr_nodes; ++i) { int node = nodeids[i]; long node_size, node_free; node_size = numa_node_size(node, &node_free); if (node_size < 0) { fprintf(stderr, "%s: numa_node_size() failed for node %d\n", gcp->program_name, node); return CMD_ERROR; } if (do_header) { do_header = false; puts(numa_header); } printf(" %3d %9ld %8ld\n", node, node_size / (1024 * 1024), node_free / (1024 * 1024)); } return CMD_SUCCESS; } /* * migrate <to-node-id[s]> [<from-node-id[s]>] * * Node id[s] - single node id or comma-separated list * <to-node-id[s]> - 1-for-1 with <from-node-id[s]>, OR * if <from-node-id[s]> omitted, <to-node-id[s]> must be * a single node id. */ static int migrate_process(char *args) { glctx_t *gcp = &glctx; unsigned int *fromids, *toids; char *idlist, *nextarg; struct timeval t_start, t_end; int nr_to, nr_from; int nr_migrated; int ret = CMD_ERROR; if (!numa_supported()) return CMD_ERROR; toids = calloc(gcp->numa_max_node, sizeof(*toids)); fromids = calloc(gcp->numa_max_node, sizeof(*fromids)); /* * <to-node-id[s]> */ if (!required_arg(args, "<to-node-id[s]>")) return CMD_ERROR; idlist = strtok_r(args, whitespace, &nextarg); nr_to = get_arg_nodeid_list(idlist, toids); if (nr_to <= 0) goto out_free; args = nextarg + strspn(nextarg, whitespace); if (*args != '\0') { /* * apparently, <from-node-id[s]> present */ idlist = strtok_r(args, whitespace, &nextarg); nr_from = get_arg_nodeid_list(idlist, fromids); if (nr_from <= 0) goto out_free; if (nr_from != nr_to) { fprintf(stderr, "%s: # of 'from' ids must = # of 'to' ids\n", gcp->program_name); goto out_free; } } else { int i; /* * no <from-node-id[s]>, nr_to must == 1, * get fromids from memory policy. */ if (nr_to > 1) { fprintf(stderr, "%s: # to ids must = 1" " when no 'from' ids specified\n", gcp->program_name); goto out_free; } nr_from = get_current_nodeid_list(fromids); if (nr_from <= 0) goto out_free; /* * remove 'to' node from 'from' list. to and from * lists can't intersect. */ for (i = nr_from - 1; i >= 0; --i) { if (*toids == *(fromids + i)) { while (i <= nr_from) { *(fromids + i) = *(fromids + i + 1); ++i; } --nr_from; break; } } /* * fill out nr_from toids with the single 'to' node */ for (; nr_to < nr_from; ++nr_to) *(toids + nr_to) = *toids; /* toids[0] */ } gettimeofday(&t_start, NULL); nr_migrated = syscall(__NR_migrate_pages, getpid(), nr_from, fromids, toids); if (nr_migrated < 0) { int err = errno; fprintf(stderr, "%s: migrate_pages failed - %s\n", gcp->program_name, strerror(err)); goto out_free; } gettimeofday(&t_end, NULL); printf("%s: migrated %d pages in %6.3fsecs\n", gcp->program_name, nr_migrated, (float)(tv_diff_usec(&t_start, &t_end)) / 1000000.0); ret = CMD_SUCCESS; out_free: free(toids); free(fromids); return ret; } static int show_seg(char *args) { glctx_t *gcp = &glctx; char *segname = NULL, *nextarg; args += strspn(args, whitespace); if (*args != '\0') segname = strtok_r(args, whitespace, &nextarg); if (!segment_show(segname)) return CMD_ERROR; return CMD_SUCCESS; } /* * anon_seg: <seg-name> <size>[kmgp] [private|shared] */ static int anon_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; int segflag = 0; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); if (!required_arg(args, "<size>")) return CMD_ERROR; args = strtok_r(args, whitespace, &nextarg); range.length = get_scaled_value(args, "size"); if (range.length == BOGUS_SIZE) return CMD_ERROR; args = nextarg + strspn(nextarg, whitespace); if (*args != '\0') { segflag = get_shared(args); if (segflag == -1) return CMD_ERROR; } if (!segment_register(SEGT_ANON, segname, &range, segflag)) return CMD_ERROR; return CMD_SUCCESS; } /* * file_seg: <path-name> [<offset>[kmgp] <length>[kmgp] [private|shared]] */ static int file_seg(char *args) { glctx_t *gcp = &glctx; char *pathname, *nextarg; range_t range = { 0L, 0L }; int segflag = MAP_PRIVATE; args += strspn(args, whitespace); if (!required_arg(args, "<path-name>")) return CMD_ERROR; pathname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); /* * offset, length are optional */ if (get_range(args, &range, &nextarg) == CMD_ERROR) return CMD_ERROR; args = nextarg; if (*args != '\0') { segflag = get_shared(args); if (segflag == -1) return CMD_ERROR; } if (!segment_register(SEGT_FILE, pathname, &range, segflag)) return CMD_ERROR; return CMD_SUCCESS; } /* * remove_seg: <seg-name> [<seg-name> ...] */ static int remove_seg(char *args) { glctx_t *gcp = &glctx; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; while (*args != '\0') { char *segname, *nextarg; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); segment_remove(segname); } return 0; } /* * touch_seg: <seg-name> [<offset> <length>] [read|write] */ static int touch_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; int axcs; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); /* * offset, length are optional */ if (get_range(args, &range, &nextarg) == CMD_ERROR) return CMD_ERROR; args = nextarg; axcs = get_access(args); if (axcs == 0) return CMD_ERROR; if (!segment_touch(segname, &range, axcs - 1)) return CMD_ERROR; return CMD_SUCCESS; } /* * unmap <seg-name> - unmap specified segment, but remember name/size/... */ static int unmap_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); if (!segment_unmap(segname)) return CMD_ERROR; return CMD_SUCCESS; } /* * map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] */ static int map_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; range_t *rangep = NULL; int segflag = MAP_PRIVATE; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); /* * offset, length are optional */ if (get_range(args, &range, &nextarg) == CMD_ERROR) return CMD_ERROR; if (args != nextarg) { rangep = ⦥ /* override any registered range */ args = nextarg; } if (*args != '\0') { segflag = get_shared(args); if (segflag == -1) return CMD_ERROR; } if (!segment_map(segname, rangep, segflag)) return CMD_ERROR; return CMD_SUCCESS; } /* * mbind <seg-name> [<offset>[kmgp] <length>[kmgp]] <policy> <node-list> */ static int mbind_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; nodemask_t *nodemask = NULL; int policy, flags = 0; int ret; if (!numa_supported()) return CMD_ERROR; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); /* * offset, length are optional */ if (get_range(args, &range, &nextarg) == CMD_ERROR) return CMD_ERROR; args = nextarg; if (!required_arg(args, "<policy>")) return CMD_ERROR; policy = get_mbind_policy(args, &nextarg); if (policy < 0) return CMD_ERROR; args = nextarg + strspn(nextarg, whitespace); if (*args == '+') { flags = get_mbind_flags(++args, &nextarg); if (flags == -1) return CMD_ERROR; } args = nextarg + strspn(nextarg, whitespace); if (policy != MPOL_DEFAULT) { if (!required_arg(args, "<node/list>")) return CMD_ERROR; nodemask = get_nodemask(args); if (nodemask == NULL) return CMD_ERROR; } ret = CMD_SUCCESS; #if 1 // for testing if (!segment_mbind(segname, &range, policy, nodemask, flags)) ret = CMD_ERROR; #endif if (nodemask != NULL) free(nodemask); return ret; } /* * shmem_seg - create [shmget] and register a SysV shared memory segment * of specified size */ static int shmem_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); if (!required_arg(args, "<size>")) return CMD_ERROR; args = strtok_r(args, whitespace, &nextarg); range.length = get_scaled_value(args, "size"); if (range.length == BOGUS_SIZE) return CMD_ERROR; args = nextarg + strspn(nextarg, whitespace); if (!segment_register(SEGT_SHM, segname, &range, MAP_SHARED)) return CMD_ERROR; return CMD_SUCCESS; } /* * where <seg-name> [<offset>[kmgp] <length>[kmgp]] - show node location * of specified range of segment. * * NOTE: if neither <offset> nor <length> specified, <offset> defaults * to 0 [start of segment], as usual, and length defaults to 64 pages * rather than the entire segment. Suitable for a "quick look" at where * segment resides. */ static int where_seg(char *args) { glctx_t *gcp = &glctx; char *segname, *nextarg; range_t range = { 0L, 0L }; int ret; if (!numa_supported()) return CMD_ERROR; args += strspn(args, whitespace); if (!required_arg(args, "<seg-name>")) return CMD_ERROR; segname = strtok_r(args, whitespace, &nextarg); args = nextarg + strspn(nextarg, whitespace); /* * offset, length are optional */ if (get_range(args, &range, &nextarg) == CMD_ERROR) return CMD_ERROR; if (args == nextarg) range.length = 64 * gcp->pagesize; /* default length */ if (!segment_location(segname, &range)) return CMD_ERROR; return CMD_SUCCESS; } #if 0 static int command(char *args) { glctx_t *gcp = &glctx; return CMD_SUCCESS; } #endif /* * ========================================================================= */ typedef int (*cmd_func_t) (char *); struct command { char *cmd_name; cmd_func_t cmd_func; /* */ char *cmd_help; } cmd_table[] = { { .cmd_name = "quit",.cmd_func = quit,.cmd_help = "quit - just what you think\n" "\tEOF on stdin has the same effect\n"}, { .cmd_name = "help",.cmd_func = help_me,.cmd_help = "help - show this help\n" "help <command> - display help for just <command>\n"}, { .cmd_name = "pid",.cmd_func = show_pid,.cmd_help = "pid - show process id of this session\n"}, { .cmd_name = "pause",.cmd_func = pause_me,.cmd_help = "pause - pause program until signal" " -- e.g., INT, USR1\n"}, { .cmd_name = "numa",.cmd_func = numa_info,.cmd_help = "numa - display numa info as seen by this program.\n" "\tshows nodes from which program may allocate memory\n" "\twith total and free memory.\n"}, { .cmd_name = "migrate",.cmd_func = migrate_process,.cmd_help = "migrate <to-node-id[s]> [<from-node-id[s]>] - \n" "\tmigrate this process' memory from <from-node-id[s]>\n" "\tto <to-node-id[s]>. Specify multiple node ids as a\n" "\tcomma-separated list. TODO - more info\n"}, { .cmd_name = "show",.cmd_func = show_seg,.cmd_help = "show [<name>] - show info for segment[s]; default all\n"}, { .cmd_name = "anon",.cmd_func = anon_seg,.cmd_help = "anon <seg-name> <seg-size>[k|m|g|p] [<seg-share>] -\n" "\tdefine a MAP_ANONYMOUS segment of specified size\n" "\t<seg-share> := private|shared - default = private\n"}, { .cmd_name = "file",.cmd_func = file_seg,.cmd_help = "file <pathname> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] -\n" "\tdefine a mapped file segment of specified length starting at the\n" "\tspecified offset into the file. <offset> and <length> may be\n" "\tomitted and specified on the map command.\n" "\t<seg-share> := private|shared - default = private\n"}, { .cmd_name = "shm",.cmd_func = shmem_seg,.cmd_help = "shm <seg-name> <seg-size>[k|m|g|p] - \n" "\tdefine a shared memory segment of specified size.\n" "\tYou may need to increase limits [/proc/sys/kernel/shmmax].\n" "\tUse map/unmap to attach/detach\n"}, { .cmd_name = "remove",.cmd_func = remove_seg,.cmd_help = "remove <seg-name> [<seg-name> ...] - remove the named segment[s]\n"}, { .cmd_name = "map",.cmd_func = map_seg,.cmd_help = "map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] - \n" "\tmmap()/shmat() a previously defined, currently unmapped() segment.\n" "\t<offset> and <length> apply only to mapped files.\n" "\tUse <length> of '*' or '0' to map to the end of the file.\n"}, { .cmd_name = "unmap",.cmd_func = unmap_seg,.cmd_help = "unmap <seg-name> - unmap specified segment, but remember name/size/...\n"}, { .cmd_name = "touch",.cmd_func = touch_seg,.cmd_help = "touch <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [read|write] - \n" "\tread [default] or write the named segment from <offset> through\n" "\t<offset>+<length>. If <offset> and <length> omitted, touches all\n" "\t of mapped segment.\n"}, { .cmd_name = "mbind",.cmd_func = mbind_seg,.cmd_help = "mbind <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]]\n" " <policy>[+move[+wait]] [<node/list>] - \n" "\tset the numa policy for the specified range of the name segment\n" "\tto policy -- one of {default, bind, preferred, interleaved}.\n" "\t<node/list> specifies a node id or a comma separated list of\n" "\tnode ids. <node> is ignored for 'default' policy, and only\n" "\tthe first node is used for 'preferred' policy.\n" "\t'+move' specifies that currently allocated pages be prepared\n" "\t for migration on next touch\n" "\t'+wait' [valid only with +move] specifies that pages mbind()\n" " touch the pages and wait for migration before returning.\n"}, { .cmd_name = "where",.cmd_func = where_seg,.cmd_help = "where <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] - \n" "\tshow the node location of pages in the specified range\n" "\tof the specified segment. <offset> defaults to start of\n" "\tsegment; <length> defaults to 64 pages.\n"}, #if 0 /* template for new commands */ { .cmd_name = "",.cmd_func =,.cmd_help =}, #endif { .cmd_name = NULL} }; static int help_me(char *args) { struct command *cmdp = cmd_table; char *cmd, *nextarg; int cmdlen; bool match = false; args += strspn(args, whitespace); if (*args != '\0') { cmd = strtok_r(args, whitespace, &nextarg); cmdlen = strlen(cmd); } else { cmd = NULL; cmdlen = 0; } for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) { if (cmd == NULL || !strncmp(cmd, cmdp->cmd_name, cmdlen)) { printf("%s\n", cmdp->cmd_help); match = true; } } if (!match) { printf("unrecognized command: %s\n", cmd); printf("\tuse 'help' for a complete list of commands\n"); return CMD_ERROR; } return CMD_SUCCESS; } /* * ========================================================================= */ #define CMDBUFSZ 256 static bool unique_abbrev(char *cmd, size_t clen, struct command *cmdp) { for (; cmdp->cmd_name != NULL; ++cmdp) { if (!strncmp(cmd, cmdp->cmd_name, clen)) return false; /* match: not unique */ } return true; } static int parse_command(char *cmdline) { glctx_t *gcp = &glctx; char *cmd, *args; struct command *cmdp; cmdline += strspn(cmdline, whitespace); /* possibly redundant */ cmd = strtok_r(cmdline, whitespace, &args); for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) { size_t clen = strlen(cmd); int ret; if (strncmp(cmd, cmdp->cmd_name, clen)) continue; if (!unique_abbrev(cmd, clen, cmdp + 1)) { fprintf(stderr, "%s: ambiguous command: %s\n", gcp->program_name, cmd); return CMD_ERROR; } gcp->cmd_name = cmdp->cmd_name; ret = cmdp->cmd_func(args); gcp->cmd_name = NULL; return ret; } fprintf(stderr, "%s: unrecognized command %s\n", __FUNCTION__, cmd); return CMD_ERROR; } void process_commands() { glctx_t *gcp = &glctx; char cmdbuf[CMDBUFSZ]; do { char *cmdline; size_t cmdlen; if (is_option(INTERACTIVE)) printf("%s>", gcp->program_name); cmdline = fgets(cmdbuf, CMDBUFSZ, stdin); if (cmdline == NULL) { printf("%s\n", is_option(INTERACTIVE) ? "" : "EOF on stdin"); exit(0); /* EOF */ } if (cmdline[0] == '\n') continue; /* * trim trailing newline, if any */ cmdlen = strlen(cmdline); if (cmdline[cmdlen - 1] == '\n') cmdline[--cmdlen] = '\0'; cmdline += strspn(cmdline, whitespace); cmdlen -= (cmdline - cmdbuf); if (cmdlen == 0) { //TODO: interactive help? continue; /* ignore blank lines */ } if (*cmdline == '#') continue; /* comments */ /* * trim trailing whitespace for ease of parsing */ while (strchr(whitespace, cmdline[cmdlen - 1])) cmdline[--cmdlen] = '\0'; if (cmdlen == 0) continue; /* * reset signals just before parsing a command. * non-interactive: exit on SIGQUIT */ if (signalled(gcp)) { if (!is_option(INTERACTIVE) && gcp->siginfo->si_signo == SIGQUIT) exit(0); reset_signal(); } /* * non-interactive: errors are fatal */ if (!is_option(INTERACTIVE)) { vprint("%s>%s\n", gcp->program_name, cmdline); if (parse_command(cmdline) == CMD_ERROR) { fprintf(stderr, "%s: command error\n", gcp->program_name); exit(4); } } else parse_command(cmdline); } while (1); } #endif