Blame alloc.c

Packit 2d622a
/*
Packit 2d622a
 * libhugetlbfs - Easy use of Linux hugepages
Packit 2d622a
 * alloc.c - Simple allocator of regions backed by hugepages
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 _GNU_SOURCE
Packit 2d622a
#include <fcntl.h>
Packit 2d622a
#include <errno.h>
Packit 2d622a
#include <stdio.h>
Packit 2d622a
#include <stdlib.h>
Packit 2d622a
#include <string.h>
Packit 2d622a
#include <unistd.h>
Packit 2d622a
#include <time.h>
Packit 2d622a
#include <sys/mman.h>
Packit 2d622a
#include <sys/types.h>
Packit 2d622a
Packit 2d622a
#include "hugetlbfs.h"
Packit 2d622a
#include "libhugetlbfs_internal.h"
Packit 2d622a
Packit 2d622a
/* Allocate base pages if huge page allocation fails */
Packit 2d622a
static void *fallback_base_pages(size_t len, ghp_t flags)
Packit 2d622a
{
Packit 2d622a
	int fd;
Packit 2d622a
	void *buf;
Packit 2d622a
	INFO("get_huge_pages: Falling back to base pages\n");
Packit 2d622a
Packit 2d622a
	/*
Packit 2d622a
	 * Map /dev/zero instead of MAP_ANONYMOUS avoid VMA mergings. Freeing
Packit 2d622a
	 * pages depends on /proc/pid/maps to find lengths of allocations.
Packit 2d622a
	 * This is a bit lazy and if found to be costly due to either the
Packit 2d622a
	 * extra open() or virtual address space usage, we could track active
Packit 2d622a
	 * mappings in a lock-protected list instead.
Packit 2d622a
	 */
Packit 2d622a
	fd = open("/dev/zero", O_RDWR);
Packit 2d622a
	if (fd == -1) {
Packit 2d622a
		ERROR("get_huge_pages: Failed to open /dev/zero for fallback");
Packit 2d622a
		return NULL;
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	buf = mmap(NULL, len,
Packit 2d622a
			PROT_READ|PROT_WRITE,
Packit 2d622a
			MAP_PRIVATE,
Packit 2d622a
			fd, 0);
Packit 2d622a
	if (buf == MAP_FAILED) {
Packit 2d622a
		WARNING("Base page fallback failed: %s\n", strerror(errno));
Packit 2d622a
		buf = NULL;
Packit 2d622a
	}
Packit 2d622a
	close(fd);
Packit 2d622a
Packit 2d622a
	return buf;
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
/**
Packit 2d622a
 * get_huge_pages - Allocate an amount of memory backed by huge pages
Packit 2d622a
 * len: Size of the region to allocate, must be hugepage-aligned
Packit 2d622a
 * flags: Flags specifying the behaviour of the function
Packit 2d622a
 *
Packit 2d622a
 * This function allocates a region of memory that is backed by huge pages
Packit 2d622a
 * and hugepage-aligned. This is not a suitable drop-in for malloc() but a
Packit 2d622a
 * a malloc library could use this function to create a new fixed-size heap
Packit 2d622a
 * similar in principal to what morecore does for glibc malloc.
Packit 2d622a
 */
Packit 2d622a
void *get_huge_pages(size_t len, ghp_t flags)
Packit 2d622a
{
Packit 2d622a
	void *buf;
Packit 2d622a
	int buf_fd = -1;
Packit 2d622a
	int mmap_reserve = __hugetlb_opts.no_reserve ? MAP_NORESERVE : 0;
Packit 2d622a
	int mmap_hugetlb = 0;
Packit 2d622a
	int ret;
Packit 2d622a
Packit 2d622a
	/* Catch an altogether-too easy typo */
Packit 2d622a
	if (flags & GHR_MASK)
Packit 2d622a
		ERROR("Improper use of GHR_* in get_huge_pages()\n");
Packit 2d622a
Packit 2d622a
#ifdef MAP_HUGETLB
Packit 2d622a
	mmap_hugetlb = MAP_HUGETLB;
Packit 2d622a
#endif
Packit 2d622a
Packit 2d622a
	if (__hugetlb_opts.map_hugetlb &&
Packit 2d622a
			gethugepagesize() == kernel_default_hugepage_size()) {
Packit 2d622a
		/* Because we can use MAP_HUGETLB, we simply mmap the region */
Packit 2d622a
		buf = mmap(NULL, len, PROT_READ|PROT_WRITE,
Packit 2d622a
			MAP_PRIVATE|MAP_ANONYMOUS|mmap_hugetlb|mmap_reserve,
Packit 2d622a
			0, 0);
Packit 2d622a
	} else {
Packit 2d622a
		/* Create a file descriptor for the new region */
Packit 2d622a
		buf_fd = hugetlbfs_unlinked_fd();
Packit 2d622a
		if (buf_fd < 0) {
Packit 2d622a
			WARNING("Couldn't open hugetlbfs file for %zd-sized buffer\n",
Packit 2d622a
					len);
Packit 2d622a
			return NULL;
Packit 2d622a
		}
Packit 2d622a
Packit 2d622a
		/* Map the requested region */
Packit 2d622a
		buf = mmap(NULL, len, PROT_READ|PROT_WRITE,
Packit 2d622a
			MAP_PRIVATE|mmap_reserve, buf_fd, 0);
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	if (buf == MAP_FAILED) {
Packit 2d622a
		if (buf_fd >= 0)
Packit 2d622a
			close(buf_fd);
Packit 2d622a
Packit 2d622a
		WARNING("get_huge_pages: New region mapping failed (flags: 0x%lX): %s\n",
Packit 2d622a
			flags, strerror(errno));
Packit 2d622a
		return NULL;
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/* Fault the region to ensure accesses succeed */
Packit 2d622a
	ret = hugetlbfs_prefault(buf, len);
Packit 2d622a
	if (ret != 0) {
Packit 2d622a
		munmap(buf, len);
Packit 2d622a
		if (buf_fd >= 0)
Packit 2d622a
			close(buf_fd);
Packit 2d622a
Packit 2d622a
		WARNING("get_huge_pages: Prefaulting failed (flags: 0x%lX): %s\n",
Packit 2d622a
			flags, strerror(ret));
Packit 2d622a
		return NULL;
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/* Close the file so we do not have to track the descriptor */
Packit 2d622a
	if (buf_fd >= 0 && close(buf_fd) != 0) {
Packit 2d622a
		WARNING("Failed to close new buffer fd: %s\n", strerror(errno));
Packit 2d622a
		munmap(buf, len);
Packit 2d622a
		return NULL;
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/* woo, new buffer of shiny */
Packit 2d622a
	return buf;
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
#define MAPS_BUF_SZ 4096
Packit 2d622a
static void __free_huge_pages(void *ptr, int aligned)
Packit 2d622a
{
Packit 2d622a
	FILE *fd;
Packit 2d622a
	char line[MAPS_BUF_SZ];
Packit 2d622a
	unsigned long start = 0, end = 0;
Packit 2d622a
	unsigned long palign = 0, hpalign = 0;
Packit 2d622a
	unsigned long hpalign_end = 0;
Packit 2d622a
Packit 2d622a
	/*
Packit 2d622a
	 * /proc/self/maps is used to determine the length of the original
Packit 2d622a
	 * allocation. As mappings are based on different files, we can
Packit 2d622a
	 * assume that maps will not merge. If the hugepages were truly
Packit 2d622a
	 * anonymous, this assumption would be broken.
Packit 2d622a
	 */
Packit 2d622a
	fd = fopen("/proc/self/maps", "r");
Packit 2d622a
	if (!fd) {
Packit 2d622a
		ERROR("Failed to open /proc/self/maps\n");
Packit 2d622a
		return;
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/*
Packit 2d622a
	 * An unaligned address allocated by get_hugepage_region()
Packit 2d622a
	 * could be either page or hugepage aligned
Packit 2d622a
	 */
Packit 2d622a
	if (!aligned) {
Packit 2d622a
		palign = ALIGN_DOWN((unsigned long)ptr, getpagesize());
Packit 2d622a
		hpalign = ALIGN_DOWN((unsigned long)ptr, gethugepagesize());
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/* Parse /proc/maps for address ranges line by line */
Packit 2d622a
	while (!feof(fd)) {
Packit 2d622a
		char *bufptr;
Packit 2d622a
		char *saveptr = NULL;
Packit 2d622a
Packit 2d622a
		/* Read a line of input */
Packit 2d622a
		if (fgets(line, MAPS_BUF_SZ, fd) == NULL)
Packit 2d622a
			break;
Packit 2d622a
Packit 2d622a
		/* Parse the line to get the start and end of each mapping */
Packit 2d622a
		bufptr = strtok_r(line, " ", &saveptr);
Packit 2d622a
		bufptr = strtok_r(bufptr, "-", &saveptr);
Packit 2d622a
		start = strtoull(bufptr, NULL, 16);
Packit 2d622a
		bufptr = strtok_r(NULL, "-", &saveptr);
Packit 2d622a
Packit 2d622a
		/* If the correct mapping is found, remove it */
Packit 2d622a
		if (start == (unsigned long)ptr) {
Packit 2d622a
			end = strtoull(bufptr, NULL, 16);
Packit 2d622a
			munmap(ptr, end - start);
Packit 2d622a
			break;
Packit 2d622a
		}
Packit 2d622a
Packit 2d622a
		/* If the passed address is aligned, just move along */
Packit 2d622a
		if (aligned)
Packit 2d622a
			continue;
Packit 2d622a
Packit 2d622a
		/*
Packit 2d622a
		 * If an address is hpage-aligned, record it but keep looking.
Packit 2d622a
		 * We might find a page-aligned or exact address later
Packit 2d622a
		 */
Packit 2d622a
		if (start == hpalign) {
Packit 2d622a
			hpalign_end = strtoull(bufptr, NULL, 16);
Packit 2d622a
			continue;
Packit 2d622a
		}
Packit 2d622a
Packit 2d622a
		/* If an address is page-aligned, free it */
Packit 2d622a
		if (start == palign) {
Packit 2d622a
			end = strtoull(bufptr, NULL, 16);
Packit 2d622a
			munmap((void *)start, end - start);
Packit 2d622a
			break;
Packit 2d622a
		}
Packit 2d622a
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/*
Packit 2d622a
	 * If no exact or page-aligned address was found, check for a
Packit 2d622a
	 * hpage-aligned address. If found, free it, otherwise warn that
Packit 2d622a
	 * the ptr pointed nowhere
Packit 2d622a
	 */
Packit 2d622a
	if (end == 0) {
Packit 2d622a
		if (hpalign_end == 0)
Packit 2d622a
			ERROR("hugepages_free using invalid or double free\n");
Packit 2d622a
		else
Packit 2d622a
			munmap((void *)hpalign, hpalign_end - hpalign);
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	fclose(fd);
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
/**
Packit 2d622a
 * free_huge_pages - Free a region allocated that was backed by large pages
Packit 2d622a
 * ptr - The pointer to the buffer returned by get_huge_pages()
Packit 2d622a
 *
Packit 2d622a
 * This function finds a region to free based on the contents of
Packit 2d622a
 * /proc/pid/maps. The assumption is made that the ptr is the start of
Packit 2d622a
 * a hugepage region allocated with free_huge_pages. No checking is made
Packit 2d622a
 * that the pointer is to a hugepage backed region.
Packit 2d622a
 */
Packit 2d622a
void free_huge_pages(void *ptr)
Packit 2d622a
{
Packit 2d622a
	__free_huge_pages(ptr, 1);
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
/*
Packit 2d622a
 * Offset the buffer using bytes wasted due to alignment to avoid using the
Packit 2d622a
 * same cache lines for the start of every buffer returned by
Packit 2d622a
 * get_huge_pages(). A small effort is made to select a random cacheline
Packit 2d622a
 * rather than sequential lines to give decent behaviour on average.
Packit 2d622a
 */
Packit 2d622a
void *cachecolor(void *buf, size_t len, size_t color_bytes)
Packit 2d622a
{
Packit 2d622a
	static long cacheline_size = 0;
Packit 2d622a
	static int linemod = 0;
Packit 2d622a
	char *bytebuf = (char *)buf;
Packit 2d622a
	int numlines;
Packit 2d622a
	int line = 0;
Packit 2d622a
Packit 2d622a
	/* Lookup our cacheline size once */
Packit 2d622a
	if (cacheline_size == 0) {
Packit 2d622a
		cacheline_size = sysconf(_SC_LEVEL2_CACHE_LINESIZE);
Packit 2d622a
		linemod = time(NULL);
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	numlines = color_bytes / cacheline_size;
Packit 2d622a
	DEBUG("%d lines of cacheline size %ld due to %zd wastage\n",
Packit 2d622a
		numlines, cacheline_size, color_bytes);
Packit 2d622a
	if (numlines) {
Packit 2d622a
		line = linemod % numlines;
Packit 2d622a
		bytebuf += cacheline_size * line;
Packit 2d622a
Packit 2d622a
		/* Pseudo-ish random line selection */
Packit 2d622a
		linemod += len % numlines;
Packit 2d622a
	}
Packit 2d622a
	DEBUG("Using line offset %d from start\n", line);
Packit 2d622a
Packit 2d622a
	return bytebuf;
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
/**
Packit 2d622a
 * get_hugepage_region - Allocate an amount of memory backed by huge pages
Packit 2d622a
 *
Packit 2d622a
 * len: Size of the region to allocate
Packit 2d622a
 * flags: Flags specifying the behaviour of the function
Packit 2d622a
 *
Packit 2d622a
 * This function allocates a region of memory backed by huge pages. Care should
Packit 2d622a
 * be taken when using this function as a drop-in replacement for malloc() as
Packit 2d622a
 * memory can be wasted if the length is not hugepage-aligned. This function
Packit 2d622a
 * is more relaxed than get_huge_pages() in that it allows fallback to small
Packit 2d622a
 * pages when requested.
Packit 2d622a
 */
Packit 2d622a
void *get_hugepage_region(size_t len, ghr_t flags)
Packit 2d622a
{
Packit 2d622a
	size_t aligned_len, wastage;
Packit 2d622a
	void *buf;
Packit 2d622a
Packit 2d622a
	/* Catch an altogether-too easy typo */
Packit 2d622a
	if (flags & GHP_MASK)
Packit 2d622a
		ERROR("Improper use of GHP_* in get_hugepage_region()\n");
Packit 2d622a
Packit 2d622a
	/* Align the len parameter to a hugepage boundary and allocate */
Packit 2d622a
	aligned_len = ALIGN(len, gethugepagesize());
Packit 2d622a
	buf = get_huge_pages(aligned_len, GHP_DEFAULT);
Packit 2d622a
	if (buf == NULL && (flags & GHR_FALLBACK)) {
Packit 2d622a
		aligned_len = ALIGN(len, getpagesize());
Packit 2d622a
		buf = fallback_base_pages(len, flags);
Packit 2d622a
	}
Packit 2d622a
Packit 2d622a
	/* Calculate wastage for coloring */
Packit 2d622a
	wastage = aligned_len - len;
Packit 2d622a
	if (wastage != 0 && !(flags & GHR_COLOR))
Packit 2d622a
		DEBUG("get_hugepage_region: Wasted %zd bytes due to alignment\n",
Packit 2d622a
			wastage);
Packit 2d622a
Packit 2d622a
	/* Only colour if requested */
Packit 2d622a
	if (flags & GHR_COLOR)
Packit 2d622a
		buf = cachecolor(buf, len, wastage);
Packit 2d622a
Packit 2d622a
	return buf;
Packit 2d622a
}
Packit 2d622a
Packit 2d622a
/**
Packit 2d622a
 * free_hugepage_region - Free a region allocated by get_hugepage_region
Packit 2d622a
 * ptr - The pointer to the buffer returned by get_hugepage_region
Packit 2d622a
 *
Packit 2d622a
 * This function finds a region to free based on the contents of
Packit 2d622a
 * /proc/pid/maps. The assumption is made that the ptr is the start of
Packit 2d622a
 * a hugepage region allocated with get_hugepage_region. No checking is made
Packit 2d622a
 * that the pointer is to a hugepage backed region.
Packit 2d622a
 */
Packit 2d622a
void free_hugepage_region(void *ptr)
Packit 2d622a
{
Packit 2d622a
	__free_huge_pages(ptr, 0);
Packit 2d622a
}