/*************************************************************************** * User front end for using huge pages Copyright (C) 2008, IBM * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as * * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * * * You should have received a copy of the Lesser 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. * ***************************************************************************/ /* * hugectl is inspired by numactl as a single front end to a large number of * options for controlling a very specific environment. Eventually it will * have support for controlling the all of the environment variables for * libhugetlbfs, but options will only be added after they have been in the * library for some time and are throughly tested and stable. * * This program should be treated as an ABI for using libhugetlbfs. */ #include #include #include #include #include #define _GNU_SOURCE /* for getopt_long */ #include #include #define REPORT(level, prefix, format, ...) \ do { \ if (verbose_level >= level) \ fprintf(stderr, "hugectl: " prefix ": " format, \ ##__VA_ARGS__); \ } while (0); #include "libhugetlbfs_debug.h" extern int errno; extern int optind; extern char *optarg; #define OPTION(opts, text) fprintf(stderr, " %-25s %s\n", opts, text) #define CONT(text) fprintf(stderr, " %-25s %s\n", "", text) void print_usage() { fprintf(stderr, "hugectl [options] target\n"); fprintf(stderr, "options:\n"); OPTION("--help, -h", "Prints this message"); OPTION("--verbose , -v", "Increases/sets tracing levels"); OPTION("--text[=]", "Requests remapping of the program text"); OPTION("--data[=]", "Requests remapping of the program data"); OPTION("--bss[=]", "Requests remapping of the program bss"); OPTION("--heap[=]", "Requests remapping of the program heap"); CONT("(malloc space)"); OPTION("--shm", "Requests remapping of shared memory segments"); OPTION("--thp", "Setup the heap space to be aligned for merging"); CONT("by khugepaged into huge pages. This requires"); CONT("kernel support for transparent huge pages to be"); CONT("enabled"); OPTION("--no-preload", "Disable preloading the libhugetlbfs library"); OPTION("--no-reserve", "Disable huge page reservation for segments"); OPTION("--force-preload", "Force preloading the libhugetlbfs library"); OPTION("--dry-run", "describe what would be done without doing it"); OPTION("--library-use-path", "Use the system library path"); OPTION("--share-text", "Share text segments between multiple"); CONT("application instances"); OPTION("--library-path ", "Select a library prefix"); CONT("(Default: " #ifdef LIBDIR32 LIBDIR32 ":" #endif #ifdef LIBDIR32 LIBDIR32 ":" #endif ")"); } int opt_dry_run = 0; int opt_force_preload = 0; int verbose_level = VERBOSITY_DEFAULT; void verbose_init(void) { char *env; env = getenv("HUGETLB_VERBOSE"); if (env) verbose_level = atoi(env); env = getenv("HUGETLB_DEBUG"); if (env) verbose_level = VERBOSITY_MAX; } void verbose(char *which) { int new_level; if (which) { new_level = atoi(which); if (new_level < 0 || new_level > 99) { ERROR("%d: verbosity out of range 0-99\n", new_level); exit(EXIT_FAILURE); } } else { new_level = verbose_level + 1; if (new_level == 100) { WARNING("verbosity limited to 99\n"); new_level--; } } verbose_level = new_level; } void quiet(void) { int new_level = verbose_level - 1; if (new_level < 0) { WARNING("verbosity must be at least 0\n"); new_level = 0; } verbose_level = new_level; } void setup_environment(char *var, char *val) { setenv(var, val, 1); INFO("%s='%s'\n", var, val); if (opt_dry_run) printf("%s='%s'\n", var, val); } void verbose_expose(void) { char level[3]; if (verbose_level == 99) { setup_environment("HUGETLB_DEBUG", "yes"); } snprintf(level, sizeof(level), "%d", verbose_level); setup_environment("HUGETLB_VERBOSE", level); } /* * getopts return values for options which are long only. */ #define MAP_BASE 0x1000 #define LONG_BASE 0x2000 #define LONG_NO_PRELOAD (LONG_BASE | 'p') #define LONG_NO_RESERVE (LONG_BASE | 'r') #define LONG_FORCE_PRELOAD (LONG_BASE | 'F') #define LONG_DRY_RUN (LONG_BASE | 'd') #define LONG_SHARE (LONG_BASE | 's') #define LONG_NO_LIBRARY (LONG_BASE | 'L') #define LONG_LIBRARY (LONG_BASE | 'l') #define LONG_THP_HEAP ('t') /* * Mapping selectors, one per remappable/backable area as requested * by the user. These are also used as returns from getopts where they * are offset from MAP_BASE, which must be removed before they are compared. */ enum { MAP_TEXT, MAP_DATA, MAP_BSS, MAP_HEAP, MAP_SHM, MAP_DISABLE, MAP_COUNT, }; char *map_size[MAP_COUNT]; char default_size[] = "the default hugepage size"; #define DEFAULT_SIZE default_size #define available(buf, ptr) ((int)(sizeof(buf) - (ptr - buf))) void setup_mappings(int count) { char value[128]; char *ptr = value; int needed; /* * HUGETLB_ELFMAP should be set to either a combination of 'R' and 'W' * which indicate which segments should be remapped. Each may take * an optional page size. It may also be set to 'no' to prevent * remapping. */ /* * Accumulate sections each with a ':' prefix to simplify later * handling. We will elide the initial ':' before use. */ if (map_size[MAP_TEXT]) { if (map_size[MAP_TEXT] == DEFAULT_SIZE) needed = snprintf(ptr, available(value, ptr), ":R"); else needed = snprintf(ptr, available(value, ptr), ":R=%s", map_size[MAP_TEXT]); ptr += needed; if (needed < 0 || available(value, ptr) < 0) { ERROR("%s: bad size specification\n", map_size[MAP_TEXT]); exit(EXIT_FAILURE); } } if (map_size[MAP_DATA] != 0 || map_size[MAP_BSS] != 0) { char *size = map_size[MAP_BSS]; if (map_size[MAP_DATA]) size = map_size[MAP_DATA]; if (map_size[MAP_DATA] != map_size[MAP_BSS]) WARNING("data and bss remapped together in %s\n", size); if (size == DEFAULT_SIZE) needed = snprintf(ptr, available(value, ptr), ":W"); else needed = snprintf(ptr, available(value, ptr), ":W=%s", size); ptr += needed; if (needed < 0 || available(value, ptr) < 0) { ERROR("%s: bad size specification\n", size); exit(EXIT_FAILURE); } } *ptr = '\0'; if (ptr != value) setup_environment("HUGETLB_ELFMAP", &value[1]); if (map_size[MAP_DISABLE]) { if (ptr != value) WARNING("--disable masks requested remap\n"); setup_environment("HUGETLB_ELFMAP", "no"); } if (map_size[MAP_HEAP] == DEFAULT_SIZE) setup_environment("HUGETLB_MORECORE", "yes"); else if (map_size[MAP_HEAP]) setup_environment("HUGETLB_MORECORE", map_size[MAP_HEAP]); if (map_size[MAP_SHM] && map_size[MAP_SHM] != DEFAULT_SIZE) WARNING("shm segments may only be mapped in the " "default hugepage size\n"); if (map_size[MAP_SHM]) setup_environment("HUGETLB_SHM", "yes"); } #define LIBRARY_DISABLE ((void *)-1) void library_path(char *path) { char val[PATH_MAX] = ""; char *env; env = getenv("LD_LIBRARY_PATH"); /* * Select which libraries we wish to use. If the path is NULL * use the libraries included with hugectl. If the path is valid * and points to a directory including a libhugetlbfs.so use it * directly. Else path is assumed to be a prefix to the 32/64 bit * directories both of which are added, where available. */ if (path) { snprintf(val, sizeof(val), "%s/libhugetlbfs.so", path); if (access(val, F_OK) == 0) { /* $PATH */ snprintf(val, sizeof(val), "%s:%s", path, env ? env : ""); } else { /* [$PATH/LIB32:][$PATH/LIB64:]$LD_LIBRARY_PATH */ snprintf(val, sizeof(val), "" #ifdef LIBDIR32 "%s/" LIB32 ":" #endif #ifdef LIBDIR64 "%s/" LIB64 ":" #endif "%s", #ifdef LIBDIR32 path, #endif #ifdef LIBDIR64 path, #endif env ? env : ""); } } else { /* [LIBDIR32:][LIBDIR64:]$LD_LIBRARY_PATH */ snprintf(val, sizeof(val), "" #ifdef LIBDIR32 LIBDIR32 ":" #endif #ifdef LIBDIR64 LIBDIR64 ":" #endif "%s", env ? env : ""); } setup_environment("LD_LIBRARY_PATH", val); } void ldpreload(int count) { int allowed = 0; if (map_size[MAP_HEAP]) allowed++; if (map_size[MAP_SHM]) allowed++; if ((allowed == count) || opt_force_preload) { setup_environment("LD_PRELOAD", "libhugetlbfs.so"); if (allowed == count) INFO("LD_PRELOAD in use for lone --heap/--shm\n"); } else { WARNING("LD_PRELOAD not appropriate for this map combination\n"); } } int main(int argc, char** argv) { int opt_mappings = 0; int opt_preload = 1; int opt_no_reserve = 0; int opt_share = 0; int opt_thp_heap = 0; char *opt_library = NULL; char opts[] = "+hvq"; int ret = 0, index = 0; struct option long_opts[] = { {"help", no_argument, NULL, 'h'}, {"verbose", required_argument, NULL, 'v' }, {"no-preload", no_argument, NULL, LONG_NO_PRELOAD}, {"no-reserve", no_argument, NULL, LONG_NO_RESERVE}, {"force-preload", no_argument, NULL, LONG_FORCE_PRELOAD}, {"dry-run", no_argument, NULL, LONG_DRY_RUN}, {"library-path", required_argument, NULL, LONG_LIBRARY}, {"library-use-path", no_argument, NULL, LONG_NO_LIBRARY}, {"share-text", no_argument, NULL, LONG_SHARE}, {"disable", optional_argument, NULL, MAP_BASE|MAP_DISABLE}, {"text", optional_argument, NULL, MAP_BASE|MAP_TEXT}, {"data", optional_argument, NULL, MAP_BASE|MAP_DATA}, {"bss", optional_argument, NULL, MAP_BASE|MAP_BSS}, {"heap", optional_argument, NULL, MAP_BASE|MAP_HEAP}, {"shm", optional_argument, NULL, MAP_BASE|MAP_SHM}, {"thp", no_argument, NULL, LONG_THP_HEAP}, {0}, }; verbose_init(); while (ret != -1) { ret = getopt_long(argc, argv, opts, long_opts, &index); if (ret > 0 && (ret & MAP_BASE)) { if (optarg) map_size[ret & ~MAP_BASE] = optarg; else map_size[ret & ~MAP_BASE] = DEFAULT_SIZE; opt_mappings++; continue; } switch (ret) { case '?': print_usage(); exit(EXIT_FAILURE); case 'h': print_usage(); exit(EXIT_SUCCESS); case 'v': verbose(optarg); break; case 'q': quiet(); break; case LONG_THP_HEAP: opt_thp_heap = 1; INFO("Aligning heap for use with THP\n"); break; case LONG_NO_PRELOAD: opt_preload = 0; INFO("LD_PRELOAD disabled\n"); break; case LONG_NO_RESERVE: opt_no_reserve = 1; INFO("MAP_NORESERVE used for huge page mappings\n"); break; case LONG_FORCE_PRELOAD: opt_preload = 1; opt_force_preload = 1; INFO("Forcing ld preload\n"); break; case LONG_DRY_RUN: opt_dry_run = 1; break; case LONG_NO_LIBRARY: opt_library = LIBRARY_DISABLE; INFO("using LD_LIBRARY_PATH to find library\n"); break; case LONG_LIBRARY: opt_library = optarg; break; case LONG_SHARE: opt_share = 1; break; case -1: break; default: WARNING("unparsed option %08x\n", ret); ret = -1; break; } } index = optind; if (!opt_dry_run && (argc - index) < 1) { print_usage(); exit(EXIT_FAILURE); } verbose_expose(); if (opt_library != LIBRARY_DISABLE) library_path(opt_library); if (opt_mappings) setup_mappings(opt_mappings); if (opt_preload) ldpreload(opt_mappings); if (opt_no_reserve) setup_environment("HUGETLB_NO_RESERVE", "yes"); if (opt_share) setup_environment("HUGETLB_SHARE", "1"); if (opt_thp_heap) setup_environment("HUGETLB_MORECORE", "thp"); if (opt_dry_run) exit(EXIT_SUCCESS); execvp(argv[index], &argv[index]); ERROR("exec failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); }