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:
committed by
Adrien Destugues
parent
0dc6805e00
commit
a959262cd0
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user