diff options
Diffstat (limited to 'test/syscalls/linux/mempolicy.cc')
-rw-r--r-- | test/syscalls/linux/mempolicy.cc | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/test/syscalls/linux/mempolicy.cc b/test/syscalls/linux/mempolicy.cc new file mode 100644 index 000000000..9f8033bdf --- /dev/null +++ b/test/syscalls/linux/mempolicy.cc @@ -0,0 +1,258 @@ +// Copyright 2018 Google LLC +// +// 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/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_MAX MPOL_INTERLEAVE +#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(__NR_get_mempolicy, policy, nmask, maxnode, addr, flags); +} + +int set_mempolicy(int mode, uint64_t *nmask, uint64_t maxnode) { + return syscall(__NR_set_mempolicy, mode, nmask, maxnode); +} + +// 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); +} + +} // namespace + +} // namespace testing +} // namespace gvisor |