implement mlock(), munlock()

Change-Id: I2f04b8986d2ed32bb4d30d238d668e21a1505778
Reviewed-on: https://review.haiku-os.org/c/haiku/+/1991
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Adrien Destugues
2020-12-03 07:58:05 +00:00
committed by Adrien Destugues
parent 0dc6805e00
commit a959262cd0
7 changed files with 324 additions and 1 deletions
+3
View File
@@ -61,6 +61,9 @@ int msync(void* address, size_t length, int flags);
int madvise(void* address, size_t length, int advice);
int posix_madvise(void* address, size_t length, int advice);
int mlock(const void* address, size_t length);
int munlock(const void* address, size_t length);
int shm_open(const char* name, int openMode, mode_t permissions);
int shm_unlink(const char* name);
+7
View File
@@ -26,6 +26,8 @@
#include <util/KernelReferenceable.h>
#include <util/list.h>
#include <SupportDefs.h>
enum additional_thread_state {
THREAD_STATE_FREE_ON_RESCHED = 7, // free the thread structure upon reschedule
@@ -64,6 +66,9 @@ struct select_info;
struct user_thread; // defined in libroot/user_thread.h
struct VMAddressSpace;
struct xsi_sem_context; // defined in xsi_semaphore.cpp
struct LockedPages;
typedef DoublyLinkedList<LockedPages> LockedPagesList;
namespace Scheduler {
struct ThreadData;
@@ -237,6 +242,8 @@ struct Team : TeamThreadIteratorEntry<team_id>, KernelReferenceable,
struct team_death_entry *death_entry; // protected by fLock
struct list dead_threads;
LockedPagesList locked_pages_list;
// protected by the team's fLock
team_dead_children dead_children;
team_job_control_children stopped_children;
+3
View File
@@ -175,6 +175,9 @@ status_t _user_memory_advice(void* address, size_t size, uint32 advice);
status_t _user_get_memory_properties(team_id teamID, const void *address,
uint32 *_protected, uint32 *_lock);
status_t _user_mlock(const void* address, size_t size);
status_t _user_munlock(const void* address, size_t size);
area_id _user_area_for(void *address);
area_id _user_find_area(const char *name);
status_t _user_get_area_info(area_id area, area_info *info);
+3
View File
@@ -448,6 +448,9 @@ extern status_t _kern_memory_advice(void *address, size_t size,
extern status_t _kern_get_memory_properties(team_id teamID,
const void *address, uint32* _protected, uint32* _lock);
extern status_t _kern_mlock(const void* address, size_t size);
extern status_t _kern_munlock(const void* address, size_t size);
/* kernel port functions */
extern port_id _kern_create_port(int32 queue_length, const char *name);
extern status_t _kern_close_port(port_id id);
+258 -1
View File
@@ -5103,7 +5103,7 @@ fill_area_info(struct VMArea* area, area_info* info, size_t size)
info->address = (void*)area->Base();
info->size = area->Size();
info->protection = area->protection;
info->lock = B_FULL_LOCK;
info->lock = area->wiring;
info->team = area->address_space->ID();
info->copy_count = 0;
info->in_count = 0;
@@ -6882,6 +6882,263 @@ _user_get_memory_properties(team_id teamID, const void* address,
}
// An ordered list of non-overlapping ranges to track mlock/munlock locking.
// It is allowed to call mlock/munlock in unbalanced ways (lock a range
// multiple times, unlock a part of it, lock several consecutive ranges and
// unlock them in one go, etc). However the low level lock_memory and
// unlock_memory calls require the locks/unlocks to be balanced (you lock a
// fixed range, and then unlock exactly the same range). This list allows to
// keep track of what was locked exactly so we can unlock the correct things.
struct LockedPages : DoublyLinkedListLinkImpl<LockedPages> {
addr_t start;
addr_t end;
status_t LockMemory()
{
return lock_memory((void*)start, end - start, 0);
}
status_t UnlockMemory()
{
return unlock_memory((void*)start, end - start, 0);
}
status_t Move(addr_t start, addr_t end)
{
status_t result = lock_memory((void*)start, end - start, 0);
if (result != B_OK)
return result;
result = UnlockMemory();
if (result != B_OK) {
// What can we do if the unlock fails?
panic("Failed to unlock memory: %s", strerror(result));
return result;
}
this->start = start;
this->end = end;
return B_OK;
}
};
status_t
_user_mlock(const void* address, size_t size) {
// Maybe there's nothing to do, in which case, do nothing
if (size == 0)
return B_OK;
// Make sure the address is multiple of B_PAGE_SIZE (POSIX allows us to
// reject the call otherwise)
if ((addr_t)address % B_PAGE_SIZE != 0)
return EINVAL;
size = ROUNDUP(size, B_PAGE_SIZE);
addr_t endAddress = (addr_t)address + size;
// Pre-allocate a linked list element we may need (it's simpler to do it
// now than run out of memory in the midle of changing things)
LockedPages* newRange = new(std::nothrow) LockedPages();
if (newRange == NULL)
return ENOMEM;
// Get and lock the team
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
teamLocker.Lock();
status_t error = B_OK;
LockedPagesList* lockedPages = &team->locked_pages_list;
// Locate the first locked range possibly overlapping ours
LockedPages* currentRange = lockedPages->Head();
while (currentRange != NULL && currentRange->end <= (addr_t)address)
currentRange = lockedPages->GetNext(currentRange);
if (currentRange == NULL || currentRange->start >= endAddress) {
// No existing range is overlapping with ours. We can just lock our
// range and stop here.
newRange->start = (addr_t)address;
newRange->end = endAddress;
error = newRange->LockMemory();
if (error != B_OK) {
delete newRange;
return error;
}
lockedPages->InsertBefore(currentRange, newRange);
return B_OK;
}
// We get here when there is at least one existing overlapping range.
if (currentRange->start <= (addr_t)address) {
if (currentRange->end >= endAddress) {
// An existing range is already fully covering the pages we need to
// lock. Nothing to do then.
delete newRange;
return B_OK;
} else {
// An existing range covers the start of the area we want to lock.
// Advance our start address to avoid it.
address = (void*)currentRange->end;
// Move on to the next range for the next step
currentRange = lockedPages->GetNext(currentRange);
}
}
// First, lock the new range
newRange->start = (addr_t)address;
newRange->end = endAddress;
error = newRange->LockMemory();
if (error != B_OK) {
delete newRange;
return error;
}
// Unlock all ranges fully overlapping with the area we need to lock
while (currentRange != NULL && currentRange->end < endAddress) {
// The existing range is fully contained inside the new one we're
// trying to lock. Delete/unlock it, and replace it with a new one
// (this limits fragmentation of the range list, and is simpler to
// manage)
error = currentRange->UnlockMemory();
if (error != B_OK) {
panic("Failed to unlock a memory range: %s", strerror(error));
newRange->UnlockMemory();
delete newRange;
return error;
}
LockedPages* temp = currentRange;
currentRange = lockedPages->GetNext(currentRange);
lockedPages->Remove(temp);
delete temp;
}
if (currentRange != NULL) {
// One last range may cover the end of the area we're trying to lock
if (currentRange->start == (addr_t)address) {
// In case two overlapping ranges (one at the start and the other
// at the end) already cover the area we're after, there's nothing
// more to do. So we destroy our new extra allocation
error = newRange->UnlockMemory();
delete newRange;
return error;
}
if (currentRange->start < endAddress) {
// Make sure the last range is not overlapping, by moving its start
error = currentRange->Move(endAddress, currentRange->end);
if (error != B_OK) {
panic("Failed to move a memory range: %s", strerror(error));
newRange->UnlockMemory();
delete newRange;
return error;
}
}
}
// Finally, store the new range in the locked list
lockedPages->InsertBefore(currentRange, newRange);
return B_OK;
}
status_t
_user_munlock(const void* address, size_t size) {
// Maybe there's nothing to do, in which case, do nothing
if (size == 0)
return B_OK;
// Make sure the address is multiple of B_PAGE_SIZE (POSIX allows us to
// reject the call otherwise)
if ((addr_t)address % B_PAGE_SIZE != 0)
return EINVAL;
// Round size up to the next page
size = ROUNDUP(size, B_PAGE_SIZE);
addr_t endAddress = (addr_t)address + size;
// Get and lock the team
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
teamLocker.Lock();
LockedPagesList* lockedPages = &team->locked_pages_list;
status_t error = B_OK;
// Locate the first locked range possibly overlapping ours
LockedPages* currentRange = lockedPages->Head();
while (currentRange != NULL && currentRange->end <= (addr_t)address)
currentRange = lockedPages->GetNext(currentRange);
if (currentRange == NULL || currentRange->start >= endAddress) {
// No range is intersecting, nothing to unlock
return B_OK;
}
if (currentRange->start < (addr_t)address) {
if (currentRange->end > endAddress) {
// There is a range fully covering the area we want to unlock,
// and it extends on both sides. We need to split it in two
LockedPages* newRange = new(std::nothrow) LockedPages();
if (newRange == NULL)
return ENOMEM;
newRange->start = endAddress;
newRange->end = currentRange->end;
error = newRange->LockMemory();
if (error != B_OK) {
delete newRange;
return error;
}
error = currentRange->Move(currentRange->start, (addr_t)address);
if (error != B_OK) {
delete newRange;
return error;
}
lockedPages->InsertAfter(currentRange, newRange);
return B_OK;
} else {
// There is a range that overlaps and extends before the one we
// want to unlock, we need to shrink it
error = currentRange->Move(currentRange->start, (addr_t)address);
if (error != B_OK)
return error;
}
}
while (currentRange != NULL && currentRange->end <= endAddress) {
// Unlock all fully overlapping ranges
error = currentRange->UnlockMemory();
if (error != B_OK)
return error;
LockedPages* temp = currentRange;
currentRange = lockedPages->GetNext(currentRange);
lockedPages->Remove(temp);
delete temp;
}
// Finally split the last partially overlapping range if any
if (currentRange != NULL && currentRange->start < endAddress) {
error = currentRange->Move(endAddress, currentRange->end);
if (error != B_OK)
return error;
}
return B_OK;
}
// #pragma mark -- compatibility
+14
View File
@@ -194,6 +194,20 @@ posix_madvise(void* address, size_t length, int advice)
}
int
mlock(const void* address, size_t length)
{
RETURN_AND_SET_ERRNO(_kern_mlock(address, length));
}
int
munlock(const void* address, size_t length)
{
RETURN_AND_SET_ERRNO(_kern_munlock(address, length));
}
int
shm_open(const char* name, int openMode, mode_t permissions)
{
+36
View File
@@ -0,0 +1,36 @@
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <kernel/OS.h>
#define SPACE_SIZE (B_PAGE_SIZE * 9)
int
main()
{
void* space = NULL;
area_id area = create_area("mlock test area", &space, B_EXACT_ADDRESS,
SPACE_SIZE, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
/* --------- */
int result = mlock(space + B_PAGE_SIZE, B_PAGE_SIZE * 7);
/* -xxxxxxx- */
assert(result == 0);
result = munlock(space + 2 * B_PAGE_SIZE, B_PAGE_SIZE * 5);
/* -x-----x- */
assert(result == 0);
result = mlock(space + 2 * B_PAGE_SIZE, B_PAGE_SIZE * 3);
/* -xxxx--x- */
assert(result == 0);
result = mlock(space, B_PAGE_SIZE * 9);
/* xxxxxxxxx */
assert(result == 0);
result = munlock(space + 4 * B_PAGE_SIZE, B_PAGE_SIZE * 5);
/* xxxx----- */
assert(result == 0);
result = munlock(space, B_PAGE_SIZE * 9);
assert(result == 0);
delete_area(area);
return 0;
}