|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* libhugetlbfs - Easy use of Linux hugepages
|
|
Packit |
2d622a |
* Copyright (C) 2005-2006 David Gibson & Adam Litke, IBM Corporation.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* This library is free software; you can redistribute it and/or
|
|
Packit |
2d622a |
* modify it under the terms of the GNU Lesser General Public License
|
|
Packit |
2d622a |
* as published by the Free Software Foundation; either version 2.1 of
|
|
Packit |
2d622a |
* the License, or (at your option) any later version.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* This library is distributed in the hope that it will be useful, but
|
|
Packit |
2d622a |
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
2d622a |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
2d622a |
* Lesser General Public License for more details.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* You should have received a copy of the GNU Lesser General Public
|
|
Packit |
2d622a |
* License along with this library; if not, write to the Free Software
|
|
Packit |
2d622a |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define _LARGEFILE64_SOURCE /* Need this for statfs64 */
|
|
Packit |
2d622a |
#define _GNU_SOURCE
|
|
Packit |
2d622a |
#include <dlfcn.h>
|
|
Packit |
2d622a |
#include <features.h>
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#include <stdio.h>
|
|
Packit |
2d622a |
#include <stdlib.h>
|
|
Packit |
2d622a |
#include <stdint.h>
|
|
Packit |
2d622a |
#include <errno.h>
|
|
Packit |
2d622a |
#include <limits.h>
|
|
Packit |
2d622a |
#include <string.h>
|
|
Packit |
2d622a |
#include <ctype.h>
|
|
Packit |
2d622a |
#include <signal.h>
|
|
Packit |
2d622a |
#include <dirent.h>
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#include <unistd.h>
|
|
Packit |
2d622a |
#include <fcntl.h>
|
|
Packit |
2d622a |
#include <sys/vfs.h>
|
|
Packit |
2d622a |
#include <sys/statfs.h>
|
|
Packit |
2d622a |
#include <sys/types.h>
|
|
Packit |
2d622a |
#include <sys/mman.h>
|
|
Packit |
2d622a |
#include <sys/file.h>
|
|
Packit |
2d622a |
#include <sys/uio.h>
|
|
Packit |
2d622a |
#include <sys/syscall.h>
|
|
Packit |
2d622a |
#include <linux/types.h>
|
|
Packit |
2d622a |
#include <linux/unistd.h>
|
|
Packit |
2d622a |
#include <dirent.h>
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#include "libhugetlbfs_internal.h"
|
|
Packit |
2d622a |
#include "hugetlbfs.h"
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
struct libhugeopts_t __hugetlb_opts;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
static int hugepagesize_errno; /* = 0 */
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define MAX_HPAGE_SIZES 10
|
|
Packit |
2d622a |
static struct hpage_size hpage_sizes[MAX_HPAGE_SIZES];
|
|
Packit |
2d622a |
static int nr_hpage_sizes;
|
|
Packit |
2d622a |
static int hpage_sizes_default_idx = -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
static long default_size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
/* Internal functions */
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Lookup the kernel default page size.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
long kernel_default_hugepage_size()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
if (default_size == 0) {
|
|
Packit |
2d622a |
default_size = file_read_ulong(MEMINFO, "Hugepagesize:");
|
|
Packit |
2d622a |
default_size = size_to_smaller_unit(default_size); /* kB to B */ }
|
|
Packit |
2d622a |
return default_size;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
void kernel_default_hugepage_size_reset(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
default_size = 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define BUF_SZ 256
|
|
Packit |
2d622a |
#define MEMINFO_SIZE 2048
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Convert a quantity in a given unit to the next smallest unit by
|
|
Packit |
2d622a |
* multiplying the quantity by 1024 (eg. convert 1MB to 1024kB).
|
|
Packit |
2d622a |
* If the conversion would overflow the variable, return ULONGLONG_MAX to
|
|
Packit |
2d622a |
* signify the error.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
unsigned long long size_to_smaller_unit(unsigned long long size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
if (size * 1024 < size)
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
else
|
|
Packit |
2d622a |
return size * 1024;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Convert a page size string with an optional unit suffix into a page size
|
|
Packit |
2d622a |
* in bytes.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* On error, -1 is returned and errno is set appropriately:
|
|
Packit |
2d622a |
* EINVAL - str could not be parsed or was not greater than zero
|
|
Packit |
2d622a |
* EOVERFLOW - Overflow when converting from the specified units
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
long parse_page_size(const char *str)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char *pos;
|
|
Packit |
2d622a |
long size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
errno = 0;
|
|
Packit |
2d622a |
size = strtol(str, &pos, 0);
|
|
Packit |
2d622a |
/* Catch strtoul errors and sizes that overflow the native word size */
|
|
Packit |
2d622a |
if (errno || str == pos || size <= 0) {
|
|
Packit |
2d622a |
if (errno == ERANGE)
|
|
Packit |
2d622a |
errno = EOVERFLOW;
|
|
Packit |
2d622a |
else
|
|
Packit |
2d622a |
errno = EINVAL;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
switch (*pos) {
|
|
Packit |
2d622a |
case 'G':
|
|
Packit |
2d622a |
case 'g':
|
|
Packit |
2d622a |
size = size_to_smaller_unit(size);
|
|
Packit |
2d622a |
case 'M':
|
|
Packit |
2d622a |
case 'm':
|
|
Packit |
2d622a |
size = size_to_smaller_unit(size);
|
|
Packit |
2d622a |
case 'K':
|
|
Packit |
2d622a |
case 'k':
|
|
Packit |
2d622a |
size = size_to_smaller_unit(size);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (size < 0)
|
|
Packit |
2d622a |
errno = EOVERFLOW;
|
|
Packit |
2d622a |
return size;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
struct hugetlb_pool_counter_info_t {
|
|
Packit |
2d622a |
char *meminfo_key;
|
|
Packit |
2d622a |
char *sysfs_file;
|
|
Packit |
2d622a |
};
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
static struct hugetlb_pool_counter_info_t hugetlb_counter_info[] = {
|
|
Packit |
2d622a |
[HUGEPAGES_TOTAL] = {
|
|
Packit |
2d622a |
.meminfo_key = "HugePages_Total:",
|
|
Packit |
2d622a |
.sysfs_file = "nr_hugepages",
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
[HUGEPAGES_TOTAL_MEMPOL] = {
|
|
Packit |
2d622a |
.meminfo_key = "HugePages_Total:",
|
|
Packit |
2d622a |
.sysfs_file = "nr_hugepages_mempolicy",
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
[HUGEPAGES_FREE] = {
|
|
Packit |
2d622a |
.meminfo_key = "HugePages_Free:",
|
|
Packit |
2d622a |
.sysfs_file = "free_hugepages",
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
[HUGEPAGES_RSVD] = {
|
|
Packit |
2d622a |
.meminfo_key = "HugePages_Rsvd:",
|
|
Packit |
2d622a |
.sysfs_file = "resv_hugepages",
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
[HUGEPAGES_SURP] = {
|
|
Packit |
2d622a |
.meminfo_key = "HugePages_Surp:",
|
|
Packit |
2d622a |
.sysfs_file = "surplus_hugepages",
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
[HUGEPAGES_OC] = {
|
|
Packit |
2d622a |
.meminfo_key = NULL,
|
|
Packit |
2d622a |
.sysfs_file = "nr_overcommit_hugepages"
|
|
Packit |
2d622a |
},
|
|
Packit |
2d622a |
};
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Read numeric data from raw and tagged kernel status files. Used to read
|
|
Packit |
2d622a |
* /proc and /sys data (without a tag) and from /proc/meminfo (with a tag).
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
long file_read_ulong(char *file, const char *tag)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int fd;
|
|
Packit |
2d622a |
char buf[MEMINFO_SIZE];
|
|
Packit |
2d622a |
int len, readerr;
|
|
Packit |
2d622a |
char *p, *q;
|
|
Packit |
2d622a |
long val;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
fd = open(file, O_RDONLY);
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
ERROR("Couldn't open %s: %s\n", file, strerror(errno));
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
len = read(fd, buf, sizeof(buf));
|
|
Packit |
2d622a |
readerr = errno;
|
|
Packit |
2d622a |
close(fd);
|
|
Packit |
2d622a |
if (len < 0) {
|
|
Packit |
2d622a |
ERROR("Error reading %s: %s\n", file, strerror(readerr));
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (len == sizeof(buf)) {
|
|
Packit |
2d622a |
ERROR("%s is too large\n", file);
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
buf[len] = '\0';
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Search for a tag if provided */
|
|
Packit |
2d622a |
if (tag) {
|
|
Packit |
2d622a |
p = strstr(buf, tag);
|
|
Packit |
2d622a |
if (!p)
|
|
Packit |
2d622a |
return -1; /* looks like the line we want isn't there */
|
|
Packit |
2d622a |
p += strlen(tag);
|
|
Packit |
2d622a |
} else
|
|
Packit |
2d622a |
p = buf;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
val = strtol(p, &q, 0);
|
|
Packit |
2d622a |
if (! isspace(*q)) {
|
|
Packit |
2d622a |
ERROR("Couldn't parse %s value\n", file);
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return val;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int file_write_ulong(char *file, unsigned long val)
|
|
Packit |
2d622a |
{
|
|
Packit |
ddf69d |
int fd, ret, buflen;
|
|
Packit |
ddf69d |
char buf[20];
|
|
Packit |
2d622a |
|
|
Packit |
ddf69d |
fd = open(file, O_WRONLY);
|
|
Packit |
ddf69d |
if (fd < 0) {
|
|
Packit |
2d622a |
ERROR("Couldn't open %s: %s\n", file, strerror(errno));
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
ddf69d |
buflen = sprintf(buf, "%lu", val);
|
|
Packit |
ddf69d |
ret = write(fd, buf, buflen);
|
|
Packit |
ddf69d |
close(fd);
|
|
Packit |
2d622a |
return ret > 0 ? 0 : -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Return the name of this executable, using buf as temporary space.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
#define MAX_EXE 4096
|
|
Packit |
2d622a |
static char *get_exe_name(char *buf, int size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char *p;
|
|
Packit |
2d622a |
int fd;
|
|
Packit |
2d622a |
ssize_t nread;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
buf[0] = 0;
|
|
Packit |
2d622a |
fd = open("/proc/self/cmdline", O_RDONLY);
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
WARNING("Unable to open cmdline, no exe name\n");
|
|
Packit |
2d622a |
return buf;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
nread = read(fd, buf, size-1);
|
|
Packit |
2d622a |
close(fd);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (nread < 0) {
|
|
Packit |
2d622a |
WARNING("Error %d reading cmdline, no exe name\n", errno);
|
|
Packit |
2d622a |
return buf;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (nread == 0) {
|
|
Packit |
2d622a |
WARNING("Read zero bytes from cmdline, no exe name\n");
|
|
Packit |
2d622a |
return buf;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
buf[nread] = 0; /* make sure we're null terminated */
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Take advantage of cmdline being a series of null-terminated
|
|
Packit |
2d622a |
* strings. The first string is the path to the executable in
|
|
Packit |
2d622a |
* the form:
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* /path/to/exe
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* The exe name starts one character after the last '/'.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
p = strrchr(buf, '/');
|
|
Packit |
2d622a |
if (!p)
|
|
Packit |
2d622a |
return buf;
|
|
Packit |
2d622a |
return p + 1; /* skip over "/" */
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Reads the contents of hugetlb environment variables and save their
|
|
Packit |
2d622a |
* values for later use.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
void hugetlbfs_setup_env()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char *env;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
__hugetlb_opts.min_copy = true;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_VERBOSE");
|
|
Packit |
2d622a |
if (env)
|
|
Packit |
2d622a |
__hugetlbfs_verbose = atoi(env);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_DEBUG");
|
|
Packit |
2d622a |
if (env) {
|
|
Packit |
2d622a |
__hugetlbfs_debug = true;
|
|
Packit |
2d622a |
__hugetlbfs_verbose = VERBOSE_DEBUG;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_RESTRICT_EXE");
|
|
Packit |
2d622a |
if (env) {
|
|
Packit |
2d622a |
char *p, *tok, *exe, buf[MAX_EXE+1], restriction[MAX_EXE];
|
|
Packit |
2d622a |
int found = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
exe = get_exe_name(buf, sizeof buf);
|
|
Packit |
2d622a |
DEBUG("Found HUGETLB_RESTRICT_EXE, this exe is \"%s\"\n", exe);
|
|
Packit |
2d622a |
strncpy(restriction, env, sizeof restriction);
|
|
Packit |
2d622a |
restriction[sizeof(restriction)-1] = 0;
|
|
Packit |
2d622a |
for (p = restriction; (tok = strtok(p, ":")) != NULL; p = NULL) {
|
|
Packit |
2d622a |
DEBUG(" ...check exe match for \"%s\"\n", tok);
|
|
Packit |
2d622a |
if (strcmp(tok, exe) == 0) {
|
|
Packit |
2d622a |
found = 1;
|
|
Packit |
2d622a |
DEBUG("exe match - libhugetlbfs is active for this exe\n");
|
|
Packit |
2d622a |
break;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (!found) {
|
|
Packit |
2d622a |
DEBUG("No exe match - libhugetlbfs is inactive for this exe\n");
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_NO_PREFAULT");
|
|
Packit |
2d622a |
if (env)
|
|
Packit |
2d622a |
__hugetlbfs_prefault = false;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
__hugetlb_opts.share_path = getenv("HUGETLB_SHARE_PATH");
|
|
Packit |
2d622a |
__hugetlb_opts.elfmap = getenv("HUGETLB_ELFMAP");
|
|
Packit |
2d622a |
__hugetlb_opts.ld_preload = getenv("LD_PRELOAD");
|
|
Packit |
2d622a |
__hugetlb_opts.def_page_size = getenv("HUGETLB_DEFAULT_PAGE_SIZE");
|
|
Packit |
2d622a |
__hugetlb_opts.path = getenv("HUGETLB_PATH");
|
|
Packit |
2d622a |
__hugetlb_opts.features = getenv("HUGETLB_FEATURES");
|
|
Packit |
2d622a |
__hugetlb_opts.morecore = getenv("HUGETLB_MORECORE");
|
|
Packit |
2d622a |
__hugetlb_opts.heapbase = getenv("HUGETLB_MORECORE_HEAPBASE");
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (__hugetlb_opts.morecore)
|
|
Packit |
2d622a |
__hugetlb_opts.thp_morecore =
|
|
Packit |
2d622a |
(strcasecmp(__hugetlb_opts.morecore, "thp") == 0);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (__hugetlb_opts.thp_morecore && __hugetlb_opts.heapbase) {
|
|
Packit |
2d622a |
DEBUG("Heapbase specified with THP for morecore, ignoring heapbase\n");
|
|
Packit |
2d622a |
__hugetlb_opts.heapbase = NULL;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_FORCE_ELFMAP");
|
|
Packit |
2d622a |
if (env && (strcasecmp(env, "yes") == 0))
|
|
Packit |
2d622a |
__hugetlb_opts.force_elfmap = 1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_MINIMAL_COPY");
|
|
Packit |
2d622a |
if (__hugetlb_opts.min_copy && env && (strcasecmp(env, "no") == 0)) {
|
|
Packit |
2d622a |
INFO("HUGETLB_MINIMAL_COPY=%s, disabling filesz copy "
|
|
Packit |
2d622a |
"optimization\n", env);
|
|
Packit |
2d622a |
__hugetlb_opts.min_copy = false;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
env = getenv("HUGETLB_SHARE");
|
|
Packit |
2d622a |
if (env)
|
|
Packit |
2d622a |
__hugetlb_opts.sharing = atoi(env);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* We have been seeing some unexpected behavior from malloc when
|
|
Packit |
2d622a |
* heap shrinking is enabled, so heap shrinking is disabled by
|
|
Packit |
2d622a |
* default.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* If malloc has been called successfully before setup_morecore,
|
|
Packit |
2d622a |
* glibc will notice a gap between the previous top-of-heap and
|
|
Packit |
2d622a |
* the new top-of-heap when it calls hugetlbfs_morecore. It treats
|
|
Packit |
2d622a |
* this as a "foreign sbrk." Unfortunately, the "foreign sbrk"
|
|
Packit |
2d622a |
* handling code will then immediately try to free the memory
|
|
Packit |
2d622a |
* allocated by hugetlbfs_morecore!
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* This behavior has been reported to the ptmalloc2 maintainer,
|
|
Packit |
2d622a |
* along with a patch to correct the behavior.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
env = getenv("HUGETLB_MORECORE_SHRINK");
|
|
Packit |
2d622a |
if (env && strcasecmp(env, "yes") == 0)
|
|
Packit |
2d622a |
__hugetlb_opts.shrink_ok = true;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Determine if shmget() calls should be overridden */
|
|
Packit |
2d622a |
env = getenv("HUGETLB_SHM");
|
|
Packit |
2d622a |
if (env && !strcasecmp(env, "yes"))
|
|
Packit |
2d622a |
__hugetlb_opts.shm_enabled = true;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Determine if all reservations should be avoided */
|
|
Packit |
2d622a |
env = getenv("HUGETLB_NO_RESERVE");
|
|
Packit |
2d622a |
if (env && !strcasecmp(env, "yes"))
|
|
Packit |
2d622a |
__hugetlb_opts.no_reserve = true;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void hugetlbfs_setup_kernel_page_size()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long page_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (page_size <= 0) {
|
|
Packit |
2d622a |
WARNING("Unable to find default kernel huge page size\n");
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
INFO("Found pagesize %ld kB\n", page_size / 1024);
|
|
Packit |
2d622a |
hpage_sizes[0].pagesize = page_size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
nr_hpage_sizes = 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void hugetlbfs_check_priv_resv()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* If the kernel supports MAP_PRIVATE reservations, we can skip
|
|
Packit |
2d622a |
* prefaulting the huge pages we allocate since the kernel
|
|
Packit |
2d622a |
* guarantees them. This can help NUMA performance quite a bit.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
if (hugetlbfs_test_feature(HUGETLB_FEATURE_PRIVATE_RESV) > 0) {
|
|
Packit |
2d622a |
INFO("Kernel has MAP_PRIVATE reservations. Disabling "
|
|
Packit |
2d622a |
"heap prefaulting.\n");
|
|
Packit |
2d622a |
__hugetlbfs_prefault = false;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void hugetlbfs_check_safe_noreserve()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Some kernels will trigger an OOM if MAP_NORESERVE is used and
|
|
Packit |
2d622a |
* a huge page allocation fails. This is unfortunate so limit
|
|
Packit |
2d622a |
* the user of NORESERVE where necessary
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
if (__hugetlb_opts.no_reserve &&
|
|
Packit |
2d622a |
hugetlbfs_test_feature(HUGETLB_FEATURE_SAFE_NORESERVE) <= 0) {
|
|
Packit |
2d622a |
INFO("Kernel is not safe for MAP_NORESERVE. Forcing "
|
|
Packit |
2d622a |
"use of reservations.\n");
|
|
Packit |
2d622a |
__hugetlb_opts.no_reserve = false;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void hugetlbfs_check_map_hugetlb()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* FIXME: MAP_HUGETLB has not been picked up by glibc so even though the
|
|
Packit |
2d622a |
* kernel may support it, without the userspace mmap flag it cannot be
|
|
Packit |
2d622a |
* used. This ifdef should be removed when the MAP_HUGETLB flag makes it
|
|
Packit |
2d622a |
* into glibc.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
#ifdef MAP_HUGETLB
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Kernels after 2.6.32 support mmaping pseudo-anonymous regions
|
|
Packit |
2d622a |
* backed by huge pages, use this feature for huge pages we
|
|
Packit |
2d622a |
* don't intend to share.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
if (hugetlbfs_test_feature(HUGETLB_FEATURE_MAP_HUGETLB) > 0) {
|
|
Packit |
2d622a |
INFO("Kernel supports MAP_HUGETLB\n");
|
|
Packit |
2d622a |
__hugetlb_opts.map_hugetlb = true;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
#endif
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Pool counters are typically exposed in sysfs in modern kernels, the
|
|
Packit |
2d622a |
* counters for the default page size are exposed in procfs in all kernels
|
|
Packit |
2d622a |
* supporting hugepages. Given a specific counter (e.g. HUGEPAGES_RSVD)
|
|
Packit |
2d622a |
* and a page size return both a filename and an optional tag to locate
|
|
Packit |
2d622a |
* and extract this counter.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
static int select_pool_counter(unsigned int counter, unsigned long pagesize,
|
|
Packit |
2d622a |
char *filename, char **key)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long default_size;
|
|
Packit |
2d622a |
char *meminfo_key;
|
|
Packit |
2d622a |
char *sysfs_file;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (counter >= HUGEPAGES_MAX_COUNTERS) {
|
|
Packit |
2d622a |
ERROR("Invalid counter specified\n");
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
meminfo_key = hugetlb_counter_info[counter].meminfo_key;
|
|
Packit |
2d622a |
sysfs_file = hugetlb_counter_info[counter].sysfs_file;
|
|
Packit |
2d622a |
if (key)
|
|
Packit |
2d622a |
*key = NULL;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Get the meminfo page size.
|
|
Packit |
2d622a |
* This could be made more efficient if utility functions were shared
|
|
Packit |
2d622a |
* between libhugetlbfs and the test suite. For now we will just
|
|
Packit |
2d622a |
* read /proc/meminfo.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
default_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
if (default_size < 0) {
|
|
Packit |
2d622a |
ERROR("Cannot determine the default page size\n");
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* If the user is dealing in the default page size, we can use /proc */
|
|
Packit |
2d622a |
if (pagesize == default_size) {
|
|
Packit |
2d622a |
if (meminfo_key && key) {
|
|
Packit |
2d622a |
strcpy(filename, MEMINFO);
|
|
Packit |
2d622a |
*key = meminfo_key;
|
|
Packit |
2d622a |
} else
|
|
Packit |
2d622a |
sprintf(filename, PROC_HUGEPAGES_DIR "%s", sysfs_file);
|
|
Packit |
2d622a |
} else /* Use the sysfs interface */
|
|
Packit |
2d622a |
sprintf(filename, SYSFS_HUGEPAGES_DIR "hugepages-%lukB/%s",
|
|
Packit |
2d622a |
pagesize / 1024, sysfs_file);
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
static int hpage_size_to_index(unsigned long size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int i;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
for (i = 0; i < nr_hpage_sizes; i++)
|
|
Packit |
2d622a |
if (hpage_sizes[i].pagesize == size)
|
|
Packit |
2d622a |
return i;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void probe_default_hpage_size(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long size;
|
|
Packit |
2d622a |
int index;
|
|
Packit |
2d622a |
int default_overrided;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (nr_hpage_sizes == 0) {
|
|
Packit |
2d622a |
INFO("No configured huge page sizes\n");
|
|
Packit |
2d622a |
hpage_sizes_default_idx = -1;
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Check if the user specified a default size, otherwise use the
|
|
Packit |
2d622a |
* system default size as reported by /proc/meminfo.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
default_overrided = (__hugetlb_opts.def_page_size &&
|
|
Packit |
2d622a |
strlen(__hugetlb_opts.def_page_size) > 0);
|
|
Packit |
2d622a |
if (default_overrided)
|
|
Packit |
2d622a |
size = parse_page_size(__hugetlb_opts.def_page_size);
|
|
Packit |
2d622a |
else {
|
|
Packit |
2d622a |
size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (size >= 0) {
|
|
Packit |
2d622a |
index = hpage_size_to_index(size);
|
|
Packit |
2d622a |
if (index >= 0)
|
|
Packit |
2d622a |
hpage_sizes_default_idx = index;
|
|
Packit |
2d622a |
else {
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* If the user specified HUGETLB_DEFAULT_PAGE_SIZE,
|
|
Packit |
2d622a |
* then this situation will alter semantics and they
|
|
Packit |
2d622a |
* should receive a WARNING. Otherwise, this detail
|
|
Packit |
2d622a |
* is purely informational in nature.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
char msg[] = "No mount point found for default huge " \
|
|
Packit |
2d622a |
"page size. Using first available mount "
|
|
Packit |
2d622a |
"point.\n";
|
|
Packit |
2d622a |
if (default_overrided)
|
|
Packit |
2d622a |
WARNING("%s", msg);
|
|
Packit |
2d622a |
else
|
|
Packit |
2d622a |
INFO("%s", msg);
|
|
Packit |
2d622a |
hpage_sizes_default_idx = 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
} else {
|
|
Packit |
2d622a |
ERROR("Unable to determine default huge page size\n");
|
|
Packit |
2d622a |
hpage_sizes_default_idx = -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
static void add_hugetlbfs_mount(char *path, int user_mount)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int idx;
|
|
Packit |
2d622a |
long size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (strlen(path) > PATH_MAX)
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (!hugetlbfs_test_path(path)) {
|
|
Packit |
2d622a |
WARNING("%s is not a hugetlbfs mount point, ignoring\n", path);
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
size = hugetlbfs_test_pagesize(path);
|
|
Packit |
2d622a |
if (size < 0) {
|
|
Packit |
2d622a |
WARNING("Unable to detect page size for path %s\n", path);
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
idx = hpage_size_to_index(size);
|
|
Packit |
2d622a |
if (idx < 0) {
|
|
Packit |
2d622a |
if (nr_hpage_sizes >= MAX_HPAGE_SIZES) {
|
|
Packit |
2d622a |
WARNING("Maximum number of huge page sizes exceeded, "
|
|
Packit |
2d622a |
"ignoring %lukB page size\n", size);
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
idx = nr_hpage_sizes;
|
|
Packit |
2d622a |
hpage_sizes[nr_hpage_sizes++].pagesize = size;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (strlen(hpage_sizes[idx].mount)) {
|
|
Packit |
2d622a |
if (user_mount)
|
|
Packit |
2d622a |
WARNING("Mount point already defined for size %li, "
|
|
Packit |
2d622a |
"ignoring %s\n", size, path);
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
strcpy(hpage_sizes[idx].mount, path);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void debug_show_page_sizes(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int i;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
INFO("Detected page sizes:\n");
|
|
Packit |
2d622a |
for (i = 0; i < nr_hpage_sizes; i++)
|
|
Packit |
2d622a |
INFO(" Size: %li kB %s Mount: %s\n",
|
|
Packit |
2d622a |
hpage_sizes[i].pagesize / 1024,
|
|
Packit |
2d622a |
i == hpage_sizes_default_idx ? "(default)" : "",
|
|
Packit |
2d622a |
hpage_sizes[i].mount);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define LINE_MAXLEN 2048
|
|
Packit |
2d622a |
static void find_mounts(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int fd;
|
|
Packit |
2d622a |
char path[PATH_MAX+1];
|
|
Packit |
2d622a |
char line[LINE_MAXLEN + 1];
|
|
Packit |
2d622a |
char *eol;
|
|
Packit |
2d622a |
char *match;
|
|
Packit |
2d622a |
char *end;
|
|
Packit |
2d622a |
int bytes;
|
|
Packit |
2d622a |
off_t offset;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
fd = open("/proc/mounts", O_RDONLY);
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
fd = open("/etc/mtab", O_RDONLY);
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
ERROR("Couldn't open /proc/mounts or /etc/mtab (%s)\n",
|
|
Packit |
2d622a |
strerror(errno));
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
while ((bytes = read(fd, line, LINE_MAXLEN)) > 0) {
|
|
Packit |
2d622a |
line[LINE_MAXLEN] = '\0';
|
|
Packit |
2d622a |
eol = strchr(line, '\n');
|
|
Packit |
2d622a |
if (!eol) {
|
|
Packit |
2d622a |
ERROR("Line too long when parsing mounts\n");
|
|
Packit |
2d622a |
break;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Truncate the string to just one line and reset the file
|
|
Packit |
2d622a |
* to begin reading at the start of the next line.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
*eol = '\0';
|
|
Packit |
2d622a |
offset = bytes - (eol + 1 - line);
|
|
Packit |
2d622a |
lseek(fd, -offset, SEEK_CUR);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Match only hugetlbfs filesystems. */
|
|
Packit |
2d622a |
match = strstr(line, " hugetlbfs ");
|
|
Packit |
2d622a |
if (match) {
|
|
Packit |
2d622a |
match = strchr(line, '/');
|
|
Packit |
2d622a |
if (!match)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
end = strchr(match, ' ');
|
|
Packit |
2d622a |
if (!end)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
strncpy(path, match, end - match);
|
|
Packit |
2d622a |
path[end - match] = '\0';
|
|
Packit |
2d622a |
if ((hugetlbfs_test_path(path) == 1) &&
|
|
Packit |
2d622a |
!(access(path, R_OK | W_OK | X_OK)))
|
|
Packit |
2d622a |
add_hugetlbfs_mount(path, 0);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
close(fd);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void setup_mounts(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int do_scan = 1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* If HUGETLB_PATH is set, only add mounts specified there */
|
|
Packit |
2d622a |
while (__hugetlb_opts.path) {
|
|
Packit |
2d622a |
char path[PATH_MAX + 1];
|
|
Packit |
2d622a |
char *next = strchrnul(__hugetlb_opts.path, ':');
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
do_scan = 0;
|
|
Packit |
2d622a |
if (next - __hugetlb_opts.path > PATH_MAX) {
|
|
Packit |
2d622a |
ERROR("Path too long in HUGETLB_PATH -- "
|
|
Packit |
2d622a |
"ignoring environment\n");
|
|
Packit |
2d622a |
break;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
strncpy(path, __hugetlb_opts.path, next - __hugetlb_opts.path);
|
|
Packit |
2d622a |
path[next - __hugetlb_opts.path] = '\0';
|
|
Packit |
2d622a |
add_hugetlbfs_mount(path, 1);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* skip the ':' token */
|
|
Packit |
2d622a |
__hugetlb_opts.path = *next == '\0' ? NULL : next + 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Then probe all mounted filesystems */
|
|
Packit |
2d622a |
if (do_scan)
|
|
Packit |
2d622a |
find_mounts();
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int get_pool_size(long size, struct hpage_pool *pool)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long nr_over = 0;
|
|
Packit |
2d622a |
long nr_used = 0;
|
|
Packit |
2d622a |
long nr_surp = 0;
|
|
Packit |
2d622a |
long nr_resv = 0;
|
|
Packit |
2d622a |
long nr_static = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
long it_used = -1;
|
|
Packit |
2d622a |
long it_surp = -1;
|
|
Packit |
2d622a |
long it_resv = -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Pick up those values which are basically stable with respect to
|
|
Packit |
2d622a |
* the admin; ie. only changed by them.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* nr_over may be negative if this kernel does not support overcommit
|
|
Packit |
2d622a |
* in that case we will consider it always 0 and max will track min
|
|
Packit |
2d622a |
* always.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
nr_over = get_huge_page_counter(size, HUGEPAGES_OC);
|
|
Packit |
2d622a |
if (nr_over < 0)
|
|
Packit |
2d622a |
nr_over = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Sample the volatile values until they are stable. */
|
|
Packit |
2d622a |
while (nr_used != it_used || nr_surp != it_surp || nr_resv != it_resv) {
|
|
Packit |
2d622a |
nr_used = it_used;
|
|
Packit |
2d622a |
nr_surp = it_surp;
|
|
Packit |
2d622a |
nr_resv = it_resv;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
it_used = get_huge_page_counter(size, HUGEPAGES_TOTAL);
|
|
Packit |
2d622a |
it_surp = get_huge_page_counter(size, HUGEPAGES_SURP);
|
|
Packit |
2d622a |
it_resv = get_huge_page_counter(size, HUGEPAGES_RSVD);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (nr_surp < 0)
|
|
Packit |
2d622a |
nr_surp = 0;
|
|
Packit |
2d622a |
if (nr_resv < 0)
|
|
Packit |
2d622a |
nr_resv = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
nr_static = nr_used - nr_surp;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (nr_static >= 0) {
|
|
Packit |
2d622a |
DEBUG("pagesize<%ld> min<%ld> max<%ld> "
|
|
Packit |
2d622a |
"in-use<%ld>\n",
|
|
Packit |
2d622a |
size, nr_static, nr_static + nr_over,
|
|
Packit |
2d622a |
nr_used);
|
|
Packit |
2d622a |
pool->pagesize = size;
|
|
Packit |
2d622a |
pool->minimum = nr_static;
|
|
Packit |
2d622a |
pool->maximum = nr_static + nr_over;
|
|
Packit |
2d622a |
pool->size = nr_used;
|
|
Packit |
2d622a |
pool->is_default = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int hpool_sizes(struct hpage_pool *pools, int pcnt)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long default_size;
|
|
Packit |
2d622a |
int which = 0;
|
|
Packit |
2d622a |
DIR *dir;
|
|
Packit |
2d622a |
struct dirent *entry;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
default_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
if (default_size >= 0 && which < pcnt)
|
|
Packit |
2d622a |
if (get_pool_size(default_size, &pools[which])) {
|
|
Packit |
2d622a |
pools[which].is_default = 1;
|
|
Packit |
2d622a |
which++;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
dir = opendir(SYSFS_HUGEPAGES_DIR);
|
|
Packit |
2d622a |
if (dir) {
|
|
Packit |
2d622a |
while ((entry = readdir(dir))) {
|
|
Packit |
2d622a |
char *name = entry->d_name;
|
|
Packit |
2d622a |
long size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
DEBUG("parsing<%s>\n", name);
|
|
Packit |
2d622a |
if (strncmp(name, "hugepages-", 10) != 0)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
name += 10;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
size = size_to_smaller_unit(atol(name));
|
|
Packit |
2d622a |
if (size < 0 || size == default_size)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (get_pool_size(size, &pools[which]))
|
|
Packit |
2d622a |
which++;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
closedir(dir);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return (which < pcnt) ? which : -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* If we have a default page size then we support hugepages.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
int kernel_has_hugepages(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long default_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
if (default_size < 0)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* If we can find the default page size, and if we can find an overcommit
|
|
Packit |
2d622a |
* control for it then the kernel must support overcommit.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
int kernel_has_overcommit(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long default_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
if (default_size < 0)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (get_huge_page_counter(default_size, HUGEPAGES_OC) < 0)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
/* Library user visible functions */
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* NOTE: This function uses data that is initialized by
|
|
Packit |
2d622a |
* setup_mounts() which is called during libhugetlbfs initialization.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* returns:
|
|
Packit |
2d622a |
* on success, size of a huge page in number of bytes
|
|
Packit |
2d622a |
* on failure, -1
|
|
Packit |
2d622a |
* errno set to ENOSYS if huge pages are not supported
|
|
Packit |
2d622a |
* errno set to EOVERFLOW if huge page size would overflow return type
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
long gethugepagesize(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long hpage_size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Are huge pages available and have they been initialized? */
|
|
Packit |
2d622a |
if (hpage_sizes_default_idx == -1) {
|
|
Packit |
2d622a |
errno = hugepagesize_errno = ENOSYS;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
errno = 0;
|
|
Packit |
2d622a |
hpage_size = hpage_sizes[hpage_sizes_default_idx].pagesize;
|
|
Packit |
2d622a |
return hpage_size;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int gethugepagesizes(long pagesizes[], int n_elem)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long default_size;
|
|
Packit |
2d622a |
DIR *sysfs;
|
|
Packit |
2d622a |
struct dirent *ent;
|
|
Packit |
2d622a |
int nr_sizes = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (n_elem < 0) {
|
|
Packit |
2d622a |
errno = EINVAL;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (n_elem > 0 && pagesizes == NULL) {
|
|
Packit |
2d622a |
errno = EINVAL;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
errno = 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Get the system default size. */
|
|
Packit |
2d622a |
default_size = kernel_default_hugepage_size();
|
|
Packit |
2d622a |
if (default_size < 0)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (pagesizes && (nr_sizes == n_elem))
|
|
Packit |
2d622a |
return nr_sizes;
|
|
Packit |
2d622a |
if (pagesizes)
|
|
Packit |
2d622a |
pagesizes[nr_sizes] = default_size;
|
|
Packit |
2d622a |
nr_sizes++;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* Scan sysfs to look for other sizes.
|
|
Packit |
2d622a |
* Non-existing dir is not an error, we got one size from /proc/meminfo.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
sysfs = opendir(SYSFS_HUGEPAGES_DIR);
|
|
Packit |
2d622a |
if (!sysfs) {
|
|
Packit |
2d622a |
if (errno == ENOENT) {
|
|
Packit |
2d622a |
errno = 0;
|
|
Packit |
2d622a |
return nr_sizes;
|
|
Packit |
2d622a |
} else
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
while ((ent = readdir(sysfs))) {
|
|
Packit |
2d622a |
long size;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (strncmp(ent->d_name, "hugepages-", 10))
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
size = strtol(ent->d_name + 10, NULL, 10);
|
|
Packit |
2d622a |
if (size == LONG_MIN || size == LONG_MAX)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
size = size_to_smaller_unit(size);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (size < 0 || size == default_size)
|
|
Packit |
2d622a |
continue;
|
|
Packit |
2d622a |
if (pagesizes && (nr_sizes == n_elem))
|
|
Packit |
2d622a |
return nr_sizes;
|
|
Packit |
2d622a |
if (pagesizes)
|
|
Packit |
2d622a |
pagesizes[nr_sizes] = size;
|
|
Packit |
2d622a |
nr_sizes++;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
closedir(sysfs);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return nr_sizes;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int getpagesizes(long pagesizes[], int n_elem)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
int ret;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (n_elem < 0 || (n_elem > 0 && pagesizes == NULL)) {
|
|
Packit |
2d622a |
errno = EINVAL;
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Requests for sizing, we need one more slot than gethugepagesizes. */
|
|
Packit |
2d622a |
if (pagesizes == NULL && n_elem == 0) {
|
|
Packit |
2d622a |
ret = gethugepagesizes(pagesizes, n_elem);
|
|
Packit |
2d622a |
} else {
|
|
Packit |
2d622a |
/* Install the base page size. */
|
|
Packit |
2d622a |
if (pagesizes && n_elem == 0)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
if (pagesizes)
|
|
Packit |
2d622a |
pagesizes[0] = sysconf(_SC_PAGESIZE);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
ret = gethugepagesizes(pagesizes + 1, n_elem - 1);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (ret < 0)
|
|
Packit |
2d622a |
return ret;
|
|
Packit |
2d622a |
return ret + 1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int hugetlbfs_test_path(const char *mount)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
struct statfs64 sb;
|
|
Packit |
2d622a |
int err;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Bugs in the 32<->64 translation code in pre-2.6.15 kernels
|
|
Packit |
2d622a |
* mean that plain statfs() returns bogus errors on hugetlbfs
|
|
Packit |
2d622a |
* filesystems. Use statfs64() to work around. */
|
|
Packit |
2d622a |
err = statfs64(mount, &sb);
|
|
Packit |
2d622a |
if (err)
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return (sb.f_type == HUGETLBFS_MAGIC);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/* Return the page size for the given mount point in bytes */
|
|
Packit |
2d622a |
long hugetlbfs_test_pagesize(const char *mount)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
struct statfs64 sb;
|
|
Packit |
2d622a |
int err;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
err = statfs64(mount, &sb);
|
|
Packit |
2d622a |
if (err)
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if ((sb.f_bsize <= 0) || (sb.f_bsize > LONG_MAX))
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return sb.f_bsize;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
const char *hugetlbfs_find_path_for_size(long page_size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char *path;
|
|
Packit |
2d622a |
int idx;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
idx = hpage_size_to_index(page_size);
|
|
Packit |
2d622a |
if (idx >= 0) {
|
|
Packit |
2d622a |
path = hpage_sizes[idx].mount;
|
|
Packit |
2d622a |
if (strlen(path))
|
|
Packit |
2d622a |
return path;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
return NULL;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
const char *hugetlbfs_find_path(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long hpage_size = gethugepagesize();
|
|
Packit |
2d622a |
if (hpage_size > 0)
|
|
Packit |
2d622a |
return hugetlbfs_find_path_for_size(hpage_size);
|
|
Packit |
2d622a |
else
|
|
Packit |
2d622a |
return NULL;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int hugetlbfs_unlinked_fd_for_size(long page_size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
const char *path;
|
|
Packit |
2d622a |
char name[PATH_MAX+1];
|
|
Packit |
2d622a |
int fd;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
path = hugetlbfs_find_path_for_size(page_size);
|
|
Packit |
2d622a |
if (!path)
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
name[sizeof(name)-1] = '\0';
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
strcpy(name, path);
|
|
Packit |
2d622a |
strncat(name, "/libhugetlbfs.tmp.XXXXXX", sizeof(name)-1);
|
|
Packit |
2d622a |
/* FIXME: deal with overflows */
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
fd = mkstemp64(name);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
ERROR("mkstemp() failed: %s\n", strerror(errno));
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
unlink(name);
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return fd;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int hugetlbfs_unlinked_fd(void)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
long hpage_size = gethugepagesize();
|
|
Packit |
2d622a |
if (hpage_size > 0)
|
|
Packit |
2d622a |
return hugetlbfs_unlinked_fd_for_size(hpage_size);
|
|
Packit |
2d622a |
else
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define IOV_LEN 64
|
|
Packit |
2d622a |
int hugetlbfs_prefault(void *addr, size_t length)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
size_t offset;
|
|
Packit |
2d622a |
struct iovec iov[IOV_LEN];
|
|
Packit |
2d622a |
int ret;
|
|
Packit |
2d622a |
int i;
|
|
Packit |
2d622a |
int fd;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (!__hugetlbfs_prefault)
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/*
|
|
Packit |
2d622a |
* The NUMA users of libhugetlbfs' malloc feature are
|
|
Packit |
2d622a |
* expected to use the numactl program to specify an
|
|
Packit |
2d622a |
* appropriate policy for hugepage allocation
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* Use readv(2) to instantiate the hugepages unless HUGETLB_NO_PREFAULT
|
|
Packit |
2d622a |
* is set. If we instead returned a hugepage mapping with insufficient
|
|
Packit |
2d622a |
* hugepages, the VM system would kill the process when the
|
|
Packit |
2d622a |
* process tried to access the missing memory.
|
|
Packit |
2d622a |
*
|
|
Packit |
2d622a |
* The value of this environment variable is read during library
|
|
Packit |
2d622a |
* initialisation and sets __hugetlbfs_prefault accordingly. If
|
|
Packit |
2d622a |
* prefaulting is enabled and we can't get all that were requested,
|
|
Packit |
2d622a |
* -ENOMEM is returned. The caller is expected to release the entire
|
|
Packit |
2d622a |
* mapping and optionally it may recover by mapping base pages instead.
|
|
Packit |
2d622a |
*/
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
fd = open("/dev/zero", O_RDONLY);
|
|
Packit |
2d622a |
if (fd < 0) {
|
|
Packit |
2d622a |
ERROR("Failed to open /dev/zero for reading\n");
|
|
Packit |
2d622a |
return -ENOMEM;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
for (offset = 0; offset < length; ) {
|
|
Packit |
2d622a |
for (i = 0; i < IOV_LEN && offset < length; i++) {
|
|
Packit |
2d622a |
iov[i].iov_base = addr + offset;
|
|
Packit |
2d622a |
iov[i].iov_len = 1;
|
|
Packit |
2d622a |
offset += gethugepagesize();
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
ret = readv(fd, iov, i);
|
|
Packit |
2d622a |
if (ret != i) {
|
|
Packit |
2d622a |
DEBUG("Got %d of %d requested; err=%d\n", ret,
|
|
Packit |
2d622a |
i, ret < 0 ? errno : 0);
|
|
Packit |
2d622a |
WARNING("Failed to reserve %ld huge pages "
|
|
Packit |
2d622a |
"for new region\n",
|
|
Packit |
2d622a |
length / gethugepagesize());
|
|
Packit |
2d622a |
close(fd);
|
|
Packit |
2d622a |
return -ENOMEM;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
close(fd);
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
long get_huge_page_counter(long pagesize, unsigned int counter)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char file[PATH_MAX+1];
|
|
Packit |
2d622a |
char *key;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (select_pool_counter(counter, pagesize, file, &key))
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (access(file, O_RDONLY))
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return file_read_ulong(file, key);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int set_huge_page_counter(long pagesize, unsigned int counter,
|
|
Packit |
2d622a |
unsigned long val)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
char file[PATH_MAX+1];
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
if (select_pool_counter(counter, pagesize, file, NULL))
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return file_write_ulong(file, val);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int set_nr_hugepages(long pagesize, unsigned long val)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
return set_huge_page_counter(pagesize, HUGEPAGES_TOTAL, val);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
int set_nr_overcommit_hugepages(long pagesize, unsigned long val)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
DEBUG("setting HUGEPAGES_OC to %ld\n", val);
|
|
Packit |
2d622a |
return set_huge_page_counter(pagesize, HUGEPAGES_OC, val);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
long read_nr_overcommit(long page_size)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
if (!kernel_has_overcommit())
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
return get_huge_page_counter(page_size, HUGEPAGES_OC);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
void restore_overcommit_pages(long page_size, long oc_pool)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
if (!kernel_has_overcommit())
|
|
Packit |
2d622a |
return;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
set_nr_overcommit_hugepages(page_size, oc_pool);
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
/* Library user visible DIAGNOSES/DEBUGGING ONLY functions */
|
|
Packit |
2d622a |
/********************************************************************/
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
#define MAPS_BUF_SZ 4096
|
|
Packit |
2d622a |
long dump_proc_pid_maps()
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
FILE *f;
|
|
Packit |
2d622a |
char line[MAPS_BUF_SZ];
|
|
Packit |
2d622a |
size_t ret;
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
f = fopen("/proc/self/maps", "r");
|
|
Packit |
2d622a |
if (!f) {
|
|
Packit |
2d622a |
ERROR("Failed to open /proc/self/maps\n");
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
while (1) {
|
|
Packit |
2d622a |
ret = fread(line, sizeof(char), MAPS_BUF_SZ, f);
|
|
Packit |
2d622a |
if (ret < 0) {
|
|
Packit |
2d622a |
ERROR("Failed to read /proc/self/maps\n");
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
if (ret == 0)
|
|
Packit |
2d622a |
break;
|
|
Packit |
2d622a |
ret = fwrite(line, sizeof(char), ret, stderr);
|
|
Packit |
2d622a |
if (ret < 0) {
|
|
Packit |
2d622a |
ERROR("Failed to write /proc/self/maps to stderr\n");
|
|
Packit |
2d622a |
return -1;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
fclose(f);
|
|
Packit |
2d622a |
return 0;
|
|
Packit |
2d622a |
}
|
|
Packit |
2d622a |
|
|
Packit |
2d622a |
long read_meminfo(const char *tag)
|
|
Packit |
2d622a |
{
|
|
Packit |
2d622a |
return file_read_ulong(MEMINFO, tag);
|
|
Packit |
2d622a |
}
|