page_writer():

* Use the same criterion when to write back temporary pages as the page daemon.
* Move wired and temporary pages that shall not be written back to the active or
  inactive queue so they don't get stuck in the modified queue and potentially
  cause the page writer to run permanently without actually making progress
  (#5382).

page writing implementation:
* If writing back a temporary page failed, move it back to the inactive or
  active queue, so it doesn't get stuck in the modified queue. If active paging
  continues, it might find its way back to the modified queue in the next
  iteration, but that improves the situation a bit at least. Particularly with
  the port heap pages not really being swappable ATM.
* Never dequeue pages from the modified queue. We mark them busy, so the page
  writer will skip them anyway. This allows others to play with the page to some
  extend (at least allowing to move it between the queues). This fixes #5404.
* Removed PageWriteWrapper::ClearModifiedFlag(). We clear the modified flag in
  PageWriteWrapper::SetTo(), now, so that the page writer doesn't need to do
  that explicitly either.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@35485 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2010-02-15 22:40:37 +00:00
parent bf96cfdcbe
commit fa47a7c434
+100 -95
View File
@@ -130,6 +130,13 @@ static page_num_t sNextPageUsagePageCount;
#endif #endif
struct page_stats {
int32 totalFreePages;
int32 unsatisfiedReservations;
int32 cachedPages;
};
struct PageReservationWaiter struct PageReservationWaiter
: public DoublyLinkedListLinkImpl<PageReservationWaiter> { : public DoublyLinkedListLinkImpl<PageReservationWaiter> {
struct thread* thread; struct thread* thread;
@@ -875,22 +882,25 @@ dump_page_usage_stats(int argc, char** argv)
// #pragma mark - // #pragma mark -
struct page_stats {
int32 totalFreePages;
int32 unsatisfiedReservations;
int32 cachedPages;
};
static void static void
get_page_stats(page_stats& pageStats) get_page_stats(page_stats& _pageStats)
{ {
pageStats.totalFreePages = sUnreservedFreePages; _pageStats.totalFreePages = sUnreservedFreePages;
pageStats.cachedPages = sCachedPageQueue.Count(); _pageStats.cachedPages = sCachedPageQueue.Count();
pageStats.unsatisfiedReservations = sUnsatisfiedPageReservations; _pageStats.unsatisfiedReservations = sUnsatisfiedPageReservations;
// TODO: We don't get an actual snapshot here! // TODO: We don't get an actual snapshot here!
} }
static bool
do_active_paging(const page_stats& pageStats)
{
return pageStats.totalFreePages + pageStats.cachedPages
< pageStats.unsatisfiedReservations
+ (int32)sFreeOrCachedPagesTarget;
}
/*! Reserves as many pages as possible from \c sUnreservedFreePages up to /*! Reserves as many pages as possible from \c sUnreservedFreePages up to
\a count. Doesn't touch the last \a dontTouch pages of \a count. Doesn't touch the last \a dontTouch pages of
\c sUnreservedFreePages, though. \c sUnreservedFreePages, though.
@@ -1123,7 +1133,7 @@ set_page_state(vm_page *page, int pageState)
The page queues must not be locked. The page queues must not be locked.
*/ */
static void static void
move_page_to_active_or_inactive_queue(vm_page *page, bool dequeued) move_page_to_active_or_inactive_queue(vm_page *page)
{ {
DEBUG_PAGE_ACCESS_CHECK(page); DEBUG_PAGE_ACCESS_CHECK(page);
@@ -1138,13 +1148,7 @@ move_page_to_active_or_inactive_queue(vm_page *page, bool dequeued)
// TODO: If free + cached pages are low, we might directly want to free the // TODO: If free + cached pages are low, we might directly want to free the
// page. // page.
if (dequeued) { set_page_state(page, state);
page->state = state;
sPageQueues[state].AppendUnlocked(page);
if (page->Cache()->temporary)
atomic_add(&sModifiedTemporaryPages, -1);
} else
set_page_state(page, state);
} }
@@ -1353,14 +1357,12 @@ class PageWriteWrapper {
public: public:
PageWriteWrapper(); PageWriteWrapper();
~PageWriteWrapper(); ~PageWriteWrapper();
void SetTo(vm_page* page, bool dequeuedPage); void SetTo(vm_page* page);
void ClearModifiedFlag();
void Done(status_t result); void Done(status_t result);
private: private:
vm_page* fPage; vm_page* fPage;
struct VMCache* fCache; struct VMCache* fCache;
bool fDequeuedPage;
bool fIsActive; bool fIsActive;
}; };
@@ -1382,7 +1384,7 @@ PageWriteWrapper::~PageWriteWrapper()
/*! The page's cache must be locked. /*! The page's cache must be locked.
*/ */
void void
PageWriteWrapper::SetTo(vm_page* page, bool dequeuedPage) PageWriteWrapper::SetTo(vm_page* page)
{ {
DEBUG_PAGE_ACCESS_CHECK(page); DEBUG_PAGE_ACCESS_CHECK(page);
@@ -1394,28 +1396,20 @@ PageWriteWrapper::SetTo(vm_page* page, bool dequeuedPage)
fPage = page; fPage = page;
fCache = page->Cache(); fCache = page->Cache();
fDequeuedPage = dequeuedPage;
fIsActive = true; fIsActive = true;
fPage->busy = true; fPage->busy = true;
fPage->busy_writing = true; fPage->busy_writing = true;
}
// We have a modified page -- however, while we're writing it back,
/*! The page's cache must be locked. // the page might still be mapped. In order not to lose any changes to the
*/
void
PageWriteWrapper::ClearModifiedFlag()
{
// We have a modified page - however, while we're writing it back,
// the page is still mapped. In order not to lose any changes to the
// page, we mark it clean before actually writing it back; if // page, we mark it clean before actually writing it back; if
// writing the page fails for some reason, we just keep it in the // writing the page fails for some reason, we'll just keep it in the
// modified page list, but that should happen only rarely. // modified page list, but that should happen only rarely.
// If the page is changed after we cleared the dirty flag, but // If the page is changed after we cleared the dirty flag, but before we
// before we had the chance to write it back, then we'll write it // had the chance to write it back, then we'll write it again later -- that
// again later - that will probably not happen that often, though. // will probably not happen that often, though.
vm_clear_map_flags(fPage, PAGE_MODIFIED); vm_clear_map_flags(fPage, PAGE_MODIFIED);
} }
@@ -1430,25 +1424,20 @@ PageWriteWrapper::Done(status_t result)
if (!fIsActive) if (!fIsActive)
panic("completing page write wrapper that is not active"); panic("completing page write wrapper that is not active");
DEBUG_PAGE_ACCESS_CHECK(fPage); DEBUG_PAGE_ACCESS_START(fPage);
fPage->busy = false; fPage->busy = false;
// Set unbusy and notify later by hand, since we might free the page. // Set unbusy and notify later by hand, since we might free the page.
if (result == B_OK) { if (result == B_OK) {
// put it into the active/inactive queue // put it into the active/inactive queue
move_page_to_active_or_inactive_queue(fPage, fDequeuedPage); move_page_to_active_or_inactive_queue(fPage);
fPage->busy_writing = false; fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage); DEBUG_PAGE_ACCESS_END(fPage);
} else { } else {
// Writing the page failed -- move to the modified queue. If we dequeued // Writing the page failed. One reason would be that the cache has been
// it from there, just enqueue it again, otherwise set the page state // shrunk and the page does no longer belong to the file. Otherwise the
// explicitly, which will take care of moving between the queues. // actual I/O failed, in which case we'll simply keep the page modified.
if (fDequeuedPage) {
fPage->state = PAGE_STATE_MODIFIED;
sModifiedPageQueue.AppendUnlocked(fPage);
} else
set_page_state(fPage, PAGE_STATE_MODIFIED);
if (!fPage->busy_writing) { if (!fPage->busy_writing) {
// The busy_writing flag was cleared. That means the cache has been // The busy_writing flag was cleared. That means the cache has been
@@ -1458,11 +1447,23 @@ PageWriteWrapper::Done(status_t result)
// TODO: Unmapping should already happen when resizing the cache! // TODO: Unmapping should already happen when resizing the cache!
fCache->RemovePage(fPage); fCache->RemovePage(fPage);
free_page(fPage, false); free_page(fPage, false);
// Adjust temporary modified pages count, if necessary.
if (fDequeuedPage && fCache->temporary)
atomic_add(&sModifiedTemporaryPages, -1);
} else { } else {
// Writing the page failed -- mark the page modified and move it to
// an appropriate queue other than the modified queue, so we don't
// keep trying to write it over and over again. We keep
// non-temporary pages in the modified queue, though, so they don't
// get lost in the inactive queue.
dprintf("PageWriteWrapper: Failed to write page %p: %s\n", fPage,
strerror(result));
fPage->modified = true;
if (!fCache->temporary)
set_page_state(fPage, PAGE_STATE_MODIFIED);
else if (fPage->wired_count > 0 || !fPage->mappings.IsEmpty())
set_page_state(fPage, PAGE_STATE_ACTIVE);
else
set_page_state(fPage, PAGE_STATE_INACTIVE);
fPage->busy_writing = false; fPage->busy_writing = false;
DEBUG_PAGE_ACCESS_END(fPage); DEBUG_PAGE_ACCESS_END(fPage);
} }
@@ -1621,7 +1622,7 @@ PageWriterRun::PrepareNextRun()
void void
PageWriterRun::AddPage(vm_page* page) PageWriterRun::AddPage(vm_page* page)
{ {
fWrappers[fWrapperCount++].SetTo(page, true); fWrappers[fWrapperCount++].SetTo(page);
if (fTransferCount == 0 || !fTransfers[fTransferCount - 1].AddPage(page)) { if (fTransferCount == 0 || !fTransfers[fTransferCount - 1].AddPage(page)) {
fTransfers[fTransferCount++].SetTo(this, page, fTransfers[fTransferCount++].SetTo(this, page,
@@ -1720,6 +1721,12 @@ page_writer(void* /*unused*/)
if (modifiedPages == 0) if (modifiedPages == 0)
continue; continue;
#if ENABLE_SWAP_SUPPORT
page_stats pageStats;
get_page_stats(pageStats);
bool activePaging = do_active_paging(pageStats);
#endif
// depending on how urgent it becomes to get pages to disk, we adjust // depending on how urgent it becomes to get pages to disk, we adjust
// our I/O priority // our I/O priority
uint32 lowPagesState = low_resource_state(B_KERNEL_RESOURCE_PAGES); uint32 lowPagesState = low_resource_state(B_KERNEL_RESOURCE_PAGES);
@@ -1742,10 +1749,6 @@ page_writer(void* /*unused*/)
// enough to do). // enough to do).
// collect pages to be written // collect pages to be written
#if ENABLE_SWAP_SUPPORT
bool lowOnPages = lowPagesState != B_NO_LOW_RESOURCE;
#endif
pageCollectionTime -= system_time(); pageCollectionTime -= system_time();
while (numPages < kNumPages) { while (numPages < kNumPages) {
@@ -1757,27 +1760,42 @@ page_writer(void* /*unused*/)
if (!cacheLocker.IsLocked()) if (!cacheLocker.IsLocked())
continue; continue;
DEBUG_PAGE_ACCESS_START(page);
VMCache *cache = page->Cache(); VMCache *cache = page->Cache();
// Don't write back wired (locked) pages and don't write RAM pages // If the page is busy or its state has changed while we were
// until we're low on pages. Also avoid writing temporary pages that // locking the cache, just ignore it.
// are active. if (page->busy || page->state != PAGE_STATE_MODIFIED)
if (page->wired_count > 0 continue;
|| (cache->temporary
#if ENABLE_SWAP_SUPPORT DEBUG_PAGE_ACCESS_START(page);
&& (!lowOnPages /*|| page->usage_count > 0*/
|| !cache->CanWritePage( // Don't write back wired (locked) pages.
(off_t)page->cache_offset << PAGE_SHIFT)) if (page->wired_count > 0) {
#endif set_page_state(page, PAGE_STATE_ACTIVE);
)) {
DEBUG_PAGE_ACCESS_END(page); DEBUG_PAGE_ACCESS_END(page);
continue; continue;
} }
// we need our own reference to the store, as it might // Write back temporary pages only when we're actively paging.
// currently be destructed if (cache->temporary
#if ENABLE_SWAP_SUPPORT
&& (!activePaging
|| !cache->CanWritePage(
(off_t)page->cache_offset << PAGE_SHIFT))
#endif
) {
// We can't/don't want to do anything with this page, so move it
// to one of the other queues.
if (page->mappings.IsEmpty())
set_page_state(page, PAGE_STATE_INACTIVE);
else
set_page_state(page, PAGE_STATE_ACTIVE);
DEBUG_PAGE_ACCESS_END(page);
}
// We need our own reference to the store, as it might currently be
// destroyed.
if (cache->AcquireUnreferencedStoreRef() != B_OK) { if (cache->AcquireUnreferencedStoreRef() != B_OK) {
DEBUG_PAGE_ACCESS_END(page); DEBUG_PAGE_ACCESS_END(page);
cacheLocker.Unlock(); cacheLocker.Unlock();
@@ -1785,21 +1803,13 @@ page_writer(void* /*unused*/)
continue; continue;
} }
// state might have changed while we were locking the cache
if (page->busy || page->state != PAGE_STATE_MODIFIED) {
// release the cache reference
DEBUG_PAGE_ACCESS_END(page);
cache->ReleaseStoreRef();
continue;
}
sModifiedPageQueue.RemoveUnlocked(page);
run.AddPage(page); run.AddPage(page);
DEBUG_PAGE_ACCESS_END(page);
//dprintf("write page %p, cache %p (%ld)\n", page, page->cache, page->cache->ref_count); //dprintf("write page %p, cache %p (%ld)\n", page, page->cache, page->cache->ref_count);
TPW(WritePage(page)); TPW(WritePage(page));
vm_clear_map_flags(page, PAGE_MODIFIED);
cache->AcquireRefLocked(); cache->AcquireRefLocked();
numPages++; numPages++;
} }
@@ -2329,9 +2339,7 @@ page_daemon(void* /*unused*/)
page_stats pageStats; page_stats pageStats;
get_page_stats(pageStats); get_page_stats(pageStats);
if (pageStats.totalFreePages + pageStats.cachedPages if (!do_active_paging(pageStats)) {
>= pageStats.unsatisfiedReservations
+ (int32)sFreeOrCachedPagesTarget) {
// Things look good -- just maintain statistics and keep the pool // Things look good -- just maintain statistics and keep the pool
// of actually free pages full enough. // of actually free pages full enough.
despairLevel = 0; despairLevel = 0;
@@ -2478,18 +2486,12 @@ vm_page_write_modified_page_range(struct VMCache* cache, uint32 firstPage,
page = NULL; page = NULL;
} }
bool dequeuedPage = false;
if (page != NULL) { if (page != NULL) {
if (page->busy) { if (page->busy
|| (page->state != PAGE_STATE_MODIFIED
&& !vm_test_map_modification(page))) {
page = NULL; page = NULL;
} else if (page->state == PAGE_STATE_MODIFIED) { }
DEBUG_PAGE_ACCESS_START(page);
sModifiedPageQueue.RemoveUnlocked(page);
dequeuedPage = true;
} else if (!vm_test_map_modification(page)) {
page = NULL;
} else
DEBUG_PAGE_ACCESS_START(page);
} }
PageWriteWrapper* wrapper = NULL; PageWriteWrapper* wrapper = NULL;
@@ -2498,8 +2500,11 @@ vm_page_write_modified_page_range(struct VMCache* cache, uint32 firstPage,
if (nextWrapper > maxPages) if (nextWrapper > maxPages)
nextWrapper = 0; nextWrapper = 0;
wrapper->SetTo(page, dequeuedPage); DEBUG_PAGE_ACCESS_START(page);
wrapper->ClearModifiedFlag();
wrapper->SetTo(page);
DEBUG_PAGE_ACCESS_END(page);
if (transferEmpty || transfer.AddPage(page)) { if (transferEmpty || transfer.AddPage(page)) {
if (transferEmpty) { if (transferEmpty) {