blob: 063c8b6f078286c93b8ac874294fdd44c7f49665 [file] [log] [blame] [edit]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include <string_view>
#include <gtest/gtest.h>
#include "src/lib/files/file.h"
#include "src/lib/fxl/strings/split_string.h"
#include "src/starnix/tests/syscalls/cpp/proc_test_base.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
class MlockProcTest : public ProcTestBase {
protected:
std::optional<size_t> GetVmLckInKb() {
std::string status;
if (!files::ReadFileToString(proc_path() + "/self/status", &status)) {
return std::nullopt;
}
const std::string_view prefix = "VmLck";
for (const auto& line :
fxl::SplitString(status, "\n", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty)) {
std::vector<std::string_view> fields =
fxl::SplitString(line, ":", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (fields[0] == prefix) {
if (fields.size() != 2) {
return std::nullopt;
}
return test_helper::parse_field_in_kb(fields[1]);
}
}
return std::nullopt;
}
};
TEST_F(MlockProcTest, UnalignedAddress) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
auto mapping = test_helper::ScopedMMap::MMap(nullptr, page_size, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(mapping.is_ok()) << mapping.error_value();
auto mapped_page = reinterpret_cast<uint8_t*>(mapping->mapping());
ASSERT_EQ(mlock(reinterpret_cast<void*>(mapped_page + 10), page_size - 10), 0) << strerror(errno);
std::string smaps;
ASSERT_TRUE(files::ReadFileToString(proc_path() + "/self/smaps", &smaps));
auto smap = test_helper::find_memory_mapping_ext(reinterpret_cast<uintptr_t>(mapped_page), smaps);
ASSERT_NE(smap, std::nullopt);
ASSERT_TRUE(smap->ContainsFlag("lo")) << *smap;
}
TEST_F(MlockProcTest, UnlignedLength) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
auto mapping = test_helper::ScopedMMap::MMap(nullptr, page_size, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(mapping.is_ok()) << mapping.error_value();
ASSERT_EQ(mlock(mapping->mapping(), page_size - 10), 0) << strerror(errno);
std::string smaps;
ASSERT_TRUE(files::ReadFileToString(proc_path() + "/self/smaps", &smaps));
auto smap =
test_helper::find_memory_mapping_ext(reinterpret_cast<uintptr_t>(mapping->mapping()), smaps);
ASSERT_NE(smap, std::nullopt);
ASSERT_TRUE(smap->ContainsFlag("lo")) << *smap;
}
TEST_F(MlockProcTest, RoundingIsCorrect) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
auto mapping = test_helper::ScopedMMap::MMap(nullptr, page_size * 2, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(mapping.is_ok()) << mapping.error_value();
// Ask to lock two bytes, starting one byte before the page boundary. This should produce a 2-page
// lock.
auto midpoint = reinterpret_cast<uint8_t*>(mapping->mapping()) + page_size;
ASSERT_EQ(mlock(midpoint - 1, 2), 0) << strerror(errno);
std::string smaps;
ASSERT_TRUE(files::ReadFileToString(proc_path() + "/self/smaps", &smaps));
auto smap =
test_helper::find_memory_mapping_ext(reinterpret_cast<uintptr_t>(mapping->mapping()), smaps);
ASSERT_NE(smap, std::nullopt);
ASSERT_EQ(smap->start, reinterpret_cast<uintptr_t>(mapping->mapping()));
ASSERT_EQ(smap->end, reinterpret_cast<uintptr_t>(mapping->mapping()) + (2 * page_size));
ASSERT_TRUE(smap->ContainsFlag("lo")) << *smap;
}
TEST_F(MlockProcTest, SplitMapping) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
auto mapping = test_helper::ScopedMMap::MMap(nullptr, page_size * 3, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(mapping.is_ok()) << mapping.error_value();
auto first_page = reinterpret_cast<uint8_t*>(mapping->mapping());
auto first_addr = reinterpret_cast<uintptr_t>(first_page);
auto second_page = reinterpret_cast<void*>(first_page + page_size);
auto second_addr = reinterpret_cast<uintptr_t>(second_page);
auto third_addr = second_addr + page_size;
// Lock the middle page of the mapping.
ASSERT_EQ(mlock(second_page, page_size), 0) << strerror(errno);
std::string smaps;
ASSERT_TRUE(files::ReadFileToString(proc_path() + "/self/smaps", &smaps));
auto first_mapping = test_helper::find_memory_mapping_ext(first_addr, smaps);
ASSERT_NE(first_mapping, std::nullopt);
ASSERT_EQ(first_mapping->start, first_addr) << *first_mapping;
ASSERT_EQ(first_mapping->end, second_addr) << *first_mapping;
ASSERT_FALSE(first_mapping->ContainsFlag("lo")) << *first_mapping;
auto second_mapping = test_helper::find_memory_mapping_ext(second_addr, smaps);
ASSERT_NE(second_mapping, std::nullopt);
ASSERT_EQ(second_mapping->start, second_addr) << *second_mapping;
ASSERT_EQ(second_mapping->end, third_addr) << *second_mapping;
ASSERT_TRUE(second_mapping->ContainsFlag("lo")) << *second_mapping;
auto third_mapping = test_helper::find_memory_mapping_ext(third_addr, smaps);
ASSERT_NE(third_mapping, std::nullopt);
ASSERT_EQ(third_mapping->start, third_addr) << *third_mapping;
ASSERT_EQ(third_mapping->end, third_addr + page_size) << *third_mapping;
ASSERT_FALSE(third_mapping->ContainsFlag("lo")) << *third_mapping;
}
TEST_F(MlockProcTest, MlockOnePageIncreasesVmLck) {
const size_t page_size = SAFE_SYSCALL(sysconf(_SC_PAGE_SIZE));
const size_t map_size = 2 * page_size;
// Map 2 private-anonymous pages.
auto mapping = test_helper::ScopedMMap::MMap(nullptr, map_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(mapping.is_ok()) << mapping.error_value();
// Touch them to ensure non-zero number of committed bytes.
reinterpret_cast<volatile char*>(mapping->mapping())[0] = 'a';
reinterpret_cast<volatile char*>(mapping->mapping())[page_size] = 'a';
// Record the `VmLck` value.
auto vmlck_before = GetVmLckInKb();
ASSERT_NE(vmlck_before, std::nullopt);
// Test that locking any of the two pages results in the increase of VmLck by one page
for (int i = 0; i < 2; i++) {
// Lock one page.
ASSERT_EQ(mlock(reinterpret_cast<uint8_t*>(mapping->mapping()) + (i * page_size), page_size), 0)
<< strerror(errno);
// Record the `VmLck` value again.
auto vmlck_after = GetVmLckInKb();
ASSERT_NE(vmlck_after, std::nullopt);
// Check that the new value is greater than the base one by exactly one page.
EXPECT_GT(*vmlck_after, *vmlck_before);
EXPECT_EQ(*vmlck_after - *vmlck_before, page_size / 1024);
ASSERT_EQ(munlock(reinterpret_cast<uint8_t*>(mapping->mapping()) + (i * page_size), page_size),
0)
<< strerror(errno);
}
}