-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
2 changed files
with
116 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* 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'); | ||
} | ||
} |