kernel/vm: Split the page writer into its own file and queue subclass.

This paves the way for there being more than one modified queue,
e.g. for each KDiskDevice.
This commit is contained in:
Augustin Cavalier
2026-04-06 22:23:26 -04:00
parent 62b8be7e22
commit 248735d5a2
7 changed files with 1034 additions and 998 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ void vm_page_free_etc(VMCache* cache, vm_page* page,
vm_page_reservation* reservation);
void vm_page_set_state(struct vm_page *page, int state);
void vm_page_requeue(struct vm_page *page, bool tail);
void vm_page_requeue(struct vm_page *page, bool tail, struct VMPageQueue** _queue);
// get some data about the number of pages in the system
page_num_t vm_page_num_pages(void);
+2
View File
@@ -39,6 +39,8 @@ void vm_debug_init();
void vm_kernel_args_init_post_area(kernel_args* args);
status_t vm_daemon_init(void);
bool vm_page_should_do_active_paging();
const char *page_state_to_string(int state);
// for debugging purposes only
+1 -1
View File
@@ -890,7 +890,7 @@ do_cache_io(void* _cacheRef, void* cookie, off_t offset, addr_t buffer,
if (page->State() == PAGE_STATE_CACHED
|| page->State() == PAGE_STATE_MODIFIED) {
DEBUG_PAGE_ACCESS_START(page);
vm_page_requeue(page, true);
vm_page_requeue(page, true, NULL);
DEBUG_PAGE_ACCESS_END(page);
}
+1
View File
@@ -12,6 +12,7 @@ KernelMergeObject kernel_vm.o :
vm_debug.cpp
vm_init.cpp
vm_page.cpp
vm_page_writer.cpp
VMAddressSpace.cpp
VMAddressSpaceLocking.cpp
VMAnonymousCache.cpp
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright 2026, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef MODIFIED_PAGE_QUEUE_H
#define MODIFIED_PAGE_QUEUE_H
#include <util/BinarySemaphore.h>
#include "VMPageQueue.h"
struct ModifiedPageQueue : public VMPageQueue {
public:
status_t StartWriter();
void NotifyWriter() { fPageWriterCondition.WakeUp(); }
private:
static status_t _WriterThreadEntry(void* _this);
status_t _PageWriter();
private:
thread_id fWriterThread;
BinarySemaphore fPageWriterCondition;
};
#endif // MODIFIED_PAGE_QUEUE_H
File diff suppressed because it is too large Load Diff
+921
View File
@@ -0,0 +1,921 @@
/*
* Copyright 2010-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2002-2010, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <KernelExport.h>
#include <OS.h>
#include <AutoDeleter.h>
#include <util/AutoLock.h>
#include <low_resource_manager.h>
#include <vfs.h>
#include <vm/vm.h>
#include <vm/vm_priv.h>
#include <vm/vm_page.h>
#include <vm/VMCache.h>
#include "IORequest.h"
#include "PageCacheLocker.h"
#include "ModifiedPageQueue.h"
//#define TRACE_VM_PAGE_WRITER
#ifdef TRACE_VM_PAGE_WRITER
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
#define MAX_PAGE_WRITER_IO_PRIORITY B_URGENT_DISPLAY_PRIORITY
// maximum I/O priority of the page writer
#define MAX_PAGE_WRITER_IO_PRIORITY_THRESHOLD 10000
// the maximum I/O priority shall be reached when this many pages need to
// be written
#if PAGE_WRITER_TRACING
namespace PageWriterTracing {
class WritePage : public AbstractTraceEntry {
public:
WritePage(vm_page* page)
:
fCache(page->Cache()),
fPage(page)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("page write: %p, cache: %p", fPage, fCache);
}
private:
VMCache* fCache;
vm_page* fPage;
};
} // namespace PageWriterTracing
# define TPW(x) new(std::nothrow) PageWriterTracing::x
#else
# define TPW(x)
#endif // PAGE_WRITER_TRACING
class PageWriteTransfer;
class PageWriteWrapper;
class PageWriterRun {
public:
status_t Init(uint32 maxPages);
void PrepareNextRun();
void AddPage(vm_page* page);
uint32 Go();
void PageWritten(PageWriteTransfer* transfer, status_t status,
bool partialTransfer, size_t bytesTransferred);
private:
uint32 fMaxPages;
uint32 fWrapperCount;
uint32 fTransferCount;
int32 fPendingTransfers;
PageWriteWrapper* fWrappers;
PageWriteTransfer* fTransfers;
ConditionVariable fAllFinishedCondition;
};
class PageWriteTransfer : public AsyncIOCallback {
public:
void SetTo(PageWriterRun* run, vm_page* page, int32 maxPages);
bool AddPage(vm_page* page);
status_t Schedule(uint32 flags);
void SetStatus(status_t status, size_t transferred);
status_t Status() const { return fStatus; }
struct VMCache* Cache() const { return fCache; }
uint32 PageCount() const { return fPageCount; }
virtual void IOFinished(status_t status, bool partialTransfer,
generic_size_t bytesTransferred);
private:
PageWriterRun* fRun;
struct VMCache* fCache;
off_t fOffset;
uint32 fPageCount;
int32 fMaxPages;
status_t fStatus;
uint32 fVecCount;
generic_io_vec fVecs[32]; // TODO: make dynamic/configurable
};
class PageWriteWrapper {
public:
PageWriteWrapper();
~PageWriteWrapper();
void SetTo(vm_page* page);
bool Done(status_t result);
private:
vm_page* fPage;
struct VMCache* fCache;
bool fIsActive;
};
PageWriteWrapper::PageWriteWrapper()
:
fIsActive(false)
{
}
PageWriteWrapper::~PageWriteWrapper()
{
if (fIsActive)
panic("page write wrapper going out of scope but isn't completed");
}
/*! The page's cache must be locked.
*/
void
PageWriteWrapper::SetTo(vm_page* page)
{
DEBUG_PAGE_ACCESS_CHECK(page);
if (page->busy)
panic("setting page write wrapper to busy page");
if (fIsActive)
panic("re-setting page write wrapper that isn't completed");
fPage = page;
fCache = page->Cache();
fIsActive = true;
fPage->busy = true;
fPage->busy_io = true;
// We have a modified page -- however, while we're writing it back,
// the page might still be mapped. In order not to lose any changes to the
// page, we mark it clean before actually writing it back; if
// writing the page fails for some reason, we'll just keep it in the
// modified page list, but that should happen only rarely.
// If the page is changed after we cleared the dirty flag, but before we
// had the chance to write it back, then we'll write it again later -- that
// will probably not happen that often, though.
vm_clear_map_flags(fPage, PAGE_MODIFIED);
}
/*! Moves a previously modified page into a now appropriate queue.
The page queues must not be locked.
*/
static void
move_page_to_appropriate_queue(vm_page *page)
{
DEBUG_PAGE_ACCESS_CHECK(page);
// Note, this logic must be in sync with what the page daemon does.
int32 state;
if (page->IsMapped())
state = PAGE_STATE_ACTIVE;
else if (page->modified)
state = PAGE_STATE_MODIFIED;
else
state = PAGE_STATE_CACHED;
// TODO: If free + cached pages are low, we might directly want to free the
// page.
vm_page_set_state(page, state);
}
/*! The page's cache must be locked.
The page queues must not be locked.
\return \c true if the page was written successfully respectively could be
handled somehow, \c false otherwise.
*/
bool
PageWriteWrapper::Done(status_t result)
{
if (!fIsActive)
panic("completing page write wrapper that is not active");
DEBUG_PAGE_ACCESS_START(fPage);
fPage->busy = false;
// Set unbusy and notify later by hand.
bool success = true;
if (!fPage->busy_io) {
// The busy_io flag was cleared. That means the cache tried to remove
// the page while we were trying to write it. Let the cache handle the rest.
fCache->FreeRemovedPage(fPage);
} else if (result == B_OK) {
// put it into the active/inactive queue
move_page_to_appropriate_queue(fPage);
fPage->busy_io = false;
DEBUG_PAGE_ACCESS_END(fPage);
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
} 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)
vm_page_set_state(fPage, PAGE_STATE_MODIFIED);
else if (fPage->IsMapped())
vm_page_set_state(fPage, PAGE_STATE_ACTIVE);
else
vm_page_set_state(fPage, PAGE_STATE_INACTIVE);
fPage->busy_io = false;
DEBUG_PAGE_ACCESS_END(fPage);
fCache->NotifyPageEvents(fPage, PAGE_EVENT_NOT_BUSY);
success = false;
}
fIsActive = false;
return success;
}
/*! The page's cache must be locked.
*/
void
PageWriteTransfer::SetTo(PageWriterRun* run, vm_page* page, int32 maxPages)
{
fRun = run;
fCache = page->Cache();
fOffset = page->cache_offset;
fPageCount = 1;
fMaxPages = maxPages;
fStatus = B_OK;
fVecs[0].base = (phys_addr_t)page->physical_page_number << PAGE_SHIFT;
fVecs[0].length = B_PAGE_SIZE;
fVecCount = 1;
}
/*! The page's cache must be locked.
*/
bool
PageWriteTransfer::AddPage(vm_page* page)
{
if (page->Cache() != fCache
|| (fMaxPages >= 0 && fPageCount >= (uint32)fMaxPages))
return false;
phys_addr_t nextBase = fVecs[fVecCount - 1].base
+ fVecs[fVecCount - 1].length;
if ((phys_addr_t)page->physical_page_number << PAGE_SHIFT == nextBase
&& (off_t)page->cache_offset == fOffset + fPageCount) {
// append to last iovec
fVecs[fVecCount - 1].length += B_PAGE_SIZE;
fPageCount++;
return true;
}
nextBase = fVecs[0].base - B_PAGE_SIZE;
if ((phys_addr_t)page->physical_page_number << PAGE_SHIFT == nextBase
&& (off_t)page->cache_offset == fOffset - 1) {
// prepend to first iovec and adjust offset
fVecs[0].base = nextBase;
fVecs[0].length += B_PAGE_SIZE;
fOffset = page->cache_offset;
fPageCount++;
return true;
}
if (((off_t)page->cache_offset == fOffset + fPageCount
|| (off_t)page->cache_offset == fOffset - 1)
&& fVecCount < sizeof(fVecs) / sizeof(fVecs[0])) {
// not physically contiguous or not in the right order
uint32 vectorIndex;
if ((off_t)page->cache_offset < fOffset) {
// we are pre-pending another vector, move the other vecs
for (uint32 i = fVecCount; i > 0; i--)
fVecs[i] = fVecs[i - 1];
fOffset = page->cache_offset;
vectorIndex = 0;
} else
vectorIndex = fVecCount;
fVecs[vectorIndex].base
= (phys_addr_t)page->physical_page_number << PAGE_SHIFT;
fVecs[vectorIndex].length = B_PAGE_SIZE;
fVecCount++;
fPageCount++;
return true;
}
return false;
}
status_t
PageWriteTransfer::Schedule(uint32 flags)
{
off_t writeOffset = (off_t)fOffset << PAGE_SHIFT;
generic_size_t writeLength = (phys_size_t)fPageCount << PAGE_SHIFT;
if (fRun != NULL) {
return fCache->WriteAsync(writeOffset, fVecs, fVecCount, writeLength,
flags | B_PHYSICAL_IO_REQUEST, this);
}
status_t status = fCache->Write(writeOffset, fVecs, fVecCount,
flags | B_PHYSICAL_IO_REQUEST, &writeLength);
SetStatus(status, writeLength);
return fStatus;
}
void
PageWriteTransfer::SetStatus(status_t status, size_t transferred)
{
// only succeed if all pages up to the last one have been written fully
// and the last page has at least been written partially
if (status == B_OK && transferred <= (fPageCount - 1) * B_PAGE_SIZE)
status = B_ERROR;
fStatus = status;
}
void
PageWriteTransfer::IOFinished(status_t status, bool partialTransfer,
generic_size_t bytesTransferred)
{
SetStatus(status, bytesTransferred);
fRun->PageWritten(this, fStatus, partialTransfer, bytesTransferred);
}
status_t
PageWriterRun::Init(uint32 maxPages)
{
fMaxPages = maxPages;
fWrapperCount = 0;
fTransferCount = 0;
fPendingTransfers = 0;
fWrappers = new(std::nothrow) PageWriteWrapper[maxPages];
fTransfers = new(std::nothrow) PageWriteTransfer[maxPages];
if (fWrappers == NULL || fTransfers == NULL)
return B_NO_MEMORY;
return B_OK;
}
void
PageWriterRun::PrepareNextRun()
{
fWrapperCount = 0;
fTransferCount = 0;
fPendingTransfers = 0;
}
/*! The page's cache must be locked.
*/
void
PageWriterRun::AddPage(vm_page* page)
{
fWrappers[fWrapperCount++].SetTo(page);
if (fTransferCount == 0 || !fTransfers[fTransferCount - 1].AddPage(page)) {
fTransfers[fTransferCount++].SetTo(this, page,
page->Cache()->MaxPagesPerAsyncWrite());
}
}
/*! Writes all pages previously added.
\return The number of pages that could not be written or otherwise handled.
*/
uint32
PageWriterRun::Go()
{
atomic_set(&fPendingTransfers, fTransferCount);
fAllFinishedCondition.Init(this, "page writer wait for I/O");
ConditionVariableEntry waitEntry;
fAllFinishedCondition.Add(&waitEntry);
// schedule writes
for (uint32 i = 0; i < fTransferCount; i++)
fTransfers[i].Schedule(B_VIP_IO_REQUEST);
// wait until all pages have been written
waitEntry.Wait();
// mark pages depending on whether they could be written or not
uint32 failedPages = 0;
uint32 wrapperIndex = 0;
for (uint32 i = 0; i < fTransferCount; i++) {
PageWriteTransfer& transfer = fTransfers[i];
transfer.Cache()->Lock();
for (uint32 j = 0; j < transfer.PageCount(); j++) {
if (!fWrappers[wrapperIndex++].Done(transfer.Status()))
failedPages++;
}
transfer.Cache()->Unlock();
}
ASSERT(wrapperIndex == fWrapperCount);
for (uint32 i = 0; i < fTransferCount; i++) {
PageWriteTransfer& transfer = fTransfers[i];
struct VMCache* cache = transfer.Cache();
// We've acquired a references for each page
for (uint32 j = 0; j < transfer.PageCount(); j++) {
// We release the cache references after all pages were made
// unbusy again - otherwise releasing a vnode could deadlock.
cache->ReleaseStoreRef();
cache->ReleaseRef();
}
}
return failedPages;
}
void
PageWriterRun::PageWritten(PageWriteTransfer* transfer, status_t status,
bool partialTransfer, size_t bytesTransferred)
{
if (atomic_add(&fPendingTransfers, -1) == 1)
fAllFinishedCondition.NotifyAll();
}
static vm_page*
next_modified_page(VMPageQueue& queue, page_num_t& maxPagesToSee)
{
InterruptsSpinLocker locker(queue.GetLock());
while (maxPagesToSee > 0) {
vm_page* page = queue.Head();
if (page == NULL)
return NULL;
queue.Requeue(page, true);
maxPagesToSee--;
if (!page->busy)
return page;
}
return NULL;
}
/*! The page writer continuously takes some pages from the modified
queue, writes them back, and moves them back to the active queue.
It runs in its own thread, and is only there to keep the number
of modified pages low, so that more pages can be reused with
fewer costs.
*/
status_t
ModifiedPageQueue::_PageWriter()
{
ModifiedPageQueue& queue = *this;
const uint32 kNumPages = 256;
#ifdef TRACE_VM_PAGE_WRITER
uint32 writtenPages = 0;
bigtime_t lastWrittenTime = 0;
bigtime_t pageCollectionTime = 0;
bigtime_t pageWritingTime = 0;
#endif
PageWriterRun run;
if (run.Init(kNumPages) != B_OK) {
panic("page writer: Failed to init PageWriterRun!");
return B_ERROR;
}
page_num_t pagesSinceLastSuccessfulWrite = 0;
while (true) {
// TODO: Maybe wait shorter when memory is low!
if (queue.Count() < kNumPages) {
fPageWriterCondition.Wait(3000000, true);
// all 3 seconds when no one triggers us
}
page_num_t modifiedPages = queue.Count();
if (modifiedPages == 0)
continue;
if (modifiedPages <= pagesSinceLastSuccessfulWrite) {
// We ran through the whole queue without being able to write a
// single page. Take a break.
snooze(500000);
pagesSinceLastSuccessfulWrite = 0;
}
#if ENABLE_SWAP_SUPPORT
const bool activePaging = vm_page_should_do_active_paging();
#endif
// depending on how urgent it becomes to get pages to disk, we adjust
// our I/O priority
uint32 lowPagesState = low_resource_state(B_KERNEL_RESOURCE_PAGES);
int32 ioPriority = B_IDLE_PRIORITY;
if (lowPagesState >= B_LOW_RESOURCE_CRITICAL
|| modifiedPages > MAX_PAGE_WRITER_IO_PRIORITY_THRESHOLD) {
ioPriority = MAX_PAGE_WRITER_IO_PRIORITY;
} else {
ioPriority = (uint64)MAX_PAGE_WRITER_IO_PRIORITY * modifiedPages
/ MAX_PAGE_WRITER_IO_PRIORITY_THRESHOLD;
}
thread_set_io_priority(ioPriority);
uint32 numPages = 0;
run.PrepareNextRun();
// TODO: make this laptop friendly, too (ie. only start doing
// something if someone else did something or there is really
// enough to do).
// collect pages to be written
#ifdef TRACE_VM_PAGE_WRITER
pageCollectionTime -= system_time();
#endif
page_num_t maxPagesToSee = modifiedPages;
while (numPages < kNumPages && maxPagesToSee > 0) {
vm_page *page = next_modified_page(queue, maxPagesToSee);
if (page == NULL)
break;
PageCacheLocker cacheLocker(page, false);
if (!cacheLocker.IsLocked())
continue;
VMCache *cache = page->Cache();
// If the page is busy or its state has changed while we were
// locking the cache, just ignore it.
if (page->busy || page->State() != PAGE_STATE_MODIFIED)
continue;
DEBUG_PAGE_ACCESS_START(page);
// Don't write back wired (locked) pages.
if (page->WiredCount() > 0) {
vm_page_set_state(page, PAGE_STATE_ACTIVE);
DEBUG_PAGE_ACCESS_END(page);
continue;
}
// Write back temporary pages only when we're actively paging.
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())
vm_page_set_state(page, PAGE_STATE_INACTIVE);
else
vm_page_set_state(page, PAGE_STATE_ACTIVE);
DEBUG_PAGE_ACCESS_END(page);
continue;
}
// We need our own reference to the store, as it might currently be
// destroyed.
if (cache->AcquireUnreferencedStoreRef() != B_OK) {
DEBUG_PAGE_ACCESS_END(page);
cacheLocker.Unlock();
thread_yield();
continue;
}
run.AddPage(page);
// TODO: We're possibly adding pages of different caches and
// thus maybe of different underlying file systems here. This
// is a potential problem for loop file systems/devices, since
// we could mark a page busy that would need to be accessed
// when writing back another page, thus causing a deadlock.
DEBUG_PAGE_ACCESS_END(page);
//dprintf("write page %p, cache %p (%ld)\n", page, page->cache, page->cache->ref_count);
TPW(WritePage(page));
cache->AcquireRefLocked();
numPages++;
// Write adjacent pages at the same time, if they're also modified.
if (cache->temporary)
continue;
while (page->cache_next != NULL && numPages < kNumPages) {
page = page->cache_next;
if (page->busy || page->State() != PAGE_STATE_MODIFIED)
break;
if (page->WiredCount() > 0)
break;
DEBUG_PAGE_ACCESS_START(page);
queue.RequeueUnlocked(page, true);
run.AddPage(page);
DEBUG_PAGE_ACCESS_END(page);
cache->AcquireStoreRef();
cache->AcquireRefLocked();
numPages++;
if (maxPagesToSee > 0)
maxPagesToSee--;
}
}
#ifdef TRACE_VM_PAGE_WRITER
pageCollectionTime += system_time();
#endif
if (numPages == 0)
continue;
// write pages to disk and do all the cleanup
#ifdef TRACE_VM_PAGE_WRITER
pageWritingTime -= system_time();
#endif
uint32 failedPages = run.Go();
#ifdef TRACE_VM_PAGE_WRITER
pageWritingTime += system_time();
// debug output only...
writtenPages += numPages;
if (writtenPages >= 1024) {
bigtime_t now = system_time();
TRACE(("page writer: wrote 1024 pages (total: %" B_PRIu64 " ms, "
"collect: %" B_PRIu64 " ms, write: %" B_PRIu64 " ms)\n",
(now - lastWrittenTime) / 1000,
pageCollectionTime / 1000, pageWritingTime / 1000));
lastWrittenTime = now;
writtenPages -= 1024;
pageCollectionTime = 0;
pageWritingTime = 0;
}
#endif
if (failedPages == numPages)
pagesSinceLastSuccessfulWrite += modifiedPages - maxPagesToSee;
else
pagesSinceLastSuccessfulWrite = 0;
}
return B_OK;
}
/*static*/ status_t
ModifiedPageQueue::_WriterThreadEntry(void* _this)
{
return ((ModifiedPageQueue*)_this)->_PageWriter();
}
// #pragma mark - private kernel API
/*! Writes a range of modified pages of a cache to disk.
You need to hold the VMCache lock when calling this function.
Note that the cache lock is released in this function.
\param cache The cache.
\param firstPage Offset (in page size units) of the first page in the range.
\param endPage End offset (in page size units) of the page range. The page
at this offset is not included.
*/
status_t
vm_page_write_modified_page_range(struct VMCache* cache, uint32 firstPage,
uint32 endPage)
{
static const int32 kMaxPages = 256;
int32 maxPages = cache->MaxPagesPerWrite();
if (maxPages < 0 || maxPages > kMaxPages)
maxPages = kMaxPages;
const uint32 allocationFlags = HEAP_DONT_WAIT_FOR_MEMORY
| HEAP_DONT_LOCK_KERNEL_SPACE;
PageWriteWrapper stackWrappersPool[2];
PageWriteWrapper* stackWrappers[1];
PageWriteWrapper* wrapperPool
= new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1];
PageWriteWrapper** wrappers
= new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages];
if (wrapperPool == NULL || wrappers == NULL) {
// don't fail, just limit our capabilities
delete[] wrapperPool;
delete[] wrappers;
wrapperPool = stackWrappersPool;
wrappers = stackWrappers;
maxPages = 1;
}
int32 nextWrapper = 0;
int32 usedWrappers = 0;
PageWriteTransfer transfer;
bool transferEmpty = true;
VMCachePagesTree::Iterator it
= cache->pages.GetIterator(firstPage, true, true);
while (true) {
vm_page* page = it.Next();
if (page == NULL || page->cache_offset >= endPage) {
if (transferEmpty)
break;
page = NULL;
}
if (page != NULL) {
if (page->busy
|| (page->State() != PAGE_STATE_MODIFIED
&& !vm_test_map_modification(page))) {
page = NULL;
}
}
PageWriteWrapper* wrapper = NULL;
if (page != NULL) {
wrapper = &wrapperPool[nextWrapper++];
if (nextWrapper > maxPages)
nextWrapper = 0;
DEBUG_PAGE_ACCESS_START(page);
wrapper->SetTo(page);
if (transferEmpty || transfer.AddPage(page)) {
if (transferEmpty) {
transfer.SetTo(NULL, page, maxPages);
transferEmpty = false;
}
DEBUG_PAGE_ACCESS_END(page);
wrappers[usedWrappers++] = wrapper;
continue;
}
DEBUG_PAGE_ACCESS_END(page);
}
if (transferEmpty)
continue;
cache->Unlock();
status_t status = transfer.Schedule(0);
cache->Lock();
for (int32 i = 0; i < usedWrappers; i++)
wrappers[i]->Done(status);
usedWrappers = 0;
if (page != NULL) {
transfer.SetTo(NULL, page, maxPages);
wrappers[usedWrappers++] = wrapper;
} else
transferEmpty = true;
}
if (wrapperPool != stackWrappersPool) {
delete[] wrapperPool;
delete[] wrappers;
}
return B_OK;
}
/*! You need to hold the VMCache lock when calling this function.
Note that the cache lock is released in this function.
*/
status_t
vm_page_write_modified_pages(VMCache *cache)
{
return vm_page_write_modified_page_range(cache, 0,
(cache->virtual_end + B_PAGE_SIZE - 1) >> PAGE_SHIFT);
}
/*! Schedules the page writer to write back the specified \a page.
Note, however, that it might not do this immediately, and it can well
take several seconds until the page is actually written out.
*/
void
vm_page_schedule_write_page(vm_page *page)
{
ASSERT(page->State() == PAGE_STATE_MODIFIED);
VMPageQueue* queue = NULL;
vm_page_requeue(page, false, &queue);
((ModifiedPageQueue*)queue)->NotifyWriter();
}
/*! Cache must be locked.
*/
void
vm_page_schedule_write_page_range(struct VMCache *cache, uint32 firstPage,
uint32 endPage)
{
VMPageQueue* queue = NULL;
uint32 modified = 0;
for (VMCachePagesTree::Iterator it
= cache->pages.GetIterator(firstPage, true, true);
vm_page *page = it.Next();) {
if (page->cache_offset >= endPage)
break;
if (!page->busy && page->State() == PAGE_STATE_MODIFIED) {
DEBUG_PAGE_ACCESS_START(page);
vm_page_requeue(page, false, &queue);
modified++;
DEBUG_PAGE_ACCESS_END(page);
}
}
if (modified > 0)
((ModifiedPageQueue*)queue)->NotifyWriter();
}
status_t
ModifiedPageQueue::StartWriter()
{
fPageWriterCondition.Init("page writer");
fWriterThread = spawn_kernel_thread(&_WriterThreadEntry, "page writer",
B_NORMAL_PRIORITY + 1, this);
if (fWriterThread < 0)
return fWriterThread;
return resume_thread(fWriterThread);
}