haiku/headers/private/kernel/vm/VMCache.h

374 lines
7.5 KiB
C
Raw Normal View History

/*
* Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2003-2007, 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.
*/
#ifndef _KERNEL_VM_VM_CACHE_H
#define _KERNEL_VM_VM_CACHE_H
#include <debug.h>
#include <kernel.h>
#include <util/list.h>
#include <vm/vm.h>
#include <vm/vm_types.h>
#include "kernel_debug_config.h"
struct kernel_args;
enum {
CACHE_TYPE_RAM = 0,
CACHE_TYPE_VNODE,
CACHE_TYPE_DEVICE,
CACHE_TYPE_NULL
};
enum {
PAGE_EVENT_NOT_BUSY = 0x01 // page not busy anymore
};
struct VMCachePagesTreeDefinition {
typedef page_num_t KeyType;
typedef vm_page NodeType;
static page_num_t GetKey(const NodeType* node)
{
return node->cache_offset;
}
static SplayTreeLink<NodeType>* GetLink(NodeType* node)
{
return &node->cache_link;
}
static int Compare(page_num_t key, const NodeType* node)
{
return key == node->cache_offset ? 0
: (key < node->cache_offset ? -1 : 1);
}
static NodeType** GetListLink(NodeType* node)
{
return &node->cache_next;
}
};
typedef IteratableSplayTree<VMCachePagesTreeDefinition> VMCachePagesTree;
struct VMCache {
public:
VMCache();
virtual ~VMCache();
status_t Init(uint32 cacheType, uint32 allocationFlags);
virtual void Delete();
inline bool Lock();
inline bool TryLock();
inline bool SwitchLock(mutex* from);
inline bool SwitchFromReadLock(rw_lock* from);
void Unlock(bool consumerLocked = false);
inline void AssertLocked();
inline void AcquireRefLocked();
inline void AcquireRef();
inline void ReleaseRefLocked();
inline void ReleaseRef();
inline void ReleaseRefAndUnlock(
bool consumerLocked = false);
inline VMCacheRef* CacheRef() const { return fCacheRef; }
void WaitForPageEvents(vm_page* page, uint32 events,
bool relock);
void NotifyPageEvents(vm_page* page, uint32 events)
{ if (fPageEventWaiters != NULL)
_NotifyPageEvents(page, events); }
inline void MarkPageUnbusy(vm_page* page);
vm_page* LookupPage(off_t offset);
void InsertPage(vm_page* page, off_t offset);
void RemovePage(vm_page* page);
* Removed DEBUG_PAGE_CACHE_TRANSITIONS debugging. * Added VMCache::MovePage() and MoveAllPages() to move pages between caches. * VMAnonymousCache: - _MergeSwapPages(): Avoid doing anything, if neither cache has swapped out pages. - _MergeSwapPages() does now also remove source cache pages that are shadowed by consumer swap pages. This allows us to call _MergeSwapPages() before _MergePagesSmallerSource(), save the swap page shadowing check there and get rid of the vm_page::merge_swap flag. This is an optimization based on the assumption that usually none or only few pages are swapped out, so we save a lot of checks. - Implemented _MergePagesSmallerConsumer() as an alternative to _MergePagesSmallerSource(). The former is used when the source cache has more pages than the consumer cache. It iterates over the consumer cache's pages, moves them to the source and finally moves all pages back to the consumer. The final move is relatively cheap (though unfortunately we still have to update all pages' vm_page::cache field), so that overall we save iterations of the main loop with the more expensive checks. The optimizations particularly improve the common fork()+exec*() situations. fork() uses CoW, which is implemented by putting two new empty caches between the to be copied area and its cache. exec*() destroys one copy of the area, its cache and thus causes merging of the other new cache with the old cache. Since this usually happens in a very short time, the old cache does still contain many pages and the new cache only few. Previously the many pages were all checked and moved individually. Now we do that for the few pages instead. A very extreme example of this situation is the Haiku image build. jam has a huge heap (> 200 MB) and it fork()s+exec*()s for every action to be executed. Since during the cache merging the cache is locked, any write access to a heap page causes jam to block until the cache merging is done. Formerly that took so long that it killed a lot of parallelism in multi-job builds. That could be observed particularly well when lots of small actions where executed (like the Link, XRes, Mimeset, SetType, SetVersion combos when building executables/libraries/add-ons). Those look dramatically better now. The overall speed improvement for a -j8 image build on my machine is only about 15%, though. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@34784 a95241bf-73f2-0310-859d-f6bbb57e9c96
2009-12-27 19:14:13 +03:00
void MovePage(vm_page* page);
void MoveAllPages(VMCache* fromCache);
inline page_num_t WiredPagesCount() const;
inline void IncrementWiredPagesCount();
inline void DecrementWiredPagesCount();
void AddConsumer(VMCache* consumer);
status_t InsertAreaLocked(VMArea* area);
status_t RemoveArea(VMArea* area);
void TransferAreas(VMCache* fromCache);
uint32 CountWritableAreas(VMArea* ignoreArea) const;
status_t WriteModified();
status_t SetMinimalCommitment(off_t commitment,
int priority);
virtual status_t Resize(off_t newSize, int priority);
status_t FlushAndRemoveAllPages();
void* UserData() { return fUserData; }
void SetUserData(void* data) { fUserData = data; }
// Settable by the lock owner and valid as
// long as the lock is owned.
// for debugging only
int32 RefCount() const
{ return fRefCount; }
// backing store operations
virtual status_t Commit(off_t size, int priority);
virtual bool HasPage(off_t offset);
virtual status_t Read(off_t offset, const generic_io_vec *vecs,
size_t count,uint32 flags,
generic_size_t *_numBytes);
virtual status_t Write(off_t offset, const generic_io_vec *vecs,
size_t count, uint32 flags,
generic_size_t *_numBytes);
virtual status_t WriteAsync(off_t offset,
const generic_io_vec* vecs, size_t count,
generic_size_t numBytes, uint32 flags,
AsyncIOCallback* callback);
virtual bool CanWritePage(off_t offset);
virtual int32 MaxPagesPerWrite() const
{ return -1; } // no restriction
virtual int32 MaxPagesPerAsyncWrite() const
{ return -1; } // no restriction
virtual status_t Fault(struct VMAddressSpace *aspace,
off_t offset);
virtual void Merge(VMCache* source);
virtual status_t AcquireUnreferencedStoreRef();
virtual void AcquireStoreRef();
virtual void ReleaseStoreRef();
virtual bool DebugHasPage(off_t offset);
vm_page* DebugLookupPage(off_t offset);
virtual void Dump(bool showPages) const;
public:
VMArea* areas;
list_link consumer_link;
list consumers;
// list of caches that use this cache as a source
VMCachePagesTree pages;
VMCache* source;
off_t virtual_base;
off_t virtual_end;
off_t committed_size;
// TODO: Remove!
uint32 page_count;
uint32 temporary : 1;
uint32 type : 6;
#if DEBUG_CACHE_LIST
VMCache* debug_previous;
VMCache* debug_next;
#endif
private:
struct PageEventWaiter;
friend struct VMCacheRef;
private:
void _NotifyPageEvents(vm_page* page, uint32 events);
inline bool _IsMergeable() const;
void _MergeWithOnlyConsumer();
void _RemoveConsumer(VMCache* consumer);
private:
int32 fRefCount;
mutex fLock;
PageEventWaiter* fPageEventWaiters;
void* fUserData;
VMCacheRef* fCacheRef;
page_num_t fWiredPagesCount;
};
#if DEBUG_CACHE_LIST
extern VMCache* gDebugCacheList;
#endif
class VMCacheFactory {
public:
static status_t CreateAnonymousCache(VMCache*& cache,
bool canOvercommit, int32 numPrecommittedPages,
int32 numGuardPages, bool swappable,
int priority);
static status_t CreateVnodeCache(VMCache*& cache,
struct vnode* vnode);
static status_t CreateDeviceCache(VMCache*& cache,
addr_t baseAddress);
static status_t CreateNullCache(int priority, VMCache*& cache);
};
bool
VMCache::Lock()
{
return mutex_lock(&fLock) == B_OK;
}
bool
VMCache::TryLock()
{
return mutex_trylock(&fLock) == B_OK;
}
bool
VMCache::SwitchLock(mutex* from)
{
return mutex_switch_lock(from, &fLock) == B_OK;
}
bool
VMCache::SwitchFromReadLock(rw_lock* from)
{
return mutex_switch_from_read_lock(from, &fLock) == B_OK;
}
void
VMCache::AssertLocked()
{
ASSERT_LOCKED_MUTEX(&fLock);
}
void
VMCache::AcquireRefLocked()
{
ASSERT_LOCKED_MUTEX(&fLock);
fRefCount++;
}
void
VMCache::AcquireRef()
{
Lock();
fRefCount++;
Unlock();
}
void
VMCache::ReleaseRefLocked()
{
ASSERT_LOCKED_MUTEX(&fLock);
fRefCount--;
}
void
VMCache::ReleaseRef()
{
Lock();
fRefCount--;
Unlock();
}
void
VMCache::ReleaseRefAndUnlock(bool consumerLocked)
{
ReleaseRefLocked();
Unlock(consumerLocked);
}
void
VMCache::MarkPageUnbusy(vm_page* page)
{
ASSERT(page->busy);
page->busy = false;
NotifyPageEvents(page, PAGE_EVENT_NOT_BUSY);
}
page_num_t
VMCache::WiredPagesCount() const
{
return fWiredPagesCount;
}
void
VMCache::IncrementWiredPagesCount()
{
ASSERT(fWiredPagesCount < page_count);
fWiredPagesCount++;
}
void
VMCache::DecrementWiredPagesCount()
{
ASSERT(fWiredPagesCount > 0);
fWiredPagesCount--;
}
// vm_page methods implemented here to avoid VMCache.h inclusion in vm_types.h
inline void
vm_page::IncrementWiredCount()
{
if (fWiredCount++ == 0)
cache_ref->cache->IncrementWiredPagesCount();
}
inline void
vm_page::DecrementWiredCount()
{
if (--fWiredCount == 0)
cache_ref->cache->DecrementWiredPagesCount();
}
#ifdef __cplusplus
extern "C" {
#endif
status_t vm_cache_init(struct kernel_args *args);
void vm_cache_init_post_heap();
struct VMCache *vm_cache_acquire_locked_page_cache(struct vm_page *page,
bool dontWait);
#ifdef __cplusplus
}
#endif
#endif /* _KERNEL_VM_VM_CACHE_H */