290 lines
9.8 KiB
C++
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
|