gvisor/test/syscalls/linux/mempolicy.cc

290 lines
9.8 KiB
C++

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <sys/syscall.h>
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "test/util/cleanup.h"
#include "test/util/memory_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
namespace gvisor {
namespace testing {
namespace {
#define BITS_PER_BYTE 8
#define MPOL_F_STATIC_NODES (1 << 15)
#define MPOL_F_RELATIVE_NODES (1 << 14)
#define MPOL_DEFAULT 0
#define MPOL_PREFERRED 1
#define MPOL_BIND 2
#define MPOL_INTERLEAVE 3
#define MPOL_LOCAL 4
#define MPOL_F_NODE (1 << 0)
#define MPOL_F_ADDR (1 << 1)
#define MPOL_F_MEMS_ALLOWED (1 << 2)
#define MPOL_MF_STRICT (1 << 0)
#define MPOL_MF_MOVE (1 << 1)
#define MPOL_MF_MOVE_ALL (1 << 2)
int get_mempolicy(int *policy, uint64_t *nmask, uint64_t maxnode, void *addr,
int flags) {
return syscall(SYS_get_mempolicy, policy, nmask, maxnode, addr, flags);
}
int set_mempolicy(int mode, uint64_t *nmask, uint64_t maxnode) {
return syscall(SYS_set_mempolicy, mode, nmask, maxnode);
}
int mbind(void *addr, unsigned long len, int mode,
const unsigned long *nodemask, unsigned long maxnode,
unsigned flags) {
return syscall(SYS_mbind, addr, len, mode, nodemask, maxnode, flags);
}
// Creates a cleanup object that resets the calling thread's mempolicy to the
// system default when the calling scope ends.
Cleanup ScopedMempolicy() {
return Cleanup([] {
EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 0), SyscallSucceeds());
});
}
// Temporarily change the memory policy for the calling thread within the
// caller's scope.
PosixErrorOr<Cleanup> ScopedSetMempolicy(int mode, uint64_t *nmask,
uint64_t maxnode) {
if (set_mempolicy(mode, nmask, maxnode)) {
return PosixError(errno, "set_mempolicy");
}
return ScopedMempolicy();
}
TEST(MempolicyTest, CheckDefaultPolicy) {
int mode = 0;
uint64_t nodemask = 0;
ASSERT_THAT(get_mempolicy(&mode, &nodemask, sizeof(nodemask) * BITS_PER_BYTE,
nullptr, 0),
SyscallSucceeds());
EXPECT_EQ(MPOL_DEFAULT, mode);
EXPECT_EQ(0x0, nodemask);
}
TEST(MempolicyTest, PolicyPreservedAfterSetMempolicy) {
uint64_t nodemask = 0x1;
auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(
MPOL_BIND, &nodemask, sizeof(nodemask) * BITS_PER_BYTE));
int mode = 0;
uint64_t nodemask_after = 0x0;
ASSERT_THAT(get_mempolicy(&mode, &nodemask_after,
sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0),
SyscallSucceeds());
EXPECT_EQ(MPOL_BIND, mode);
EXPECT_EQ(0x1, nodemask_after);
// Try throw in some mode flags.
for (auto mode_flag : {MPOL_F_STATIC_NODES, MPOL_F_RELATIVE_NODES}) {
auto cleanup2 = ASSERT_NO_ERRNO_AND_VALUE(
ScopedSetMempolicy(MPOL_INTERLEAVE | mode_flag, &nodemask,
sizeof(nodemask) * BITS_PER_BYTE));
mode = 0;
nodemask_after = 0x0;
ASSERT_THAT(
get_mempolicy(&mode, &nodemask_after,
sizeof(nodemask_after) * BITS_PER_BYTE, nullptr, 0),
SyscallSucceeds());
EXPECT_EQ(MPOL_INTERLEAVE | mode_flag, mode);
EXPECT_EQ(0x1, nodemask_after);
}
}
TEST(MempolicyTest, SetMempolicyRejectsInvalidInputs) {
auto cleanup = ScopedMempolicy();
uint64_t nodemask;
if (IsRunningOnGvisor()) {
// Invalid nodemask, we only support a single node on gvisor.
nodemask = 0x4;
ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask,
sizeof(nodemask) * BITS_PER_BYTE),
SyscallFailsWithErrno(EINVAL));
}
nodemask = 0x1;
// Invalid mode.
ASSERT_THAT(set_mempolicy(7439, &nodemask, sizeof(nodemask) * BITS_PER_BYTE),
SyscallFailsWithErrno(EINVAL));
// Invalid nodemask size.
ASSERT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0),
SyscallFailsWithErrno(EINVAL));
// Invalid mode flag.
ASSERT_THAT(
set_mempolicy(MPOL_DEFAULT | MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES,
&nodemask, sizeof(nodemask) * BITS_PER_BYTE),
SyscallFailsWithErrno(EINVAL));
// MPOL_INTERLEAVE with empty nodemask.
nodemask = 0x0;
ASSERT_THAT(set_mempolicy(MPOL_INTERLEAVE, &nodemask,
sizeof(nodemask) * BITS_PER_BYTE),
SyscallFailsWithErrno(EINVAL));
}
// The manpages specify that the nodemask provided to set_mempolicy are
// considered empty if the nodemask pointer is null, or if the nodemask size is
// 0. We use a policy which accepts both empty and non-empty nodemasks
// (MPOL_PREFERRED), a policy which requires a non-empty nodemask (MPOL_BIND),
// and a policy which completely ignores the nodemask (MPOL_DEFAULT) to verify
// argument checking around nodemasks.
TEST(MempolicyTest, EmptyNodemaskOnSet) {
auto cleanup = ScopedMempolicy();
EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, nullptr, 1), SyscallSucceeds());
EXPECT_THAT(set_mempolicy(MPOL_BIND, nullptr, 1),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, nullptr, 1), SyscallSucceeds());
uint64_t nodemask = 0x1;
EXPECT_THAT(set_mempolicy(MPOL_DEFAULT, &nodemask, 0),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(set_mempolicy(MPOL_BIND, &nodemask, 0),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(set_mempolicy(MPOL_PREFERRED, &nodemask, 0),
SyscallFailsWithErrno(EINVAL));
}
TEST(MempolicyTest, QueryAvailableNodes) {
uint64_t nodemask = 0;
ASSERT_THAT(
get_mempolicy(nullptr, &nodemask, sizeof(nodemask) * BITS_PER_BYTE,
nullptr, MPOL_F_MEMS_ALLOWED),
SyscallSucceeds());
// We can only be sure there is a single node if running on gvisor.
if (IsRunningOnGvisor()) {
EXPECT_EQ(0x1, nodemask);
}
// MPOL_F_ADDR and MPOL_F_NODE flags may not be combined with
// MPOL_F_MEMS_ALLLOWED.
for (auto flags :
{MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR, MPOL_F_MEMS_ALLOWED | MPOL_F_NODE,
MPOL_F_MEMS_ALLOWED | MPOL_F_ADDR | MPOL_F_NODE}) {
ASSERT_THAT(get_mempolicy(nullptr, &nodemask,
sizeof(nodemask) * BITS_PER_BYTE, nullptr, flags),
SyscallFailsWithErrno(EINVAL));
}
}
TEST(MempolicyTest, GetMempolicyQueryNodeForAddress) {
uint64_t dummy_stack_address;
auto dummy_heap_address = absl::make_unique<uint64_t>();
int mode;
for (auto ptr : {&dummy_stack_address, dummy_heap_address.get()}) {
mode = -1;
ASSERT_THAT(
get_mempolicy(&mode, nullptr, 0, ptr, MPOL_F_ADDR | MPOL_F_NODE),
SyscallSucceeds());
// If we're not running on gvisor, the address may be allocated on a
// different numa node.
if (IsRunningOnGvisor()) {
EXPECT_EQ(0, mode);
}
}
void* invalid_address = reinterpret_cast<void*>(-1);
// Invalid address.
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, invalid_address,
MPOL_F_ADDR | MPOL_F_NODE),
SyscallFailsWithErrno(EFAULT));
// Invalid mode pointer.
ASSERT_THAT(get_mempolicy(reinterpret_cast<int*>(invalid_address), nullptr, 0,
&dummy_stack_address, MPOL_F_ADDR | MPOL_F_NODE),
SyscallFailsWithErrno(EFAULT));
}
TEST(MempolicyTest, GetMempolicyCanOmitPointers) {
int mode;
uint64_t nodemask;
// Omit nodemask pointer.
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, 0), SyscallSucceeds());
// Omit mode pointer.
ASSERT_THAT(get_mempolicy(nullptr, &nodemask,
sizeof(nodemask) * BITS_PER_BYTE, nullptr, 0),
SyscallSucceeds());
// Omit both pointers.
ASSERT_THAT(get_mempolicy(nullptr, nullptr, 0, nullptr, 0),
SyscallSucceeds());
}
TEST(MempolicyTest, GetMempolicyNextInterleaveNode) {
int mode;
// Policy for thread not yet set to MPOL_INTERLEAVE, can't query for
// the next node which will be used for allocation.
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE),
SyscallFailsWithErrno(EINVAL));
// Set default policy for thread to MPOL_INTERLEAVE.
uint64_t nodemask = 0x1;
auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(
MPOL_INTERLEAVE, &nodemask, sizeof(nodemask) * BITS_PER_BYTE));
mode = -1;
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, nullptr, MPOL_F_NODE),
SyscallSucceeds());
EXPECT_EQ(0, mode);
}
TEST(MempolicyTest, Mbind) {
// Temporarily set the thread policy to MPOL_PREFERRED.
const auto cleanup_thread_policy =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetMempolicy(MPOL_PREFERRED, nullptr, 0));
const auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS));
// vmas default to MPOL_DEFAULT irrespective of the thread policy (currently
// MPOL_PREFERRED).
int mode;
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR),
SyscallSucceeds());
EXPECT_EQ(mode, MPOL_DEFAULT);
// Set MPOL_PREFERRED for the vma and read it back.
ASSERT_THAT(
mbind(mapping.ptr(), mapping.len(), MPOL_PREFERRED, nullptr, 0, 0),
SyscallSucceeds());
ASSERT_THAT(get_mempolicy(&mode, nullptr, 0, mapping.ptr(), MPOL_F_ADDR),
SyscallSucceeds());
EXPECT_EQ(mode, MPOL_PREFERRED);
}
} // namespace
} // namespace testing
} // namespace gvisor