diff options
Diffstat (limited to 'test/syscalls/linux/affinity.cc')
-rw-r--r-- | test/syscalls/linux/affinity.cc | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/test/syscalls/linux/affinity.cc b/test/syscalls/linux/affinity.cc new file mode 100644 index 000000000..128364c34 --- /dev/null +++ b/test/syscalls/linux/affinity.cc @@ -0,0 +1,242 @@ +// 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 <sched.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "absl/strings/str_split.h" +#include "test/util/cleanup.h" +#include "test/util/fs_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { +namespace { + +// These tests are for both the sched_getaffinity(2) and sched_setaffinity(2) +// syscalls. +class AffinityTest : public ::testing::Test { + protected: + void SetUp() override { + EXPECT_THAT( + // Needs use the raw syscall to get the actual size. + cpuset_size_ = syscall(SYS_sched_getaffinity, /*pid=*/0, + sizeof(cpu_set_t), &mask_), + SyscallSucceeds()); + // Lots of tests rely on having more than 1 logical processor available. + EXPECT_GT(CPU_COUNT(&mask_), 1); + } + + static PosixError ClearLowestBit(cpu_set_t* mask, size_t cpus) { + const size_t mask_size = CPU_ALLOC_SIZE(cpus); + for (size_t n = 0; n < cpus; ++n) { + if (CPU_ISSET_S(n, mask_size, mask)) { + CPU_CLR_S(n, mask_size, mask); + return NoError(); + } + } + return PosixError(EINVAL, "No bit to clear, mask is empty"); + } + + PosixError ClearLowestBit() { return ClearLowestBit(&mask_, CPU_SETSIZE); } + + // Stores the initial cpu mask for this process. + cpu_set_t mask_ = {}; + int cpuset_size_ = 0; +}; + +// sched_getaffinity(2) is implemented. +TEST_F(AffinityTest, SchedGetAffinityImplemented) { + EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), + SyscallSucceeds()); +} + +// PID is not found. +TEST_F(AffinityTest, SchedGetAffinityInvalidPID) { + // Flaky, but it's tough to avoid a race condition when finding an unused pid + EXPECT_THAT(sched_getaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), + SyscallFailsWithErrno(ESRCH)); +} + +// PID is not found. +TEST_F(AffinityTest, SchedSetAffinityInvalidPID) { + // Flaky, but it's tough to avoid a race condition when finding an unused pid + EXPECT_THAT(sched_setaffinity(/*pid=*/INT_MAX - 1, sizeof(cpu_set_t), &mask_), + SyscallFailsWithErrno(ESRCH)); +} + +TEST_F(AffinityTest, SchedSetAffinityZeroMask) { + CPU_ZERO(&mask_); + EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), + SyscallFailsWithErrno(EINVAL)); +} + +// N.B. This test case relies on cpuset_size_ larger than the actual number of +// of all existing CPUs. Check your machine if the test fails. +TEST_F(AffinityTest, SchedSetAffinityNonexistentCPUDropped) { + cpu_set_t mask = mask_; + // Add a nonexistent CPU. + // + // The number needs to be larger than the possible number of CPU available, + // but smaller than the number of the CPU that the kernel claims to support -- + // it's implicitly returned by raw sched_getaffinity syscall. + CPU_SET(cpuset_size_ * 8 - 1, &mask); + EXPECT_THAT( + // Use raw syscall because it will be rejected by the libc wrapper + // otherwise. + syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask), + SyscallSucceeds()) + << "failed with cpumask : " << CPUSetToString(mask) + << ", cpuset_size_ : " << cpuset_size_; + cpu_set_t newmask; + EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), + SyscallSucceeds()); + EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) + << "got: " << CPUSetToString(newmask) + << " != expected: " << CPUSetToString(mask_); +} + +TEST_F(AffinityTest, SchedSetAffinityOnlyNonexistentCPUFails) { + // Make an empty cpu set. + CPU_ZERO(&mask_); + // Add a nonexistent CPU. + // + // The number needs to be larger than the possible number of CPU available, + // but smaller than the number of the CPU that the kernel claims to support -- + // it's implicitly returned by raw sched_getaffinity syscall. + int cpu = cpuset_size_ * 8 - 1; + if (cpu <= NumCPUs()) { + GTEST_SKIP() << "Skipping test: cpu " << cpu << " exists"; + } + CPU_SET(cpu, &mask_); + EXPECT_THAT( + // Use raw syscall because it will be rejected by the libc wrapper + // otherwise. + syscall(SYS_sched_setaffinity, /*pid=*/0, sizeof(cpu_set_t), &mask_), + SyscallFailsWithErrno(EINVAL)); +} + +TEST_F(AffinityTest, SchedSetAffinityInvalidSize) { + EXPECT_GT(cpuset_size_, 0); + // Not big enough. + EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ - 1, &mask_), + SyscallFailsWithErrno(EINVAL)); + // Not a multiple of word size. + EXPECT_THAT(sched_getaffinity(/*pid=*/0, cpuset_size_ + 1, &mask_), + SyscallFailsWithErrno(EINVAL)); +} + +TEST_F(AffinityTest, Sanity) { + ASSERT_NO_ERRNO(ClearLowestBit()); + EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), + SyscallSucceeds()); + cpu_set_t newmask; + EXPECT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &newmask), + SyscallSucceeds()); + EXPECT_TRUE(CPU_EQUAL(&mask_, &newmask)) + << "got: " << CPUSetToString(newmask) + << " != expected: " << CPUSetToString(mask_); +} + +TEST_F(AffinityTest, NewThread) { + SKIP_IF(CPU_COUNT(&mask_) < 3); + ASSERT_NO_ERRNO(ClearLowestBit()); + ASSERT_NO_ERRNO(ClearLowestBit()); + EXPECT_THAT(sched_setaffinity(/*pid=*/0, sizeof(cpu_set_t), &mask_), + SyscallSucceeds()); + ScopedThread([this]() { + cpu_set_t child_mask; + ASSERT_THAT(sched_getaffinity(/*pid=*/0, sizeof(cpu_set_t), &child_mask), + SyscallSucceeds()); + ASSERT_TRUE(CPU_EQUAL(&child_mask, &mask_)) + << "child cpu mask: " << CPUSetToString(child_mask) + << " != parent cpu mask: " << CPUSetToString(mask_); + }); +} + +TEST_F(AffinityTest, ConsistentWithProcCpuInfo) { + // Count how many cpus are shown in /proc/cpuinfo. + std::string cpuinfo = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/cpuinfo")); + int count = 0; + for (auto const& line : absl::StrSplit(cpuinfo, '\n')) { + if (absl::StartsWith(line, "processor")) { + count++; + } + } + EXPECT_GE(count, CPU_COUNT(&mask_)); +} + +TEST_F(AffinityTest, ConsistentWithProcStat) { + // Count how many cpus are shown in /proc/stat. + std::string stat = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/stat")); + int count = 0; + for (auto const& line : absl::StrSplit(stat, '\n')) { + if (absl::StartsWith(line, "cpu") && !absl::StartsWith(line, "cpu ")) { + count++; + } + } + EXPECT_GE(count, CPU_COUNT(&mask_)); +} + +TEST_F(AffinityTest, SmallCpuMask) { + const int num_cpus = NumCPUs(); + const size_t mask_size = CPU_ALLOC_SIZE(num_cpus); + cpu_set_t* mask = CPU_ALLOC(num_cpus); + ASSERT_NE(mask, nullptr); + const auto free_mask = Cleanup([&] { CPU_FREE(mask); }); + + CPU_ZERO_S(mask_size, mask); + ASSERT_THAT(sched_getaffinity(0, mask_size, mask), SyscallSucceeds()); +} + +TEST_F(AffinityTest, LargeCpuMask) { + // Allocate mask bigger than cpu_set_t normally allocates. + const size_t cpus = CPU_SETSIZE * 8; + const size_t mask_size = CPU_ALLOC_SIZE(cpus); + + cpu_set_t* large_mask = CPU_ALLOC(cpus); + auto free_mask = Cleanup([large_mask] { CPU_FREE(large_mask); }); + CPU_ZERO_S(mask_size, large_mask); + + // Check that get affinity with large mask works as expected. + ASSERT_THAT(sched_getaffinity(/*pid=*/0, mask_size, large_mask), + SyscallSucceeds()); + EXPECT_TRUE(CPU_EQUAL(&mask_, large_mask)) + << "got: " << CPUSetToString(*large_mask, cpus) + << " != expected: " << CPUSetToString(mask_); + + // Check that set affinity with large mask works as expected. + ASSERT_NO_ERRNO(ClearLowestBit(large_mask, cpus)); + EXPECT_THAT(sched_setaffinity(/*pid=*/0, mask_size, large_mask), + SyscallSucceeds()); + + cpu_set_t* new_mask = CPU_ALLOC(cpus); + auto free_new_mask = Cleanup([new_mask] { CPU_FREE(new_mask); }); + CPU_ZERO_S(mask_size, new_mask); + EXPECT_THAT(sched_getaffinity(/*pid=*/0, mask_size, new_mask), + SyscallSucceeds()); + + EXPECT_TRUE(CPU_EQUAL_S(mask_size, large_mask, new_mask)) + << "got: " << CPUSetToString(*new_mask, cpus) + << " != expected: " << CPUSetToString(*large_mask, cpus); +} + +} // namespace +} // namespace testing +} // namespace gvisor |