* Added a simple mechanism to wait for events to VMCache. WaitForPageEvents()
waits for certain events on a given page, NotifyPageEvents() wakes up waiting threads respectively. * Used the new feature instead of condition variables for waiting on busy pages. We save publishing and unpublishing of a condition variable whenever a page is marked busy. There's only something to do, if there's at least one thread waiting in the list of the respective cache. The general assumption is that this is only rarely the case and even if it happens, there should be only very few threads. * Added an apparently missing notification in cache_io(). At least I didn't see the reason for it not being there. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@34537 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -27,6 +27,11 @@ enum {
|
||||
CACHE_TYPE_NULL
|
||||
};
|
||||
|
||||
enum {
|
||||
PAGE_EVENT_NOT_BUSY = 0x01 // page not busy anymore
|
||||
};
|
||||
|
||||
|
||||
struct VMCachePagesTreeDefinition {
|
||||
typedef page_num_t KeyType;
|
||||
typedef vm_page NodeType;
|
||||
@@ -82,6 +87,12 @@ public:
|
||||
void ReleaseRefAndUnlock()
|
||||
{ ReleaseRefLocked(); Unlock(); }
|
||||
|
||||
void WaitForPageEvents(vm_page* page, uint32 events,
|
||||
bool relock);
|
||||
void NotifyPageEvents(vm_page* page, uint32 events)
|
||||
{ if (fPageEventWaiters != NULL)
|
||||
_NotifyPageEvents(page, events); }
|
||||
|
||||
vm_page* LookupPage(off_t offset);
|
||||
void InsertPage(vm_page* page, off_t offset);
|
||||
void RemovePage(vm_page* page);
|
||||
@@ -153,6 +164,11 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct PageEventWaiter;
|
||||
|
||||
private:
|
||||
void _NotifyPageEvents(vm_page* page, uint32 events);
|
||||
|
||||
inline bool _IsMergeable() const;
|
||||
|
||||
void _MergeWithOnlyConsumer();
|
||||
@@ -161,6 +177,7 @@ private:
|
||||
private:
|
||||
int32 fRefCount;
|
||||
mutex fLock;
|
||||
PageEventWaiter* fPageEventWaiters;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+9
-23
@@ -114,7 +114,6 @@ PrecacheIO::PrecacheIO(file_cache_ref* ref, off_t offset, size_t size)
|
||||
fRef(ref),
|
||||
fCache(ref->cache),
|
||||
fPages(NULL),
|
||||
fBusyConditions(NULL),
|
||||
fVecs(NULL),
|
||||
fOffset(offset),
|
||||
fVecCount(0),
|
||||
@@ -128,7 +127,6 @@ PrecacheIO::PrecacheIO(file_cache_ref* ref, off_t offset, size_t size)
|
||||
PrecacheIO::~PrecacheIO()
|
||||
{
|
||||
delete[] fPages;
|
||||
delete[] fBusyConditions;
|
||||
delete[] fVecs;
|
||||
fCache->ReleaseRefLocked();
|
||||
}
|
||||
@@ -144,10 +142,6 @@ PrecacheIO::Prepare()
|
||||
if (fPages == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
fBusyConditions = new(std::nothrow) ConditionVariable[fPageCount];
|
||||
if (fBusyConditions == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
fVecs = new(std::nothrow) iovec[fPageCount];
|
||||
if (fVecs == NULL)
|
||||
return B_NO_MEMORY;
|
||||
@@ -159,7 +153,6 @@ PrecacheIO::Prepare()
|
||||
if (page == NULL)
|
||||
break;
|
||||
|
||||
fBusyConditions[i].Publish(page, "page");
|
||||
fCache->InsertPage(page, fOffset + pos);
|
||||
|
||||
add_to_iovec(fVecs, fVecCount, fPageCount,
|
||||
@@ -170,7 +163,7 @@ PrecacheIO::Prepare()
|
||||
if (i != fPageCount) {
|
||||
// allocating pages failed
|
||||
while (i-- > 0) {
|
||||
fBusyConditions[i].Unpublish();
|
||||
fCache->NotifyPageEvents(fPages[i], PAGE_EVENT_NOT_BUSY);
|
||||
fCache->RemovePage(fPages[i]);
|
||||
vm_page_set_state(fPages[i], PAGE_STATE_FREE);
|
||||
}
|
||||
@@ -215,12 +208,12 @@ PrecacheIO::IOFinished(status_t status, bool partialTransfer,
|
||||
}
|
||||
|
||||
fPages[i]->state = PAGE_STATE_ACTIVE;
|
||||
fBusyConditions[i].Unpublish();
|
||||
fCache->NotifyPageEvents(fPages[i], PAGE_EVENT_NOT_BUSY);
|
||||
}
|
||||
|
||||
// Free pages after failed I/O
|
||||
for (uint32 i = pagesTransferred; i < fPageCount; i++) {
|
||||
fBusyConditions[i].Unpublish();
|
||||
fCache->NotifyPageEvents(fPages[i], PAGE_EVENT_NOT_BUSY);
|
||||
fCache->RemovePage(fPages[i]);
|
||||
vm_page_set_state(fPages[i], PAGE_STATE_FREE);
|
||||
}
|
||||
@@ -383,7 +376,6 @@ read_into_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
|
||||
size_t numBytes = PAGE_ALIGN(pageOffset + bufferSize);
|
||||
vm_page* pages[MAX_IO_VECS];
|
||||
ConditionVariable busyConditions[MAX_IO_VECS];
|
||||
int32 pageIndex = 0;
|
||||
|
||||
// allocate pages for the cache and mark them busy
|
||||
@@ -393,8 +385,6 @@ read_into_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
if (page == NULL)
|
||||
panic("no more pages!");
|
||||
|
||||
busyConditions[pageIndex - 1].Publish(page, "page");
|
||||
|
||||
cache->InsertPage(page, offset + pos);
|
||||
|
||||
add_to_iovec(vecs, vecCount, MAX_IO_VECS,
|
||||
@@ -417,7 +407,7 @@ read_into_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
cache->Lock();
|
||||
|
||||
for (int32 i = 0; i < pageIndex; i++) {
|
||||
busyConditions[i].Unpublish();
|
||||
cache->NotifyPageEvents(pages[i], PAGE_EVENT_NOT_BUSY);
|
||||
cache->RemovePage(pages[i]);
|
||||
vm_page_set_state(pages[i], PAGE_STATE_FREE);
|
||||
}
|
||||
@@ -448,7 +438,7 @@ read_into_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
for (int32 i = pageIndex; i-- > 0;) {
|
||||
pages[i]->state = PAGE_STATE_ACTIVE;
|
||||
|
||||
busyConditions[i].Unpublish();
|
||||
cache->NotifyPageEvents(pages[i], PAGE_EVENT_NOT_BUSY);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
@@ -504,7 +494,6 @@ write_to_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
vm_page* pages[MAX_IO_VECS];
|
||||
int32 pageIndex = 0;
|
||||
status_t status = B_OK;
|
||||
ConditionVariable busyConditions[MAX_IO_VECS];
|
||||
|
||||
// ToDo: this should be settable somewhere
|
||||
bool writeThrough = false;
|
||||
@@ -518,7 +507,6 @@ write_to_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
// in cache_io()
|
||||
vm_page* page = pages[pageIndex++] = vm_page_allocate_page(
|
||||
PAGE_STATE_FREE, true);
|
||||
busyConditions[pageIndex - 1].Publish(page, "page");
|
||||
|
||||
ref->cache->InsertPage(page, offset + pos);
|
||||
|
||||
@@ -616,7 +604,7 @@ write_to_cache(file_cache_ref* ref, void* cookie, off_t offset,
|
||||
|
||||
// make the pages accessible in the cache
|
||||
for (int32 i = pageIndex; i-- > 0;) {
|
||||
busyConditions[i].Unpublish();
|
||||
ref->cache->NotifyPageEvents(pages[i], PAGE_EVENT_NOT_BUSY);
|
||||
|
||||
if (writeThrough)
|
||||
pages[i]->state = PAGE_STATE_ACTIVE;
|
||||
@@ -796,11 +784,7 @@ cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
|
||||
return status;
|
||||
|
||||
if (page->state == PAGE_STATE_BUSY) {
|
||||
ConditionVariableEntry entry;
|
||||
entry.Add(page);
|
||||
locker.Unlock();
|
||||
entry.Wait();
|
||||
locker.Lock();
|
||||
cache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -845,6 +829,8 @@ cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
|
||||
page->state = oldPageState;
|
||||
if (doWrite && page->state != PAGE_STATE_MODIFIED)
|
||||
vm_page_set_state(page, PAGE_STATE_MODIFIED);
|
||||
|
||||
cache->NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
|
||||
}
|
||||
|
||||
if (bytesLeft <= bytesInPage) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* Distributed under the terms of the NewOS License.
|
||||
*/
|
||||
|
||||
|
||||
#include <vm/VMCache.h>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -45,6 +46,14 @@ static mutex sCacheListLock = MUTEX_INITIALIZER("global VMCache list");
|
||||
// The lock is also needed when the debug feature is disabled.
|
||||
|
||||
|
||||
struct VMCache::PageEventWaiter {
|
||||
struct thread* thread;
|
||||
PageEventWaiter* next;
|
||||
vm_page* page;
|
||||
uint32 events;
|
||||
};
|
||||
|
||||
|
||||
#if VM_CACHE_TRACING
|
||||
|
||||
namespace VMCacheTracing {
|
||||
@@ -406,6 +415,7 @@ VMCache::Init(uint32 cacheType)
|
||||
scan_skip = 0;
|
||||
page_count = 0;
|
||||
type = cacheType;
|
||||
fPageEventWaiters = NULL;
|
||||
|
||||
#if DEBUG_CACHE_LIST
|
||||
mutex_lock(&sCacheListLock);
|
||||
@@ -622,6 +632,37 @@ VMCache::RemovePage(vm_page* page)
|
||||
}
|
||||
|
||||
|
||||
/*! Waits until one or more events happened for a given page which belongs to
|
||||
this cache.
|
||||
The cache must be locked. It will be unlocked by the method. \a relock
|
||||
specifies whether the method shall re-lock the cache before returning.
|
||||
\param page The page for which to wait.
|
||||
\param events The mask of events the caller is interested in.
|
||||
\param relock If \c true, the cache will be locked when returning,
|
||||
otherwise it won't be locked.
|
||||
*/
|
||||
void
|
||||
VMCache::WaitForPageEvents(vm_page* page, uint32 events, bool relock)
|
||||
{
|
||||
PageEventWaiter waiter;
|
||||
waiter.thread = thread_get_current_thread();
|
||||
waiter.next = fPageEventWaiters;
|
||||
waiter.page = page;
|
||||
waiter.events = events;
|
||||
|
||||
fPageEventWaiters = &waiter;
|
||||
|
||||
thread_prepare_to_block(waiter.thread, 0, THREAD_BLOCK_TYPE_OTHER,
|
||||
"cache page events");
|
||||
|
||||
Unlock();
|
||||
thread_block();
|
||||
|
||||
if (relock)
|
||||
Lock();
|
||||
}
|
||||
|
||||
|
||||
/*! Makes this case the source of the \a consumer cache,
|
||||
and adds the \a consumer to its list.
|
||||
This also grabs a reference to the source cache.
|
||||
@@ -778,11 +819,7 @@ VMCache::Resize(off_t newSize)
|
||||
// this will notify the writer to free the page
|
||||
} else {
|
||||
// wait for page to become unbusy
|
||||
ConditionVariableEntry entry;
|
||||
entry.Add(page);
|
||||
Unlock();
|
||||
entry.Wait();
|
||||
Lock();
|
||||
WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
|
||||
|
||||
// restart from the start of the list
|
||||
it = pages.GetIterator(newPageCount, true, true);
|
||||
@@ -824,11 +861,7 @@ VMCache::FlushAndRemoveAllPages()
|
||||
vm_page* page = it.Next();) {
|
||||
if (page->state == PAGE_STATE_BUSY) {
|
||||
// wait for page to become unbusy
|
||||
ConditionVariableEntry entry;
|
||||
entry.Add(page);
|
||||
Unlock();
|
||||
entry.Wait();
|
||||
Lock();
|
||||
WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
|
||||
|
||||
// restart from the start of the list
|
||||
it = pages.GetIterator();
|
||||
@@ -969,6 +1002,26 @@ VMCache::ReleaseStoreRef()
|
||||
}
|
||||
|
||||
|
||||
/*! Wakes up threads waiting for page events.
|
||||
\param page The page for which events occurred.
|
||||
\param events The mask of events that occurred.
|
||||
*/
|
||||
void
|
||||
VMCache::_NotifyPageEvents(vm_page* page, uint32 events)
|
||||
{
|
||||
PageEventWaiter** it = &fPageEventWaiters;
|
||||
while (PageEventWaiter* waiter = *it) {
|
||||
if (waiter->page == page && (waiter->events & events) != 0) {
|
||||
// remove from list and unblock
|
||||
*it = waiter->next;
|
||||
InterruptsSpinLocker threadsLocker(gThreadSpinlock);
|
||||
thread_unblock_locked(waiter->thread, B_OK);
|
||||
} else
|
||||
it = &waiter->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*! Merges the given cache with its only consumer.
|
||||
The caller must hold both the cache's and the consumer's lock. The method
|
||||
will unlock the consumer lock.
|
||||
|
||||
+10
-13
@@ -3574,7 +3574,7 @@ public:
|
||||
return fBottomCache;
|
||||
}
|
||||
|
||||
void Unlock()
|
||||
void Unlock(VMCache* exceptCache = NULL)
|
||||
{
|
||||
if (fTopCache == NULL)
|
||||
return;
|
||||
@@ -3582,7 +3582,8 @@ public:
|
||||
VMCache* cache = fTopCache;
|
||||
while (cache != NULL) {
|
||||
VMCache* nextCache = cache->source;
|
||||
cache->ReleaseRefAndUnlock();
|
||||
if (cache != exceptCache)
|
||||
cache->ReleaseRefAndUnlock();
|
||||
|
||||
if (cache == fBottomCache)
|
||||
break;
|
||||
@@ -3637,11 +3638,11 @@ struct PageFaultContext {
|
||||
cacheChainLocker.SetTo(topCache);
|
||||
}
|
||||
|
||||
void UnlockAll()
|
||||
void UnlockAll(VMCache* exceptCache = NULL)
|
||||
{
|
||||
topCache = NULL;
|
||||
addressSpaceLocker.Unlock();
|
||||
cacheChainLocker.Unlock();
|
||||
cacheChainLocker.Unlock(exceptCache);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3678,10 +3679,9 @@ fault_get_page(PageFaultContext& context)
|
||||
}
|
||||
|
||||
// page must be busy -- wait for it to become unbusy
|
||||
ConditionVariableEntry entry;
|
||||
entry.Add(page);
|
||||
context.UnlockAll();
|
||||
entry.Wait();
|
||||
context.UnlockAll(cache);
|
||||
cache->ReleaseRefLocked();
|
||||
cache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, false);
|
||||
|
||||
// restart the whole process
|
||||
context.restart = true;
|
||||
@@ -3699,9 +3699,6 @@ fault_get_page(PageFaultContext& context)
|
||||
page = vm_page_allocate_page(PAGE_STATE_FREE, true);
|
||||
cache->InsertPage(page, context.cacheOffset);
|
||||
|
||||
ConditionVariable busyCondition;
|
||||
busyCondition.Publish(page, "page");
|
||||
|
||||
// We need to unlock all caches and the address space while reading
|
||||
// the page in. Keep a reference to the cache around.
|
||||
cache->AcquireRefLocked();
|
||||
@@ -3722,7 +3719,7 @@ fault_get_page(PageFaultContext& context)
|
||||
dprintf("reading page from cache %p returned: %s!\n",
|
||||
cache, strerror(status));
|
||||
|
||||
busyCondition.Unpublish();
|
||||
cache->NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
|
||||
cache->RemovePage(page);
|
||||
vm_page_set_state(page, PAGE_STATE_FREE);
|
||||
|
||||
@@ -3732,7 +3729,7 @@ fault_get_page(PageFaultContext& context)
|
||||
|
||||
// mark the page unbusy again
|
||||
page->state = PAGE_STATE_ACTIVE;
|
||||
busyCondition.Unpublish();
|
||||
cache->NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
|
||||
|
||||
// Since we needed to unlock everything temporarily, the area
|
||||
// situation might have changed. So we need to restart the whole
|
||||
|
||||
@@ -1038,7 +1038,6 @@ private:
|
||||
bool fDequeuedPage;
|
||||
bool fIsActive;
|
||||
int fOldPageState;
|
||||
ConditionVariable fBusyCondition;
|
||||
};
|
||||
|
||||
|
||||
@@ -1073,8 +1072,6 @@ PageWriteWrapper::SetTo(vm_page* page, bool dequeuedPage)
|
||||
fOldPageState = fPage->state;
|
||||
fPage->state = PAGE_STATE_BUSY;
|
||||
fPage->busy_writing = true;
|
||||
|
||||
fBusyCondition.Publish(fPage, "page");
|
||||
}
|
||||
|
||||
|
||||
@@ -1143,7 +1140,8 @@ PageWriteWrapper::Done(status_t result)
|
||||
fPage->busy_writing = false;
|
||||
}
|
||||
|
||||
fBusyCondition.Unpublish();
|
||||
|
||||
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
|
||||
fIsActive = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user