1 /*
2 * memtoy: commands.c - command line interface
3 *
4 * A brute force/ad hoc command interpreter:
5 * + parse commands [interactive or batch]
6 * + convert/validate arguments
7 * + some general/administrative commands herein
8 * + actual segment management routines in segment.c
9 */
10 /*
11 * Copyright (c) 2005 Hewlett-Packard, Inc
12 * All rights reserved.
13 */
14
15 /*
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 */
30
31 #include "config.h"
32 #if HAVE_NUMA_H && HAVE_NUMAIF_H && HAVE_LINUX_MEMPOLICY_H
33 #include <linux/mempolicy.h>
34 #include <sys/types.h>
35 #include <sys/time.h>
36 #include <sys/mman.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <numa.h>
40 #include <numaif.h>
41 #include <stdarg.h>
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <sys/syscall.h>
47
48 #include "memtoy.h"
49 #include "test.h"
50
51 #define CMD_SUCCESS 0
52 #define CMD_ERROR 1
53
54 #ifndef __NR_migrate_pages
55 #define __NR_migrate_pages 0
56 #endif
57
58 #ifndef MPOL_MF_WAIT
59 #define MPOL_MF_WAIT (1<<2) /* Wait for existing pages to migrate */
60 #endif
61
62 #if defined(LIBNUMA_API_VERSION) && LIBNUMA_API_VERSION == 2
nodemask_isset(nodemask_t * mask,int node)63 static inline int nodemask_isset(nodemask_t * mask, int node)
64 {
65 if ((unsigned)node >= NUMA_NUM_NODES)
66 return 0;
67 if (mask->n[node / (8 * sizeof(unsigned long))] &
68 (1UL << (node % (8 * sizeof(unsigned long)))))
69 return 1;
70 return 0;
71 }
72
nodemask_set(nodemask_t * mask,int node)73 static inline void nodemask_set(nodemask_t * mask, int node)
74 {
75 mask->n[node / (8 * sizeof(unsigned long))] |=
76 (1UL << (node % (8 * sizeof(unsigned long))));
77 }
78 #endif
79
80 static char *whitespace = " \t";
81
82 /*
83 * =========================================================================
84 */
85 static int help_me(char *); /* forward reference */
86
87 /*
88 * required_arg -- check for a required argument; issue message if not there
89 *
90 * return true if arg [something] exists; else return false
91 */
required_arg(char * arg,char * arg_name)92 static bool required_arg(char *arg, char *arg_name)
93 {
94 glctx_t *gcp = &glctx;
95
96 if (*arg != '\0')
97 return true;
98
99 fprintf(stderr, "%s: command '%s' missing required argument: %s\n\n",
100 gcp->program_name, gcp->cmd_name, arg_name);
101 help_me(gcp->cmd_name);
102
103 return false;
104 }
105
106 /*
107 * size_kmgp() -- convert ascii arg to numeric and scale as requested
108 */
109 #define KILO_SHIFT 10
size_kmgp(char * arg)110 static size_t size_kmgp(char *arg)
111 {
112 size_t argval;
113 char *next;
114
115 argval = strtoul(arg, &next, 0);
116 if (*next == '\0')
117 return argval;
118
119 switch (tolower(*next)) {
120 case 'p': /* pages */
121 argval *= glctx.pagesize;
122 break;
123
124 case 'k':
125 argval <<= KILO_SHIFT;
126 break;
127
128 case 'm':
129 argval <<= KILO_SHIFT * 2;
130 break;
131
132 case 'g':
133 argval <<= KILO_SHIFT * 3;
134 break;
135
136 default:
137 return BOGUS_SIZE; /* bogus chars after number */
138 }
139
140 return argval;
141 }
142
get_scaled_value(char * args,char * what)143 static size_t get_scaled_value(char *args, char *what)
144 {
145 glctx_t *gcp = &glctx;
146 size_t size = size_kmgp(args);
147
148 if (size == BOGUS_SIZE) {
149 fprintf(stderr, "%s: segment %s must be numeric value"
150 " followed by optional k, m, g or p [pages] scale factor.\n",
151 gcp->program_name, what);
152 }
153
154 return size;
155 }
156
get_range(char * args,range_t * range,char ** nextarg)157 static int get_range(char *args, range_t * range, char **nextarg)
158 {
159
160 if (isdigit(*args)) {
161 char *nextarg;
162
163 args = strtok_r(args, whitespace, &nextarg);
164 range->offset = get_scaled_value(args, "offset");
165 if (range->offset == BOGUS_SIZE)
166 return CMD_ERROR;
167 args = nextarg + strspn(nextarg, whitespace);
168
169 /*
170 * <length> ... only if offset specified
171 */
172 if (*args != '\0') {
173 args = strtok_r(args, whitespace, &nextarg);
174 if (*args != '*') {
175 range->length =
176 get_scaled_value(args, "length");
177 if (range->length == BOGUS_SIZE)
178 return CMD_ERROR;
179 } else
180 range->length = 0; /* map to end of file */
181 args = nextarg + strspn(nextarg, whitespace);
182 }
183 }
184
185 *nextarg = args;
186 return CMD_SUCCESS;
187 }
188
get_shared(char * args)189 static int get_shared(char *args)
190 {
191 glctx_t *gcp = &glctx;
192 int segflag = MAP_PRIVATE;
193
194 if (!strcmp(args, "shared"))
195 segflag = MAP_SHARED;
196 else if (*args != '\0' && strcmp(args, "private")) {
197 fprintf(stderr, "%s: anon seg access type must be one of: "
198 "'private' or 'shared'\n", gcp->program_name);
199 return -1;
200 }
201 return segflag;
202 }
203
204 /*
205 * get_access() - check args for 'read'\'write'
206 * return:
207 * 1 = read
208 * 2 = write
209 * 0 = neither [error]
210 */
get_access(char * args)211 static int get_access(char *args)
212 {
213 glctx_t *gcp = &glctx;
214 int axcs = 1;
215 int len = strlen(args);
216
217 if (tolower(*args) == 'w')
218 axcs = 2;
219 else if (len != 0 && tolower(*args) != 'r') {
220 fprintf(stderr,
221 "%s: segment access must be 'r[ead]' or 'w[rite]'\n",
222 gcp->program_name);
223 return 0;
224 }
225
226 return axcs;
227 }
228
numa_supported(void)229 static bool numa_supported(void)
230 {
231 glctx_t *gcp = &glctx;
232
233 if (gcp->numa_max_node <= 0) {
234 fprintf(stderr, "%s: no NUMA support on this platform\n",
235 gcp->program_name);
236 return false;
237 }
238 return true;
239 }
240
241 static struct policies {
242 char *pol_name;
243 int pol_flag;
244 } policies[] = {
245 {
246 "default", MPOL_DEFAULT}, {
247 "preferred", MPOL_PREFERRED}, {
248 "bind", MPOL_BIND}, {
249 "interleaved", MPOL_INTERLEAVE}, {
250 NULL, -1}
251 };
252
253 /*
254 * get_mbind_policy() - parse <policy> argument to mbind command
255 *
256 * format: <mpol>[+<flags>]
257 * <mpol> is one of the policies[] above.
258 * '+<flags>' = modifiers to mbind() call. parsed by get_mbind_flags()
259 */
get_mbind_policy(char * args,char ** nextarg)260 static int get_mbind_policy(char *args, char **nextarg)
261 {
262 glctx_t *gcp = &glctx;
263 struct policies *polp;
264 char *pol;
265
266 pol = args;
267 args += strcspn(args, " +");
268
269 for (polp = policies; polp->pol_name != NULL; ++polp) {
270 size_t plen = args - pol;
271
272 if (strncmp(pol, polp->pol_name, plen))
273 continue;
274
275 *nextarg = args;
276 return polp->pol_flag;
277 }
278
279 fprintf(stderr, "%s: unrecognized policy %s\n",
280 gcp->program_name, pol);
281 return CMD_ERROR;
282 }
283
284 /*
285 * get_mbind_flags() - parse mbind(2) modifier flags
286 *
287 * format: +move[+wait]
288 * 'move' specifies that currently allocated pages should be migrated.
289 * => MPOL_MF_MOVE
290 * 'wait' [only if 'move' specified] specifies that mbind(2) should not
291 * return until all pages that can be migrated have been.
292 * => MPOL_MF_WAIT
293 *
294 * returns flags on success; -1 on error
295 */
get_mbind_flags(char * args,char ** nextarg)296 static int get_mbind_flags(char *args, char **nextarg)
297 {
298 glctx_t *gcp = &glctx;
299 char *arg;
300 int flags = 0;
301
302 arg = args;
303 args += strcspn(args, " +");
304
305 if (strncmp(arg, "move", args - arg))
306 goto flags_err;
307
308 flags = MPOL_MF_MOVE;
309
310 if (*args == '+') {
311 ++args;
312 if (*args == '\0') {
313 fprintf(stderr, "%s: expected 'wait' after '+'\n",
314 gcp->program_name);
315 return -1;
316 }
317 arg = strtok_r(args, " ", &args);
318 if (strncmp(arg, "wait", strlen(arg)))
319 goto flags_err;
320
321 flags |= MPOL_MF_WAIT;
322 }
323
324 *nextarg = args;
325 return flags;
326
327 flags_err:
328 fprintf(stderr, "%s: unrecognized mbind flag: %s\n",
329 gcp->program_name, arg);
330 return -1;
331
332 }
333
334 /*
335 * get_nodemask() -- get nodemask from comma-separated list of node ids.
336 *
337 * N.B., caller must free returned nodemask
338 */
get_nodemask(char * args)339 static nodemask_t *get_nodemask(char *args)
340 {
341 glctx_t *gcp = &glctx;
342 nodemask_t *nmp = (nodemask_t *) calloc(1, sizeof(nodemask_t));
343 char *next;
344 int node;
345 while (*args != '\0') {
346 if (!isdigit(*args)) {
347 fprintf(stderr, "%s: expected digit for <node/list>\n",
348 gcp->program_name);
349 goto out_err;
350 }
351
352 node = strtoul(args, &next, 10);
353
354 if (node > gcp->numa_max_node) {
355 fprintf(stderr, "%s: node ids must be <= %d\n",
356 gcp->program_name, gcp->numa_max_node);
357 goto out_err;
358 }
359
360 nodemask_set(nmp, node);
361
362 if (*next == '\0')
363 return nmp;
364 if (*next != ',') {
365 break;
366 }
367 args = next + 1;
368 }
369
370 out_err:
371 free(nmp);
372 return NULL;
373 }
374
375 /*
376 * get_arg_nodeid_list() -- get list [array] of node ids from comma-separated list.
377 *
378 * on success, returns count of id's in list; on error -1
379 */
get_arg_nodeid_list(char * args,unsigned int * list)380 static int get_arg_nodeid_list(char *args, unsigned int *list)
381 {
382 glctx_t *gcp;
383 char *next;
384 nodemask_t my_allowed_nodes;
385 int node, count = 0;
386
387 gcp = &glctx;
388 #if defined(LIBNUMA_API_VERSION) && LIBNUMA_API_VERSION == 2
389 my_allowed_nodes = numa_get_membind_compat();
390 #else
391 my_allowed_nodes = numa_get_membind();
392 #endif
393 while (*args != '\0') {
394 if (!isdigit(*args)) {
395 fprintf(stderr, "%s: expected digit for <node/list>\n",
396 gcp->program_name);
397 return -1;
398 }
399
400 node = strtoul(args, &next, 10);
401
402 if (node > gcp->numa_max_node) {
403 fprintf(stderr, "%s: node ids must be <= %d\n",
404 gcp->program_name, gcp->numa_max_node);
405 return -1;
406 }
407
408 if (!nodemask_isset(&my_allowed_nodes, node)) {
409 fprintf(stderr,
410 "%s: node %d is not in my allowed node mask\n",
411 gcp->program_name, node);
412 return -1;
413 }
414
415 *(list + count++) = node;
416
417 if (*next == '\0')
418 return count;
419 if (*next != ',') {
420 break;
421 }
422
423 if (count >= gcp->numa_max_node) {
424 fprintf(stderr, "%s: too many node ids in list\n",
425 gcp->program_name);
426 }
427 args = next + 1;
428 }
429
430 return -1;
431 }
432
433 /*
434 * get_current_nodeid_list() - fill arg array with nodes from
435 * current thread's allowed node mask. return # of nodes in
436 * mask.
437 */
get_current_nodeid_list(unsigned int * fromids)438 static int get_current_nodeid_list(unsigned int *fromids)
439 {
440 /*
441 * FIXME (garrcoop): gcp is unitialized and shortly hereafter used in
442 * an initialization statement..... UHHHHHHH... test writer fail?
443 */
444 glctx_t *gcp;
445 nodemask_t my_allowed_nodes;
446 int nr_nodes = 0, max_node = gcp->numa_max_node;
447 int node;
448
449 gcp = &glctx;
450 #if defined(LIBNUMA_API_VERSION) && LIBNUMA_API_VERSION == 2
451 my_allowed_nodes = numa_get_membind_compat();
452 #else
453 my_allowed_nodes = numa_get_membind();
454 #endif
455 for (node = 0; node <= max_node; ++node) {
456 if (nodemask_isset(&my_allowed_nodes, node))
457 *(fromids + nr_nodes++) = node;
458 }
459
460 /*
461 * shouldn't happen, but let 'em know if it does
462 */
463 if (nr_nodes == 0)
464 fprintf(stderr, "%s: my allowed node mask is empty !!???\n",
465 gcp->program_name);
466 return nr_nodes;
467 }
468
469 /*
470 * NOTE (garrcoop): Get rid of an -Wunused warning. This wasn't deleted because
471 * I don't know what the original intent was for this code.
472 */
473 #if 0
474 static void not_implemented()
475 {
476 glctx_t *gcp = &glctx;
477
478 fprintf(stderr, "%s: %s not implemented yet\n",
479 gcp->program_name, gcp->cmd_name);
480 }
481 #endif
482
483 /*
484 * =========================================================================
485 */
quit(char * args)486 static int quit(char *args)
487 {
488 exit(0); /* let cleanup() do its thing */
489 }
490
show_pid(char * args)491 static int show_pid(char *args)
492 {
493 glctx_t *gcp = &glctx;
494
495 printf("%s: pid = %d\n", gcp->program_name, getpid());
496
497 return CMD_SUCCESS;
498 }
499
pause_me(char * args)500 static int pause_me(char *args)
501 {
502 // glctx_t *gcp = &glctx;
503
504 pause();
505 reset_signal();
506
507 return CMD_SUCCESS;
508 }
509
510 static char *numa_header = " Node Total Mem[MB] Free Mem[MB]\n";
numa_info(char * args)511 static int numa_info(char *args)
512 {
513 glctx_t *gcp = &glctx;
514 unsigned int *nodeids;
515 int nr_nodes, i;
516 bool do_header = true;
517
518 if (!numa_supported())
519 return CMD_ERROR;
520
521 nodeids = calloc(gcp->numa_max_node, sizeof(*nodeids));
522 nr_nodes = get_current_nodeid_list(nodeids);
523 if (nr_nodes < 0)
524 return CMD_ERROR;
525
526 for (i = 0; i < nr_nodes; ++i) {
527 int node = nodeids[i];
528 long node_size, node_free;
529
530 node_size = numa_node_size(node, &node_free);
531 if (node_size < 0) {
532 fprintf(stderr,
533 "%s: numa_node_size() failed for node %d\n",
534 gcp->program_name, node);
535 return CMD_ERROR;
536 }
537
538 if (do_header) {
539 do_header = false;
540 puts(numa_header);
541 }
542 printf(" %3d %9ld %8ld\n", node,
543 node_size / (1024 * 1024), node_free / (1024 * 1024));
544 }
545
546 return CMD_SUCCESS;
547 }
548
549 /*
550 * migrate <to-node-id[s]> [<from-node-id[s]>]
551 *
552 * Node id[s] - single node id or comma-separated list
553 * <to-node-id[s]> - 1-for-1 with <from-node-id[s]>, OR
554 * if <from-node-id[s]> omitted, <to-node-id[s]> must be
555 * a single node id.
556 */
migrate_process(char * args)557 static int migrate_process(char *args)
558 {
559 glctx_t *gcp = &glctx;
560 unsigned int *fromids, *toids;
561 char *idlist, *nextarg;
562 struct timeval t_start, t_end;
563 int nr_to, nr_from;
564 int nr_migrated;
565 int ret = CMD_ERROR;
566
567 if (!numa_supported())
568 return CMD_ERROR;
569
570 toids = calloc(gcp->numa_max_node, sizeof(*toids));
571 fromids = calloc(gcp->numa_max_node, sizeof(*fromids));
572
573 /*
574 * <to-node-id[s]>
575 */
576 if (!required_arg(args, "<to-node-id[s]>"))
577 return CMD_ERROR;
578 idlist = strtok_r(args, whitespace, &nextarg);
579 nr_to = get_arg_nodeid_list(idlist, toids);
580 if (nr_to <= 0)
581 goto out_free;
582 args = nextarg + strspn(nextarg, whitespace);
583
584 if (*args != '\0') {
585 /*
586 * apparently, <from-node-id[s]> present
587 */
588 idlist = strtok_r(args, whitespace, &nextarg);
589 nr_from = get_arg_nodeid_list(idlist, fromids);
590 if (nr_from <= 0)
591 goto out_free;
592 if (nr_from != nr_to) {
593 fprintf(stderr,
594 "%s: # of 'from' ids must = # of 'to' ids\n",
595 gcp->program_name);
596 goto out_free;
597 }
598 } else {
599 int i;
600
601 /*
602 * no <from-node-id[s]>, nr_to must == 1,
603 * get fromids from memory policy.
604 */
605 if (nr_to > 1) {
606 fprintf(stderr, "%s: # to ids must = 1"
607 " when no 'from' ids specified\n",
608 gcp->program_name);
609 goto out_free;
610 }
611 nr_from = get_current_nodeid_list(fromids);
612 if (nr_from <= 0)
613 goto out_free;
614
615 /*
616 * remove 'to' node from 'from' list. to and from
617 * lists can't intersect.
618 */
619 for (i = nr_from - 1; i >= 0; --i) {
620 if (*toids == *(fromids + i)) {
621 while (i <= nr_from) {
622 *(fromids + i) = *(fromids + i + 1);
623 ++i;
624 }
625 --nr_from;
626 break;
627 }
628 }
629
630 /*
631 * fill out nr_from toids with the single 'to' node
632 */
633 for (; nr_to < nr_from; ++nr_to)
634 *(toids + nr_to) = *toids; /* toids[0] */
635 }
636
637 gettimeofday(&t_start, NULL);
638 nr_migrated =
639 syscall(__NR_migrate_pages, getpid(), nr_from, fromids, toids);
640 if (nr_migrated < 0) {
641 int err = errno;
642 fprintf(stderr, "%s: migrate_pages failed - %s\n",
643 gcp->program_name, strerror(err));
644 goto out_free;
645 }
646 gettimeofday(&t_end, NULL);
647 printf("%s: migrated %d pages in %6.3fsecs\n",
648 gcp->program_name, nr_migrated,
649 (float)(tv_diff_usec(&t_start, &t_end)) / 1000000.0);
650 ret = CMD_SUCCESS;
651
652 out_free:
653 free(toids);
654 free(fromids);
655 return ret;
656 }
657
show_seg(char * args)658 static int show_seg(char *args)
659 {
660 glctx_t *gcp = &glctx;
661
662 char *segname = NULL, *nextarg;
663
664 args += strspn(args, whitespace);
665 if (*args != '\0')
666 segname = strtok_r(args, whitespace, &nextarg);
667
668 if (!segment_show(segname))
669 return CMD_ERROR;
670
671 return CMD_SUCCESS;
672 }
673
674 /*
675 * anon_seg: <seg-name> <size>[kmgp] [private|shared]
676 */
anon_seg(char * args)677 static int anon_seg(char *args)
678 {
679 glctx_t *gcp = &glctx;
680
681 char *segname, *nextarg;
682 range_t range = { 0L, 0L };
683 int segflag = 0;
684
685 args += strspn(args, whitespace);
686
687 if (!required_arg(args, "<seg-name>"))
688 return CMD_ERROR;
689 segname = strtok_r(args, whitespace, &nextarg);
690 args = nextarg + strspn(nextarg, whitespace);
691
692 if (!required_arg(args, "<size>"))
693 return CMD_ERROR;
694 args = strtok_r(args, whitespace, &nextarg);
695 range.length = get_scaled_value(args, "size");
696 if (range.length == BOGUS_SIZE)
697 return CMD_ERROR;
698 args = nextarg + strspn(nextarg, whitespace);
699
700 if (*args != '\0') {
701 segflag = get_shared(args);
702 if (segflag == -1)
703 return CMD_ERROR;
704 }
705
706 if (!segment_register(SEGT_ANON, segname, &range, segflag))
707 return CMD_ERROR;
708
709 return CMD_SUCCESS;
710 }
711
712 /*
713 * file_seg: <path-name> [<offset>[kmgp] <length>[kmgp] [private|shared]]
714 */
file_seg(char * args)715 static int file_seg(char *args)
716 {
717 glctx_t *gcp = &glctx;
718
719 char *pathname, *nextarg;
720 range_t range = { 0L, 0L };
721 int segflag = MAP_PRIVATE;
722
723 args += strspn(args, whitespace);
724
725 if (!required_arg(args, "<path-name>"))
726 return CMD_ERROR;
727 pathname = strtok_r(args, whitespace, &nextarg);
728 args = nextarg + strspn(nextarg, whitespace);
729
730 /*
731 * offset, length are optional
732 */
733 if (get_range(args, &range, &nextarg) == CMD_ERROR)
734 return CMD_ERROR;
735 args = nextarg;
736
737 if (*args != '\0') {
738 segflag = get_shared(args);
739 if (segflag == -1)
740 return CMD_ERROR;
741 }
742
743 if (!segment_register(SEGT_FILE, pathname, &range, segflag))
744 return CMD_ERROR;
745
746 return CMD_SUCCESS;
747 }
748
749 /*
750 * remove_seg: <seg-name> [<seg-name> ...]
751 */
remove_seg(char * args)752 static int remove_seg(char *args)
753 {
754 glctx_t *gcp = &glctx;
755
756 args += strspn(args, whitespace);
757 if (!required_arg(args, "<seg-name>"))
758 return CMD_ERROR;
759
760 while (*args != '\0') {
761 char *segname, *nextarg;
762
763 segname = strtok_r(args, whitespace, &nextarg);
764 args = nextarg + strspn(nextarg, whitespace);
765
766 segment_remove(segname);
767 }
768 return 0;
769 }
770
771 /*
772 * touch_seg: <seg-name> [<offset> <length>] [read|write]
773 */
touch_seg(char * args)774 static int touch_seg(char *args)
775 {
776 glctx_t *gcp = &glctx;
777
778 char *segname, *nextarg;
779 range_t range = { 0L, 0L };
780 int axcs;
781
782 args += strspn(args, whitespace);
783 if (!required_arg(args, "<seg-name>"))
784 return CMD_ERROR;
785 segname = strtok_r(args, whitespace, &nextarg);
786 args = nextarg + strspn(nextarg, whitespace);
787
788 /*
789 * offset, length are optional
790 */
791 if (get_range(args, &range, &nextarg) == CMD_ERROR)
792 return CMD_ERROR;
793 args = nextarg;
794
795 axcs = get_access(args);
796 if (axcs == 0)
797 return CMD_ERROR;
798
799 if (!segment_touch(segname, &range, axcs - 1))
800 return CMD_ERROR;
801
802 return CMD_SUCCESS;
803 }
804
805 /*
806 * unmap <seg-name> - unmap specified segment, but remember name/size/...
807 */
unmap_seg(char * args)808 static int unmap_seg(char *args)
809 {
810 glctx_t *gcp = &glctx;
811 char *segname, *nextarg;
812
813 args += strspn(args, whitespace);
814 if (!required_arg(args, "<seg-name>"))
815 return CMD_ERROR;
816 segname = strtok_r(args, whitespace, &nextarg);
817 args = nextarg + strspn(nextarg, whitespace);
818
819 if (!segment_unmap(segname))
820 return CMD_ERROR;
821
822 return CMD_SUCCESS;
823 }
824
825 /*
826 * map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>]
827 */
map_seg(char * args)828 static int map_seg(char *args)
829 {
830 glctx_t *gcp = &glctx;
831
832 char *segname, *nextarg;
833 range_t range = { 0L, 0L };
834 range_t *rangep = NULL;
835 int segflag = MAP_PRIVATE;
836
837 args += strspn(args, whitespace);
838 if (!required_arg(args, "<seg-name>"))
839 return CMD_ERROR;
840 segname = strtok_r(args, whitespace, &nextarg);
841 args = nextarg + strspn(nextarg, whitespace);
842
843 /*
844 * offset, length are optional
845 */
846 if (get_range(args, &range, &nextarg) == CMD_ERROR)
847 return CMD_ERROR;
848 if (args != nextarg) {
849 rangep = ⦥ /* override any registered range */
850 args = nextarg;
851 }
852
853 if (*args != '\0') {
854 segflag = get_shared(args);
855 if (segflag == -1)
856 return CMD_ERROR;
857 }
858
859 if (!segment_map(segname, rangep, segflag))
860 return CMD_ERROR;
861
862 return CMD_SUCCESS;
863 }
864
865 /*
866 * mbind <seg-name> [<offset>[kmgp] <length>[kmgp]] <policy> <node-list>
867 */
mbind_seg(char * args)868 static int mbind_seg(char *args)
869 {
870 glctx_t *gcp = &glctx;
871
872 char *segname, *nextarg;
873 range_t range = { 0L, 0L };
874 nodemask_t *nodemask = NULL;
875 int policy, flags = 0;
876 int ret;
877
878 if (!numa_supported())
879 return CMD_ERROR;
880
881 args += strspn(args, whitespace);
882 if (!required_arg(args, "<seg-name>"))
883 return CMD_ERROR;
884 segname = strtok_r(args, whitespace, &nextarg);
885 args = nextarg + strspn(nextarg, whitespace);
886
887 /*
888 * offset, length are optional
889 */
890 if (get_range(args, &range, &nextarg) == CMD_ERROR)
891 return CMD_ERROR;
892 args = nextarg;
893
894 if (!required_arg(args, "<policy>"))
895 return CMD_ERROR;
896 policy = get_mbind_policy(args, &nextarg);
897 if (policy < 0)
898 return CMD_ERROR;
899
900 args = nextarg + strspn(nextarg, whitespace);
901 if (*args == '+') {
902 flags = get_mbind_flags(++args, &nextarg);
903 if (flags == -1)
904 return CMD_ERROR;
905 }
906 args = nextarg + strspn(nextarg, whitespace);
907
908 if (policy != MPOL_DEFAULT) {
909 if (!required_arg(args, "<node/list>"))
910 return CMD_ERROR;
911 nodemask = get_nodemask(args);
912 if (nodemask == NULL)
913 return CMD_ERROR;
914 }
915
916 ret = CMD_SUCCESS;
917 #if 1 // for testing
918 if (!segment_mbind(segname, &range, policy, nodemask, flags))
919 ret = CMD_ERROR;
920 #endif
921
922 if (nodemask != NULL)
923 free(nodemask);
924 return ret;
925 }
926
927 /*
928 * shmem_seg - create [shmget] and register a SysV shared memory segment
929 * of specified size
930 */
shmem_seg(char * args)931 static int shmem_seg(char *args)
932 {
933 glctx_t *gcp = &glctx;
934
935 char *segname, *nextarg;
936 range_t range = { 0L, 0L };
937
938 args += strspn(args, whitespace);
939
940 if (!required_arg(args, "<seg-name>"))
941 return CMD_ERROR;
942 segname = strtok_r(args, whitespace, &nextarg);
943 args = nextarg + strspn(nextarg, whitespace);
944
945 if (!required_arg(args, "<size>"))
946 return CMD_ERROR;
947 args = strtok_r(args, whitespace, &nextarg);
948 range.length = get_scaled_value(args, "size");
949 if (range.length == BOGUS_SIZE)
950 return CMD_ERROR;
951 args = nextarg + strspn(nextarg, whitespace);
952
953 if (!segment_register(SEGT_SHM, segname, &range, MAP_SHARED))
954 return CMD_ERROR;
955
956 return CMD_SUCCESS;
957 }
958
959 /*
960 * where <seg-name> [<offset>[kmgp] <length>[kmgp]] - show node location
961 * of specified range of segment.
962 *
963 * NOTE: if neither <offset> nor <length> specified, <offset> defaults
964 * to 0 [start of segment], as usual, and length defaults to 64 pages
965 * rather than the entire segment. Suitable for a "quick look" at where
966 * segment resides.
967 */
where_seg(char * args)968 static int where_seg(char *args)
969 {
970 glctx_t *gcp = &glctx;
971
972 char *segname, *nextarg;
973 range_t range = { 0L, 0L };
974 int ret;
975
976 if (!numa_supported())
977 return CMD_ERROR;
978
979 args += strspn(args, whitespace);
980 if (!required_arg(args, "<seg-name>"))
981 return CMD_ERROR;
982 segname = strtok_r(args, whitespace, &nextarg);
983 args = nextarg + strspn(nextarg, whitespace);
984
985 /*
986 * offset, length are optional
987 */
988 if (get_range(args, &range, &nextarg) == CMD_ERROR)
989 return CMD_ERROR;
990 if (args == nextarg)
991 range.length = 64 * gcp->pagesize; /* default length */
992
993 if (!segment_location(segname, &range))
994 return CMD_ERROR;
995
996 return CMD_SUCCESS;
997 }
998
999 #if 0
1000 static int command(char *args)
1001 {
1002 glctx_t *gcp = &glctx;
1003
1004 return CMD_SUCCESS;
1005 }
1006
1007 #endif
1008 /*
1009 * =========================================================================
1010 */
1011 typedef int (*cmd_func_t) (char *);
1012
1013 struct command {
1014 char *cmd_name;
1015 cmd_func_t cmd_func; /* */
1016 char *cmd_help;
1017
1018 } cmd_table[] = {
1019 {
1020 .cmd_name = "quit",.cmd_func = quit,.cmd_help =
1021 "quit - just what you think\n"
1022 "\tEOF on stdin has the same effect\n"}, {
1023 .cmd_name = "help",.cmd_func = help_me,.cmd_help =
1024 "help - show this help\n"
1025 "help <command> - display help for just <command>\n"}, {
1026 .cmd_name = "pid",.cmd_func = show_pid,.cmd_help =
1027 "pid - show process id of this session\n"}, {
1028 .cmd_name = "pause",.cmd_func = pause_me,.cmd_help =
1029 "pause - pause program until signal"
1030 " -- e.g., INT, USR1\n"}, {
1031 .cmd_name = "numa",.cmd_func = numa_info,.cmd_help =
1032 "numa - display numa info as seen by this program.\n"
1033 "\tshows nodes from which program may allocate memory\n"
1034 "\twith total and free memory.\n"}, {
1035 .cmd_name = "migrate",.cmd_func = migrate_process,.cmd_help =
1036 "migrate <to-node-id[s]> [<from-node-id[s]>] - \n"
1037 "\tmigrate this process' memory from <from-node-id[s]>\n"
1038 "\tto <to-node-id[s]>. Specify multiple node ids as a\n"
1039 "\tcomma-separated list. TODO - more info\n"}, {
1040 .cmd_name = "show",.cmd_func = show_seg,.cmd_help =
1041 "show [<name>] - show info for segment[s]; default all\n"},
1042 {
1043 .cmd_name = "anon",.cmd_func = anon_seg,.cmd_help =
1044 "anon <seg-name> <seg-size>[k|m|g|p] [<seg-share>] -\n"
1045 "\tdefine a MAP_ANONYMOUS segment of specified size\n"
1046 "\t<seg-share> := private|shared - default = private\n"}, {
1047 .cmd_name = "file",.cmd_func = file_seg,.cmd_help =
1048 "file <pathname> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] -\n"
1049 "\tdefine a mapped file segment of specified length starting at the\n"
1050 "\tspecified offset into the file. <offset> and <length> may be\n"
1051 "\tomitted and specified on the map command.\n"
1052 "\t<seg-share> := private|shared - default = private\n"}, {
1053 .cmd_name = "shm",.cmd_func = shmem_seg,.cmd_help =
1054 "shm <seg-name> <seg-size>[k|m|g|p] - \n"
1055 "\tdefine a shared memory segment of specified size.\n"
1056 "\tYou may need to increase limits [/proc/sys/kernel/shmmax].\n"
1057 "\tUse map/unmap to attach/detach\n"}, {
1058 .cmd_name = "remove",.cmd_func = remove_seg,.cmd_help =
1059 "remove <seg-name> [<seg-name> ...] - remove the named segment[s]\n"},
1060 {
1061 .cmd_name = "map",.cmd_func = map_seg,.cmd_help =
1062 "map <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [<seg-share>] - \n"
1063 "\tmmap()/shmat() a previously defined, currently unmapped() segment.\n"
1064 "\t<offset> and <length> apply only to mapped files.\n"
1065 "\tUse <length> of '*' or '0' to map to the end of the file.\n"},
1066 {
1067 .cmd_name = "unmap",.cmd_func = unmap_seg,.cmd_help =
1068 "unmap <seg-name> - unmap specified segment, but remember name/size/...\n"},
1069 {
1070 .cmd_name = "touch",.cmd_func = touch_seg,.cmd_help =
1071 "touch <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] [read|write] - \n"
1072 "\tread [default] or write the named segment from <offset> through\n"
1073 "\t<offset>+<length>. If <offset> and <length> omitted, touches all\n"
1074 "\t of mapped segment.\n"}, {
1075 .cmd_name = "mbind",.cmd_func = mbind_seg,.cmd_help =
1076 "mbind <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]]\n"
1077 " <policy>[+move[+wait]] [<node/list>] - \n"
1078 "\tset the numa policy for the specified range of the name segment\n"
1079 "\tto policy -- one of {default, bind, preferred, interleaved}.\n"
1080 "\t<node/list> specifies a node id or a comma separated list of\n"
1081 "\tnode ids. <node> is ignored for 'default' policy, and only\n"
1082 "\tthe first node is used for 'preferred' policy.\n"
1083 "\t'+move' specifies that currently allocated pages be prepared\n"
1084 "\t for migration on next touch\n"
1085 "\t'+wait' [valid only with +move] specifies that pages mbind()\n"
1086 " touch the pages and wait for migration before returning.\n"},
1087 {
1088 .cmd_name = "where",.cmd_func = where_seg,.cmd_help =
1089 "where <seg-name> [<offset>[k|m|g|p] <length>[k|m|g|p]] - \n"
1090 "\tshow the node location of pages in the specified range\n"
1091 "\tof the specified segment. <offset> defaults to start of\n"
1092 "\tsegment; <length> defaults to 64 pages.\n"},
1093 #if 0 /* template for new commands */
1094 {
1095 .cmd_name = "",.cmd_func =,.cmd_help =},
1096 #endif
1097 {
1098 .cmd_name = NULL}
1099 };
1100
help_me(char * args)1101 static int help_me(char *args)
1102 {
1103 struct command *cmdp = cmd_table;
1104 char *cmd, *nextarg;
1105 int cmdlen;
1106 bool match = false;
1107
1108 args += strspn(args, whitespace);
1109 if (*args != '\0') {
1110 cmd = strtok_r(args, whitespace, &nextarg);
1111 cmdlen = strlen(cmd);
1112 } else {
1113 cmd = NULL;
1114 cmdlen = 0;
1115 }
1116
1117 for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) {
1118 if (cmd == NULL || !strncmp(cmd, cmdp->cmd_name, cmdlen)) {
1119 printf("%s\n", cmdp->cmd_help);
1120 match = true;
1121 }
1122 }
1123
1124 if (!match) {
1125 printf("unrecognized command: %s\n", cmd);
1126 printf("\tuse 'help' for a complete list of commands\n");
1127 return CMD_ERROR;
1128 }
1129
1130 return CMD_SUCCESS;
1131 }
1132
1133 /*
1134 * =========================================================================
1135 */
1136 #define CMDBUFSZ 256
1137
unique_abbrev(char * cmd,size_t clen,struct command * cmdp)1138 static bool unique_abbrev(char *cmd, size_t clen, struct command *cmdp)
1139 {
1140 for (; cmdp->cmd_name != NULL; ++cmdp) {
1141 if (!strncmp(cmd, cmdp->cmd_name, clen))
1142 return false; /* match: not unique */
1143 }
1144 return true;
1145 }
1146
parse_command(char * cmdline)1147 static int parse_command(char *cmdline)
1148 {
1149 glctx_t *gcp = &glctx;
1150 char *cmd, *args;
1151 struct command *cmdp;
1152
1153 cmdline += strspn(cmdline, whitespace); /* possibly redundant */
1154
1155 cmd = strtok_r(cmdline, whitespace, &args);
1156
1157 for (cmdp = cmd_table; cmdp->cmd_name != NULL; ++cmdp) {
1158 size_t clen = strlen(cmd);
1159 int ret;
1160
1161 if (strncmp(cmd, cmdp->cmd_name, clen))
1162 continue;
1163 if (!unique_abbrev(cmd, clen, cmdp + 1)) {
1164 fprintf(stderr, "%s: ambiguous command: %s\n",
1165 gcp->program_name, cmd);
1166 return CMD_ERROR;
1167 }
1168 gcp->cmd_name = cmdp->cmd_name;
1169 ret = cmdp->cmd_func(args);
1170 gcp->cmd_name = NULL;
1171 return ret;
1172 }
1173
1174 fprintf(stderr, "%s: unrecognized command %s\n", __FUNCTION__, cmd);
1175 return CMD_ERROR;
1176 }
1177
process_commands()1178 void process_commands()
1179 {
1180 glctx_t *gcp = &glctx;
1181
1182 char cmdbuf[CMDBUFSZ];
1183
1184 do {
1185 char *cmdline;
1186 size_t cmdlen;
1187
1188 if (is_option(INTERACTIVE))
1189 printf("%s>", gcp->program_name);
1190
1191 cmdline = fgets(cmdbuf, CMDBUFSZ, stdin);
1192 if (cmdline == NULL) {
1193 printf("%s\n",
1194 is_option(INTERACTIVE) ? "" : "EOF on stdin");
1195 exit(0); /* EOF */
1196 }
1197 if (cmdline[0] == '\n')
1198 continue;
1199
1200 /*
1201 * trim trailing newline, if any
1202 */
1203 cmdlen = strlen(cmdline);
1204 if (cmdline[cmdlen - 1] == '\n')
1205 cmdline[--cmdlen] = '\0';
1206
1207 cmdline += strspn(cmdline, whitespace);
1208 cmdlen -= (cmdline - cmdbuf);
1209
1210 if (cmdlen == 0) {
1211 //TODO: interactive help?
1212 continue; /* ignore blank lines */
1213 }
1214
1215 if (*cmdline == '#')
1216 continue; /* comments */
1217
1218 /*
1219 * trim trailing whitespace for ease of parsing
1220 */
1221 while (strchr(whitespace, cmdline[cmdlen - 1]))
1222 cmdline[--cmdlen] = '\0';
1223
1224 if (cmdlen == 0)
1225 continue;
1226
1227 /*
1228 * reset signals just before parsing a command.
1229 * non-interactive: exit on SIGQUIT
1230 */
1231 if (signalled(gcp)) {
1232 if (!is_option(INTERACTIVE) &&
1233 gcp->siginfo->si_signo == SIGQUIT)
1234 exit(0);
1235 reset_signal();
1236 }
1237
1238 /*
1239 * non-interactive: errors are fatal
1240 */
1241 if (!is_option(INTERACTIVE)) {
1242 vprint("%s>%s\n", gcp->program_name, cmdline);
1243 if (parse_command(cmdline) == CMD_ERROR) {
1244 fprintf(stderr, "%s: command error\n",
1245 gcp->program_name);
1246 exit(4);
1247 }
1248 } else
1249 parse_command(cmdline);
1250
1251 } while (1);
1252 }
1253 #endif
1254