Commit 29750f71 authored by Mina Almasry's avatar Mina Almasry Committed by Linus Torvalds

hugetlb_cgroup: add hugetlb_cgroup reservation tests

The tests use both shared and private mapped hugetlb memory, and monitors
the hugetlb usage counter as well as the hugetlb reservation counter.
They test different configurations such as hugetlb memory usage via
hugetlbfs, or MAP_HUGETLB, or shmget/shmat, and with and without
MAP_POPULATE.

Also add test for hugetlb reservation reparenting, since this is a subtle
issue.
Signed-off-by: default avatarMina Almasry <almasrymina@google.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Tested-by: Sandipan Das <sandipan@linux.ibm.com>	[powerpc64]
Acked-by: default avatarMike Kravetz <mike.kravetz@oracle.com>
Cc: Sandipan Das <sandipan@linux.ibm.com>
Cc: David Rientjes <rientjes@google.com>
Cc: Greg Thelen <gthelen@google.com>
Cc: Shakeel Butt <shakeelb@google.com>
Cc: Shuah Khan <shuah@kernel.org>
Link: http://lkml.kernel.org/r/20200211213128.73302-8-almasrymina@google.comSigned-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent a9b3f867
...@@ -14,3 +14,4 @@ virtual_address_range ...@@ -14,3 +14,4 @@ virtual_address_range
gup_benchmark gup_benchmark
va_128TBswitch va_128TBswitch
map_fixed_noreplace map_fixed_noreplace
write_to_hugetlbfs
...@@ -23,6 +23,7 @@ TEST_GEN_FILES += userfaultfd ...@@ -23,6 +23,7 @@ TEST_GEN_FILES += userfaultfd
ifneq (,$(filter $(ARCH),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sh64 sparc64 x86_64)) ifneq (,$(filter $(ARCH),arm64 ia64 mips64 parisc64 ppc64 riscv64 s390x sh64 sparc64 x86_64))
TEST_GEN_FILES += va_128TBswitch TEST_GEN_FILES += va_128TBswitch
TEST_GEN_FILES += virtual_address_range TEST_GEN_FILES += virtual_address_range
TEST_GEN_FILES += write_to_hugetlbfs
endif endif
TEST_PROGS := run_vmtests TEST_PROGS := run_vmtests
......
This diff is collapsed.
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
set -e
if [[ $(id -u) -ne 0 ]]; then
echo "This test must be run as root. Skipping..."
exit 0
fi
usage_file=usage_in_bytes
if [[ "$1" == "-cgroup-v2" ]]; then
cgroup2=1
usage_file=current
fi
CGROUP_ROOT='/dev/cgroup/memory'
MNT='/mnt/huge/'
if [[ ! -e $CGROUP_ROOT ]]; then
mkdir -p $CGROUP_ROOT
if [[ $cgroup2 ]]; then
mount -t cgroup2 none $CGROUP_ROOT
sleep 1
echo "+hugetlb +memory" >$CGROUP_ROOT/cgroup.subtree_control
else
mount -t cgroup memory,hugetlb $CGROUP_ROOT
fi
fi
function get_machine_hugepage_size() {
hpz=$(grep -i hugepagesize /proc/meminfo)
kb=${hpz:14:-3}
mb=$(($kb / 1024))
echo $mb
}
MB=$(get_machine_hugepage_size)
function cleanup() {
echo cleanup
set +e
rm -rf "$MNT"/* 2>/dev/null
umount "$MNT" 2>/dev/null
rmdir "$MNT" 2>/dev/null
rmdir "$CGROUP_ROOT"/a/b 2>/dev/null
rmdir "$CGROUP_ROOT"/a 2>/dev/null
rmdir "$CGROUP_ROOT"/test1 2>/dev/null
echo 0 >/proc/sys/vm/nr_hugepages
set -e
}
function assert_state() {
local expected_a="$1"
local expected_a_hugetlb="$2"
local expected_b=""
local expected_b_hugetlb=""
if [ ! -z ${3:-} ] && [ ! -z ${4:-} ]; then
expected_b="$3"
expected_b_hugetlb="$4"
fi
local tolerance=$((5 * 1024 * 1024))
local actual_a
actual_a="$(cat "$CGROUP_ROOT"/a/memory.$usage_file)"
if [[ $actual_a -lt $(($expected_a - $tolerance)) ]] ||
[[ $actual_a -gt $(($expected_a + $tolerance)) ]]; then
echo actual a = $((${actual_a%% *} / 1024 / 1024)) MB
echo expected a = $((${expected_a%% *} / 1024 / 1024)) MB
echo fail
cleanup
exit 1
fi
local actual_a_hugetlb
actual_a_hugetlb="$(cat "$CGROUP_ROOT"/a/hugetlb.${MB}MB.$usage_file)"
if [[ $actual_a_hugetlb -lt $(($expected_a_hugetlb - $tolerance)) ]] ||
[[ $actual_a_hugetlb -gt $(($expected_a_hugetlb + $tolerance)) ]]; then
echo actual a hugetlb = $((${actual_a_hugetlb%% *} / 1024 / 1024)) MB
echo expected a hugetlb = $((${expected_a_hugetlb%% *} / 1024 / 1024)) MB
echo fail
cleanup
exit 1
fi
if [[ -z "$expected_b" || -z "$expected_b_hugetlb" ]]; then
return
fi
local actual_b
actual_b="$(cat "$CGROUP_ROOT"/a/b/memory.$usage_file)"
if [[ $actual_b -lt $(($expected_b - $tolerance)) ]] ||
[[ $actual_b -gt $(($expected_b + $tolerance)) ]]; then
echo actual b = $((${actual_b%% *} / 1024 / 1024)) MB
echo expected b = $((${expected_b%% *} / 1024 / 1024)) MB
echo fail
cleanup
exit 1
fi
local actual_b_hugetlb
actual_b_hugetlb="$(cat "$CGROUP_ROOT"/a/b/hugetlb.${MB}MB.$usage_file)"
if [[ $actual_b_hugetlb -lt $(($expected_b_hugetlb - $tolerance)) ]] ||
[[ $actual_b_hugetlb -gt $(($expected_b_hugetlb + $tolerance)) ]]; then
echo actual b hugetlb = $((${actual_b_hugetlb%% *} / 1024 / 1024)) MB
echo expected b hugetlb = $((${expected_b_hugetlb%% *} / 1024 / 1024)) MB
echo fail
cleanup
exit 1
fi
}
function setup() {
echo 100 >/proc/sys/vm/nr_hugepages
mkdir "$CGROUP_ROOT"/a
sleep 1
if [[ $cgroup2 ]]; then
echo "+hugetlb +memory" >$CGROUP_ROOT/a/cgroup.subtree_control
else
echo 0 >$CGROUP_ROOT/a/cpuset.mems
echo 0 >$CGROUP_ROOT/a/cpuset.cpus
fi
mkdir "$CGROUP_ROOT"/a/b
if [[ ! $cgroup2 ]]; then
echo 0 >$CGROUP_ROOT/a/b/cpuset.mems
echo 0 >$CGROUP_ROOT/a/b/cpuset.cpus
fi
mkdir -p "$MNT"
mount -t hugetlbfs none "$MNT"
}
write_hugetlbfs() {
local cgroup="$1"
local path="$2"
local size="$3"
if [[ $cgroup2 ]]; then
echo $$ >$CGROUP_ROOT/$cgroup/cgroup.procs
else
echo 0 >$CGROUP_ROOT/$cgroup/cpuset.mems
echo 0 >$CGROUP_ROOT/$cgroup/cpuset.cpus
echo $$ >"$CGROUP_ROOT/$cgroup/tasks"
fi
./write_to_hugetlbfs -p "$path" -s "$size" -m 0 -o
if [[ $cgroup2 ]]; then
echo $$ >$CGROUP_ROOT/cgroup.procs
else
echo $$ >"$CGROUP_ROOT/tasks"
fi
echo
}
set -e
size=$((${MB} * 1024 * 1024 * 25)) # 50MB = 25 * 2MB hugepages.
cleanup
echo
echo
echo Test charge, rmdir, uncharge
setup
echo mkdir
mkdir $CGROUP_ROOT/test1
echo write
write_hugetlbfs test1 "$MNT"/test $size
echo rmdir
rmdir $CGROUP_ROOT/test1
mkdir $CGROUP_ROOT/test1
echo uncharge
rm -rf /mnt/huge/*
cleanup
echo done
echo
echo
if [[ ! $cgroup2 ]]; then
echo "Test parent and child hugetlb usage"
setup
echo write
write_hugetlbfs a "$MNT"/test $size
echo Assert memory charged correctly for parent use.
assert_state 0 $size 0 0
write_hugetlbfs a/b "$MNT"/test2 $size
echo Assert memory charged correctly for child use.
assert_state 0 $(($size * 2)) 0 $size
rmdir "$CGROUP_ROOT"/a/b
sleep 5
echo Assert memory reparent correctly.
assert_state 0 $(($size * 2))
rm -rf "$MNT"/*
umount "$MNT"
echo Assert memory uncharged correctly.
assert_state 0 0
cleanup
fi
echo
echo
echo "Test child only hugetlb usage"
echo setup
setup
echo write
write_hugetlbfs a/b "$MNT"/test2 $size
echo Assert memory charged correctly for child only use.
assert_state 0 $(($size)) 0 $size
rmdir "$CGROUP_ROOT"/a/b
echo Assert memory reparent correctly.
assert_state 0 $size
rm -rf "$MNT"/*
umount "$MNT"
echo Assert memory uncharged correctly.
assert_state 0 0
cleanup
echo ALL PASS
umount $CGROUP_ROOT
rm -rf $CGROUP_ROOT
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
set -e
size=$1
populate=$2
write=$3
cgroup=$4
path=$5
method=$6
private=$7
want_sleep=$8
reserve=$9
echo "Putting task in cgroup '$cgroup'"
echo $$ > /dev/cgroup/memory/"$cgroup"/cgroup.procs
echo "Method is $method"
set +e
./write_to_hugetlbfs -p "$path" -s "$size" "$write" "$populate" -m "$method" \
"$private" "$want_sleep" "$reserve"
// SPDX-License-Identifier: GPL-2.0
/*
* This program reserves and uses hugetlb memory, supporting a bunch of
* scenarios needed by the charged_reserved_hugetlb.sh test.
*/
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
/* Global definitions. */
enum method {
HUGETLBFS,
MMAP_MAP_HUGETLB,
SHM,
MAX_METHOD
};
/* Global variables. */
static const char *self;
static char *shmaddr;
static int shmid;
/*
* Show usage and exit.
*/
static void exit_usage(void)
{
printf("Usage: %s -p <path to hugetlbfs file> -s <size to map> "
"[-m <0=hugetlbfs | 1=mmap(MAP_HUGETLB)>] [-l] [-r] "
"[-o] [-w] [-n]\n",
self);
exit(EXIT_FAILURE);
}
void sig_handler(int signo)
{
printf("Received %d.\n", signo);
if (signo == SIGINT) {
printf("Deleting the memory\n");
if (shmdt((const void *)shmaddr) != 0) {
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(4);
}
shmctl(shmid, IPC_RMID, NULL);
printf("Done deleting the memory\n");
}
exit(2);
}
int main(int argc, char **argv)
{
int fd = 0;
int key = 0;
int *ptr = NULL;
int c = 0;
int size = 0;
char path[256] = "";
enum method method = MAX_METHOD;
int want_sleep = 0, private = 0;
int populate = 0;
int write = 0;
int reserve = 1;
unsigned long i;
if (signal(SIGINT, sig_handler) == SIG_ERR)
err(1, "\ncan't catch SIGINT\n");
/* Parse command-line arguments. */
setvbuf(stdout, NULL, _IONBF, 0);
self = argv[0];
while ((c = getopt(argc, argv, "s:p:m:owlrn")) != -1) {
switch (c) {
case 's':
size = atoi(optarg);
break;
case 'p':
strncpy(path, optarg, sizeof(path));
break;
case 'm':
if (atoi(optarg) >= MAX_METHOD) {
errno = EINVAL;
perror("Invalid -m.");
exit_usage();
}
method = atoi(optarg);
break;
case 'o':
populate = 1;
break;
case 'w':
write = 1;
break;
case 'l':
want_sleep = 1;
break;
case 'r':
private
= 1;
break;
case 'n':
reserve = 0;
break;
default:
errno = EINVAL;
perror("Invalid arg");
exit_usage();
}
}
if (strncmp(path, "", sizeof(path)) != 0) {
printf("Writing to this path: %s\n", path);
} else {
errno = EINVAL;
perror("path not found");
exit_usage();
}
if (size != 0) {
printf("Writing this size: %d\n", size);
} else {
errno = EINVAL;
perror("size not found");
exit_usage();
}
if (!populate)
printf("Not populating.\n");
else
printf("Populating.\n");
if (!write)
printf("Not writing to memory.\n");
if (method == MAX_METHOD) {
errno = EINVAL;
perror("-m Invalid");
exit_usage();
} else
printf("Using method=%d\n", method);
if (!private)
printf("Shared mapping.\n");
else
printf("Private mapping.\n");
if (!reserve)
printf("NO_RESERVE mapping.\n");
else
printf("RESERVE mapping.\n");
switch (method) {
case HUGETLBFS:
printf("Allocating using HUGETLBFS.\n");
fd = open(path, O_CREAT | O_RDWR, 0777);
if (fd == -1)
err(1, "Failed to open file.");
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
(private ? MAP_PRIVATE : MAP_SHARED) |
(populate ? MAP_POPULATE : 0) |
(reserve ? 0 : MAP_NORESERVE),
fd, 0);
if (ptr == MAP_FAILED) {
close(fd);
err(1, "Error mapping the file");
}
break;
case MMAP_MAP_HUGETLB:
printf("Allocating using MAP_HUGETLB.\n");
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
(private ? (MAP_PRIVATE | MAP_ANONYMOUS) :
MAP_SHARED) |
MAP_HUGETLB | (populate ? MAP_POPULATE : 0) |
(reserve ? 0 : MAP_NORESERVE),
-1, 0);
if (ptr == MAP_FAILED)
err(1, "mmap");
printf("Returned address is %p\n", ptr);
break;
case SHM:
printf("Allocating using SHM.\n");
shmid = shmget(key, size,
SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
if (shmid < 0) {
shmid = shmget(++key, size,
SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
if (shmid < 0)
err(1, "shmget");
}
printf("shmid: 0x%x, shmget key:%d\n", shmid, key);
ptr = shmat(shmid, NULL, 0);
if (ptr == (int *)-1) {
perror("Shared memory attach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(2);
}
printf("shmaddr: %p\n", ptr);
break;
default:
errno = EINVAL;
err(1, "Invalid method.");
}
if (write) {
printf("Writing to memory.\n");
memset(ptr, 1, size);
}
if (want_sleep) {
/* Signal to caller that we're done. */
printf("DONE\n");
/* Hold memory until external kill signal is delivered. */
while (1)
sleep(100);
}
if (method == HUGETLBFS)
close(fd);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment