Added vm_[un]wire_page(), which are essentially versions of
[un]lock_memory_etc() optimized for a single page. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@36156 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -43,6 +43,10 @@ struct VMAreaWiredRange : SinglyLinkedListLinkImpl<VMAreaWiredRange> {
|
||||
bool implicit; // range created automatically
|
||||
VMAreaUnwiredWaiterList waiters;
|
||||
|
||||
VMAreaWiredRange()
|
||||
{
|
||||
}
|
||||
|
||||
VMAreaWiredRange(addr_t base, size_t size, bool writable, bool implicit)
|
||||
:
|
||||
area(NULL),
|
||||
@@ -53,6 +57,15 @@ struct VMAreaWiredRange : SinglyLinkedListLinkImpl<VMAreaWiredRange> {
|
||||
{
|
||||
}
|
||||
|
||||
void SetTo(addr_t base, size_t size, bool writable, bool implicit)
|
||||
{
|
||||
this->area = NULL;
|
||||
this->base = base;
|
||||
this->size = size;
|
||||
this->writable = writable;
|
||||
this->implicit = implicit;
|
||||
}
|
||||
|
||||
bool IntersectsWith(addr_t base, size_t size) const
|
||||
{
|
||||
return this->base + this->size - 1 >= base
|
||||
@@ -63,6 +76,16 @@ struct VMAreaWiredRange : SinglyLinkedListLinkImpl<VMAreaWiredRange> {
|
||||
typedef SinglyLinkedList<VMAreaWiredRange> VMAreaWiredRangeList;
|
||||
|
||||
|
||||
struct VMPageWiringInfo {
|
||||
VMAreaWiredRange range;
|
||||
addr_t physicalAddress;
|
||||
// the actual physical address corresponding to
|
||||
// the virtual address passed to vm_wire_page()
|
||||
// (i.e. with in-page offset)
|
||||
vm_page* page;
|
||||
};
|
||||
|
||||
|
||||
struct VMArea {
|
||||
char* name;
|
||||
area_id id;
|
||||
|
||||
@@ -24,6 +24,7 @@ struct VMArea;
|
||||
struct VMCache;
|
||||
struct vm_page;
|
||||
struct vnode;
|
||||
struct VMPageWiringInfo;
|
||||
|
||||
|
||||
// area creation flags
|
||||
@@ -113,6 +114,9 @@ void vm_clear_map_flags(struct vm_page *page, uint32 flags);
|
||||
void vm_remove_all_page_mappings(struct vm_page *page);
|
||||
int32 vm_clear_page_mapping_accessed_flags(struct vm_page *page);
|
||||
int32 vm_remove_all_page_mappings_if_unaccessed(struct vm_page *page);
|
||||
status_t vm_wire_page(team_id team, addr_t address, bool writable,
|
||||
struct VMPageWiringInfo* info);
|
||||
void vm_unwire_page(struct VMPageWiringInfo* info);
|
||||
|
||||
status_t vm_get_physical_page(addr_t paddr, addr_t* vaddr, void** _handle);
|
||||
status_t vm_put_physical_page(addr_t vaddr, void* handle);
|
||||
|
||||
@@ -4722,6 +4722,142 @@ user_memset(void* s, char c, size_t count)
|
||||
}
|
||||
|
||||
|
||||
/*! Wires a single page at the given address.
|
||||
|
||||
\param team The team whose address space the address belongs to. Supports
|
||||
also \c B_CURRENT_TEAM. If the given address is a kernel address, the
|
||||
parameter is ignored.
|
||||
\param address address The virtual address to wire down. Does not need to
|
||||
be page aligned.
|
||||
\param writable If \c true the page shall be writable.
|
||||
\param info On success the info is filled in, among other things
|
||||
containing the physical address the given virtual one translates to.
|
||||
\return \c B_OK, when the page could be wired, another error code otherwise.
|
||||
*/
|
||||
status_t
|
||||
vm_wire_page(team_id team, addr_t address, bool writable,
|
||||
VMPageWiringInfo* info)
|
||||
{
|
||||
addr_t pageAddress = ROUNDDOWN((addr_t)address, B_PAGE_SIZE);
|
||||
info->range.SetTo(pageAddress, B_PAGE_SIZE, writable, false);
|
||||
|
||||
// compute the page protection that is required
|
||||
bool isUser = IS_USER_ADDRESS(address);
|
||||
uint32 requiredProtection = PAGE_PRESENT
|
||||
| B_KERNEL_READ_AREA | (isUser ? B_READ_AREA : 0);
|
||||
if (writable)
|
||||
requiredProtection |= B_KERNEL_WRITE_AREA | (isUser ? B_WRITE_AREA : 0);
|
||||
|
||||
// get and read lock the address space
|
||||
VMAddressSpace* addressSpace = NULL;
|
||||
if (isUser) {
|
||||
if (team == B_CURRENT_TEAM)
|
||||
addressSpace = VMAddressSpace::GetCurrent();
|
||||
else
|
||||
addressSpace = VMAddressSpace::Get(team);
|
||||
} else
|
||||
addressSpace = VMAddressSpace::GetKernel();
|
||||
if (addressSpace == NULL)
|
||||
return B_ERROR;
|
||||
|
||||
AddressSpaceReadLocker addressSpaceLocker(addressSpace, true);
|
||||
|
||||
VMTranslationMap* map = addressSpace->TranslationMap();
|
||||
status_t error = B_OK;
|
||||
|
||||
// get the area
|
||||
VMArea* area = addressSpace->LookupArea(pageAddress);
|
||||
if (area == NULL) {
|
||||
addressSpace->Put();
|
||||
return B_BAD_ADDRESS;
|
||||
}
|
||||
|
||||
// Lock the area's top cache. This is a requirement for VMArea::Wire().
|
||||
VMCacheChainLocker cacheChainLocker(vm_area_get_locked_cache(area));
|
||||
|
||||
// mark the area range wired
|
||||
area->Wire(&info->range);
|
||||
|
||||
// Lock the area's cache chain and the translation map. Needed to look
|
||||
// up the page and play with its wired count.
|
||||
cacheChainLocker.LockAllSourceCaches();
|
||||
map->Lock();
|
||||
|
||||
addr_t physicalAddress;
|
||||
uint32 flags;
|
||||
vm_page* page;
|
||||
if (map->Query(pageAddress, &physicalAddress, &flags) == B_OK
|
||||
&& (flags & requiredProtection) == requiredProtection
|
||||
&& (page = vm_lookup_page(physicalAddress / B_PAGE_SIZE))
|
||||
!= NULL) {
|
||||
// Already mapped with the correct permissions -- just increment
|
||||
// the page's wired count.
|
||||
increment_page_wired_count(page);
|
||||
|
||||
map->Unlock();
|
||||
cacheChainLocker.Unlock();
|
||||
addressSpaceLocker.Unlock();
|
||||
} else {
|
||||
// Let vm_soft_fault() map the page for us, if possible. We need
|
||||
// to fully unlock to avoid deadlocks. Since we have already
|
||||
// wired the area itself, nothing disturbing will happen with it
|
||||
// in the meantime.
|
||||
map->Unlock();
|
||||
cacheChainLocker.Unlock();
|
||||
addressSpaceLocker.Unlock();
|
||||
|
||||
error = vm_soft_fault(addressSpace, pageAddress, writable, isUser,
|
||||
&page, &info->range);
|
||||
|
||||
if (error != B_OK) {
|
||||
// The page could not be mapped -- clean up.
|
||||
VMCache* cache = vm_area_get_locked_cache(area);
|
||||
area->Unwire(&info->range);
|
||||
cache->ReleaseRefAndUnlock();
|
||||
addressSpace->Put();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
info->physicalAddress = page->physical_page_number * B_PAGE_SIZE
|
||||
+ address % B_PAGE_SIZE;
|
||||
info->page = page;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
/*! Unwires a single page previously wired via vm_wire_page().
|
||||
|
||||
\param info The same object passed to vm_wire_page() before.
|
||||
*/
|
||||
void
|
||||
vm_unwire_page(VMPageWiringInfo* info)
|
||||
{
|
||||
// lock the address space
|
||||
VMArea* area = info->range.area;
|
||||
AddressSpaceReadLocker addressSpaceLocker(area->address_space, false);
|
||||
// takes over our reference
|
||||
|
||||
// lock the top cache
|
||||
VMCache* cache = vm_area_get_locked_cache(area);
|
||||
VMCacheChainLocker cacheChainLocker(cache);
|
||||
|
||||
if (info->page->Cache() != cache) {
|
||||
// The page is not in the top cache, so we lock the whole cache chain
|
||||
// before touching the page's wired count.
|
||||
cacheChainLocker.LockAllSourceCaches();
|
||||
}
|
||||
|
||||
decrement_page_wired_count(info->page);
|
||||
|
||||
// remove the wired range from the range
|
||||
area->Unwire(&info->range);
|
||||
|
||||
cacheChainLocker.Unlock();
|
||||
}
|
||||
|
||||
|
||||
/*! Wires down the given address range in the specified team's address space.
|
||||
|
||||
If successful the function
|
||||
|
||||
Reference in New Issue
Block a user