1316 lines
30 KiB
C
1316 lines
30 KiB
C
/*
|
|
* 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"
|
|
|
|
#ifdef HAVE_NUMA_V2
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/mman.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.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
|
|
#define CMDBUFSZ 256
|
|
|
|
#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";
|
|
|
|
inline char *get_next_arg(char *args, char *nextarg)
|
|
{
|
|
return nextarg ? nextarg + strspn(nextarg, whitespace) : args + strnlen(args, CMDBUFSZ);
|
|
}
|
|
|
|
/*
|
|
* =========================================================================
|
|
*/
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* <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 = get_next_arg(args, nextarg);
|
|
}
|
|
}
|
|
|
|
*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 uninitialized 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 = get_next_arg(args, nextarg);
|
|
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* deletefile <file-name>
|
|
*/
|
|
static int delete_file(char *args)
|
|
{
|
|
glctx_t *gcp = &glctx;
|
|
char *filename, *nextarg;
|
|
|
|
args += strspn(args, whitespace);
|
|
if (!required_arg(args, "<file-name>"))
|
|
return CMD_ERROR;
|
|
filename = strtok_r(args, whitespace, &nextarg);
|
|
|
|
if (remove(filename)) {
|
|
fprintf(stderr, "%s: deletefile failed - %s\n",
|
|
gcp->program_name, strerror(errno));
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* createfile <file-name> <size>[k|m|g|p]]
|
|
*/
|
|
static int create_file(char *args)
|
|
{
|
|
glctx_t *gcp = &glctx;
|
|
char *filename, *nextarg;
|
|
size_t len;
|
|
int fd;
|
|
|
|
args += strspn(args, whitespace);
|
|
if (!required_arg(args, "<file-name>"))
|
|
return CMD_ERROR;
|
|
filename = strtok_r(args, whitespace, &nextarg);
|
|
args = nextarg + strspn(nextarg, whitespace);
|
|
|
|
if (!required_arg(args, "<size>"))
|
|
return CMD_ERROR;
|
|
args = strtok_r(args, whitespace, &nextarg);
|
|
len = get_scaled_value(args, "size");
|
|
if (len == BOGUS_SIZE)
|
|
return CMD_ERROR;
|
|
args = get_next_arg(args, nextarg);
|
|
|
|
fd = open(filename, O_RDWR | O_CREAT, 0600);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "%s: createfile failed - %s\n",
|
|
gcp->program_name, strerror(errno));
|
|
return CMD_ERROR;
|
|
}
|
|
|
|
if (posix_fallocate(fd, 0, len)) {
|
|
fprintf(stderr, "%s: createfile failed - %s\n",
|
|
gcp->program_name, strerror(errno));
|
|
return CMD_ERROR;
|
|
}
|
|
close(fd);
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* 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 = get_next_arg(args, nextarg);
|
|
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* 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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* 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 = get_next_arg(args, nextarg);
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
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 = get_next_arg(args, nextarg);
|
|
|
|
/*
|
|
* 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 = "createfile", .cmd_func = create_file, .cmd_help =
|
|
"createfile <file-name> <size>[k|m|g|p]]",}, {
|
|
.cmd_name = "deletefile", .cmd_func = delete_file, .cmd_help =
|
|
"deletefile <file-name>"}, {
|
|
.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;
|
|
}
|
|
|
|
/*
|
|
* =========================================================================
|
|
*/
|
|
|
|
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
|