serenity/Tests/Kernel/TestAnonymousMmap.cpp
brody-qq 1ac1db5cf8 Tests/Kernel: Add new tests for anonymous mmaps
This adds the following tests:
* test that writes to a MAP_SHARED | MAP_ANONYMOUS mmap region are
  visible in processes sharing the region.
* test that writes to a MAP_PRIVATE | MAP_ANONYMOUS mmap region are not
  visible in processes sharing the region.
* test that multi-region mmaps backed by cow pages work correctly.
2024-07-12 08:52:06 -04:00

115 lines
3.4 KiB
C++

/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
static void check_if_page_zeroed(char* ptr, size_t page)
{
for (size_t j = 0; j < PAGE_SIZE; ++j)
EXPECT(ptr[page * PAGE_SIZE + j] == 0);
}
TEST_CASE(shared_anonymous_mmap)
{
size_t pages = 100;
size_t len = pages * PAGE_SIZE;
char* shared_ptr = (char*)mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
EXPECT(shared_ptr != MAP_FAILED);
size_t forks = 20;
for (size_t i = 0; i < forks; ++i) {
pid_t pid = fork();
VERIFY(pid != -1);
if (pid == 0) {
// sleep so that multiple child processes can be created before performing the writes
sleep(1);
char c = '$' + (char)i;
shared_ptr[i * PAGE_SIZE] = c;
exit(EXIT_SUCCESS);
}
}
// wait for all child processes to exit
for (size_t i = 0; i < forks; ++i)
wait(nullptr);
// check that writes to the shared anonymous mmap in the multiple child processes are visible
for (size_t i = 0; i < forks; ++i) {
char c = '$' + (char)i;
EXPECT(shared_ptr[i * PAGE_SIZE] == c);
}
// check that the pages that haven't been written to are zeroed
for (size_t i = forks; i < pages; ++i)
check_if_page_zeroed(shared_ptr, i);
}
TEST_CASE(private_anonymous_mmap)
{
size_t pages = 100;
size_t len = pages * PAGE_SIZE;
char* private_ptr = (char*)mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
EXPECT(private_ptr != MAP_FAILED);
pid_t pid = fork();
VERIFY(pid != -1);
if (pid == 0) {
// write to all pages of the mmap region
for (size_t i = 0; i < pages; ++i)
private_ptr[i * PAGE_SIZE] = '$';
exit(EXIT_SUCCESS);
} else {
wait(NULL);
// check that the writes that happened in the child process are not visible, all pages should be zeroed
for (size_t i = 0; i < pages; ++i)
check_if_page_zeroed(private_ptr, i);
}
}
TEST_CASE(test_that_partial_munmap_does_not_break_cow)
{
size_t pages = 3;
size_t len = pages * PAGE_SIZE;
char* map = (char*)mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
EXPECT(map != MAP_FAILED);
// make writes before forking so the pages are marked as cow
map[0] = 'A';
map[PAGE_SIZE] = 'B';
map[2 * PAGE_SIZE] = 'C';
pid_t pid = fork();
VERIFY(pid != -1);
if (pid == 0) {
EXPECT(map[0] == 'A');
EXPECT(map[PAGE_SIZE] == 'B');
EXPECT(map[2 * PAGE_SIZE] == 'C');
// unmap part of the range, this should not interfere with the cow status of map's pages
int rc = munmap(map + PAGE_SIZE, PAGE_SIZE);
VERIFY(rc != -1);
EXPECT(map[0] == 'A');
EXPECT(map[2 * PAGE_SIZE] == 'C');
// write to map, these writes should be local to this child process
map[0] = '!';
map[2 * PAGE_SIZE] = '!';
exit(EXIT_SUCCESS);
} else {
wait(NULL);
// test that the writes made in the child process are not visible in this parent process
EXPECT(map[0] == 'A');
EXPECT(map[PAGE_SIZE] == 'B');
EXPECT(map[2 * PAGE_SIZE] == 'C');
}
}