bonefish+axeld:
* Removed the vm_cache/vm_store ref_count duality that besides being a bit ugly also created the page dameon cache retrieval problem: now, only areas (and cache consumers) retrieve a reference to the store (and therefore, the vnode). The page daemon doesn't need to care about this at all anymore, and the pseudo references of the vm_cache could be removed again. * Rearranged deletion of vnodes such that its ID can be reused directly after fs_remove_vnode() has been called. * vm_page_allocate_page() no longer panics when it runs out of pages, but just waits for new pages to become available using the new sFreeCondition condition variable - to make sure this happens in an acceptable time frame, it'll trigger a run of the low memory handlers. * Implemented a page_thief() that steals inactive pages from caches and puts them into the free queue. It runs as a low memory handler. * The file cache now sets the usage count on the pages it inserts into the cache (needs some rework though, cache_io() doesn't do it yet). * Instead of panicking, the kernel will currently dead lock in low memory situations, since BFS does a bit too much in bfs_release_vnode(). * Some minor cleanup. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22315 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -47,7 +47,7 @@ extern void cache_prefetch(dev_t mountID, ino_t vnodeID, off_t offset, size_t si
|
|||||||
extern status_t file_cache_init_post_boot_device(void);
|
extern status_t file_cache_init_post_boot_device(void);
|
||||||
extern status_t file_cache_init(void);
|
extern status_t file_cache_init(void);
|
||||||
|
|
||||||
extern vm_store *vm_create_vnode_store(void *vnode);
|
extern vm_store *vm_create_vnode_store(struct vnode *vnode);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ struct vm_cache;
|
|||||||
struct file_descriptor;
|
struct file_descriptor;
|
||||||
struct selectsync;
|
struct selectsync;
|
||||||
struct pollfd;
|
struct pollfd;
|
||||||
|
struct vnode;
|
||||||
|
|
||||||
|
|
||||||
/** The I/O context of a process/team, holds the fd array among others */
|
/** The I/O context of a process/team, holds the fd array among others */
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
struct kernel_args;
|
struct kernel_args;
|
||||||
struct team;
|
struct team;
|
||||||
|
struct vnode;
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -65,7 +66,7 @@ area_id vm_clone_area(team_id team, const char *name, void **address,
|
|||||||
uint32 addressSpec, uint32 protection, uint32 mapping,
|
uint32 addressSpec, uint32 protection, uint32 mapping,
|
||||||
area_id sourceArea);
|
area_id sourceArea);
|
||||||
status_t vm_delete_area(team_id aid, area_id id);
|
status_t vm_delete_area(team_id aid, area_id id);
|
||||||
status_t vm_create_vnode_cache(void *vnode, vm_cache **_cache);
|
status_t vm_create_vnode_cache(struct vnode *vnode, vm_cache **_cache);
|
||||||
vm_area *vm_area_lookup(vm_address_space *addressSpace, addr_t address);
|
vm_area *vm_area_lookup(vm_address_space *addressSpace, addr_t address);
|
||||||
status_t vm_set_area_memory_type(area_id id, addr_t physicalBase, uint32 type);
|
status_t vm_set_area_memory_type(area_id id, addr_t physicalBase, uint32 type);
|
||||||
status_t vm_get_page_mapping(team_id team, addr_t vaddr, addr_t *paddr);
|
status_t vm_get_page_mapping(team_id team, addr_t vaddr, addr_t *paddr);
|
||||||
|
|||||||
+11
-8
@@ -612,6 +612,11 @@ read_chunk_into_cache(file_cache_ref *ref, off_t offset, size_t numBytes,
|
|||||||
// make the pages accessible in the cache
|
// make the pages accessible in the cache
|
||||||
for (int32 i = pageIndex; i-- > 0;) {
|
for (int32 i = pageIndex; i-- > 0;) {
|
||||||
pages[i]->state = PAGE_STATE_ACTIVE;
|
pages[i]->state = PAGE_STATE_ACTIVE;
|
||||||
|
if (pages[i]->usage_count < 0)
|
||||||
|
pages[i]->usage_count = 1;
|
||||||
|
else
|
||||||
|
pages[i]->usage_count++;
|
||||||
|
|
||||||
busyConditions[i].Unpublish();
|
busyConditions[i].Unpublish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,6 +797,11 @@ write_chunk_to_cache(file_cache_ref *ref, off_t offset, size_t numBytes,
|
|||||||
for (int32 i = pageIndex; i-- > 0;) {
|
for (int32 i = pageIndex; i-- > 0;) {
|
||||||
busyConditions[i].Unpublish();
|
busyConditions[i].Unpublish();
|
||||||
|
|
||||||
|
if (pages[i]->usage_count < 0)
|
||||||
|
pages[i]->usage_count = 1;
|
||||||
|
else
|
||||||
|
pages[i]->usage_count++;
|
||||||
|
|
||||||
if (writeThrough)
|
if (writeThrough)
|
||||||
pages[i]->state = PAGE_STATE_ACTIVE;
|
pages[i]->state = PAGE_STATE_ACTIVE;
|
||||||
else
|
else
|
||||||
@@ -1248,17 +1258,10 @@ file_cache_create(dev_t mountID, ino_t vnodeID, off_t size, int fd)
|
|||||||
if (vfs_lookup_vnode(mountID, vnodeID, &ref->vnode) != B_OK)
|
if (vfs_lookup_vnode(mountID, vnodeID, &ref->vnode) != B_OK)
|
||||||
goto err2;
|
goto err2;
|
||||||
|
|
||||||
// Gets (usually creates) the cache for the node - note, this does grab a
|
// Gets (usually creates) the cache for the node
|
||||||
// reference to the node...
|
|
||||||
if (vfs_get_vnode_cache(ref->vnode, &ref->cache, true) != B_OK)
|
if (vfs_get_vnode_cache(ref->vnode, &ref->cache, true) != B_OK)
|
||||||
goto err2;
|
goto err2;
|
||||||
|
|
||||||
// ... that we don't need, and therefore release it again.
|
|
||||||
// Our caller already holds a reference to the vnode; it will destroy us
|
|
||||||
// when the last one goes away (which, of course, can only ever happen if
|
|
||||||
// we don't grab an extra reference).
|
|
||||||
vfs_put_vnode(ref->vnode);
|
|
||||||
|
|
||||||
ref->cache->virtual_size = size;
|
ref->cache->virtual_size = size;
|
||||||
((vnode_store *)ref->cache->store)->file_cache_ref = ref;
|
((vnode_store *)ref->cache->store)->file_cache_ref = ref;
|
||||||
return ref;
|
return ref;
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2004-2006, Axel Dörfler, [email protected].
|
* Copyright 2004-2007, Axel Dörfler, [email protected].
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ static vm_store_ops sStoreOps = {
|
|||||||
|
|
||||||
|
|
||||||
extern "C" vm_store *
|
extern "C" vm_store *
|
||||||
vm_create_vnode_store(void *vnode)
|
vm_create_vnode_store(struct vnode *vnode)
|
||||||
{
|
{
|
||||||
vnode_store *store = (vnode_store *)malloc(sizeof(struct vnode_store));
|
vnode_store *store = (vnode_store *)malloc(sizeof(struct vnode_store));
|
||||||
if (store == NULL)
|
if (store == NULL)
|
||||||
|
|||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2004, Axel Dörfler, [email protected].
|
* Copyright 2004-2007, Axel Dörfler, [email protected].
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*/
|
*/
|
||||||
#ifndef VNODE_STORE_H
|
#ifndef VNODE_STORE_H
|
||||||
@@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
|
|
||||||
struct vnode_store {
|
struct vnode_store {
|
||||||
vm_store vm;
|
vm_store vm;
|
||||||
void *vnode;
|
struct vnode* vnode;
|
||||||
void *file_cache_ref;
|
void* file_cache_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* VNODE_STORE_H */
|
#endif /* VNODE_STORE_H */
|
||||||
|
|||||||
@@ -685,6 +685,12 @@ free_vnode(struct vnode *vnode, bool reenter)
|
|||||||
// count, so that it will neither become negative nor 0.
|
// count, so that it will neither become negative nor 0.
|
||||||
vnode->ref_count = 2;
|
vnode->ref_count = 2;
|
||||||
|
|
||||||
|
// The file system has removed the resources of the vnode now, so we can
|
||||||
|
// make it available again (and remove the busy vnode from the hash)
|
||||||
|
mutex_lock(&sVnodeMutex);
|
||||||
|
hash_remove(sVnodeTable, vnode);
|
||||||
|
mutex_unlock(&sVnodeMutex);
|
||||||
|
|
||||||
// TODO: Usually, when the vnode is unreferenced, no one can get hold of the
|
// TODO: Usually, when the vnode is unreferenced, no one can get hold of the
|
||||||
// cache either (i.e. no one can get a cache reference while we're deleting
|
// cache either (i.e. no one can get a cache reference while we're deleting
|
||||||
// the vnode).. This is, however, not the case for the page daemon. It gets
|
// the vnode).. This is, however, not the case for the page daemon. It gets
|
||||||
@@ -692,18 +698,15 @@ free_vnode(struct vnode *vnode, bool reenter)
|
|||||||
// vnode reference while we're deleting the vnode.
|
// vnode reference while we're deleting the vnode.
|
||||||
|
|
||||||
if (!vnode->unpublished) {
|
if (!vnode->unpublished) {
|
||||||
if (vnode->remove)
|
if (vnode->remove) {
|
||||||
FS_CALL(vnode, remove_vnode)(vnode->mount->cookie, vnode->private_node, reenter);
|
FS_CALL(vnode, remove_vnode)(vnode->mount->cookie,
|
||||||
else
|
vnode->private_node, reenter);
|
||||||
FS_CALL(vnode, put_vnode)(vnode->mount->cookie, vnode->private_node, reenter);
|
} else {
|
||||||
|
FS_CALL(vnode, put_vnode)(vnode->mount->cookie, vnode->private_node,
|
||||||
|
reenter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The file system has removed the resources of the vnode now, so we can
|
|
||||||
// make it available again (and remove the busy vnode from the hash)
|
|
||||||
mutex_lock(&sVnodeMutex);
|
|
||||||
hash_remove(sVnodeTable, vnode);
|
|
||||||
mutex_unlock(&sVnodeMutex);
|
|
||||||
|
|
||||||
// if we have a vm_cache attached, remove it
|
// if we have a vm_cache attached, remove it
|
||||||
if (vnode->cache)
|
if (vnode->cache)
|
||||||
vm_cache_release_ref(vnode->cache);
|
vm_cache_release_ref(vnode->cache);
|
||||||
@@ -3116,12 +3119,11 @@ vfs_write_pages(void *_vnode, void *cookie, off_t pos, const iovec *vecs, size_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Gets the vnode's vm_cache object. If it didn't have one, it will be
|
/*! Gets the vnode's vm_cache object. If it didn't have one, it will be
|
||||||
* created if \a allocate is \c true.
|
created if \a allocate is \c true.
|
||||||
* In case it's successful, it will also grab a reference to the cache
|
In case it's successful, it will also grab a reference to the cache
|
||||||
* it returns (and therefore, one from the \a vnode in question as well).
|
it returns.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern "C" status_t
|
extern "C" status_t
|
||||||
vfs_get_vnode_cache(void *_vnode, vm_cache **_cache, bool allocate)
|
vfs_get_vnode_cache(void *_vnode, vm_cache **_cache, bool allocate)
|
||||||
{
|
{
|
||||||
@@ -4222,9 +4224,9 @@ common_fcntl(int fd, int op, uint32 argument, bool kernel)
|
|||||||
|
|
||||||
// O_CLOEXEC is the only flag available at this time
|
// O_CLOEXEC is the only flag available at this time
|
||||||
mutex_lock(&context->io_mutex);
|
mutex_lock(&context->io_mutex);
|
||||||
fd_set_close_on_exec(context, fd, argument & FD_CLOEXEC);
|
fd_set_close_on_exec(context, fd, (argument & FD_CLOEXEC) != 0);
|
||||||
mutex_unlock(&context->io_mutex);
|
mutex_unlock(&context->io_mutex);
|
||||||
|
|
||||||
status = B_OK;
|
status = B_OK;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-12
@@ -1761,7 +1761,7 @@ vm_create_null_area(team_id team, const char *name, void **address,
|
|||||||
The vnode has to be marked busy when calling this function.
|
The vnode has to be marked busy when calling this function.
|
||||||
*/
|
*/
|
||||||
status_t
|
status_t
|
||||||
vm_create_vnode_cache(void *vnode, struct vm_cache **_cache)
|
vm_create_vnode_cache(struct vnode *vnode, struct vm_cache **_cache)
|
||||||
{
|
{
|
||||||
status_t status;
|
status_t status;
|
||||||
|
|
||||||
@@ -1787,11 +1787,10 @@ err1:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Will map the file at the path specified by \a name to an area in memory.
|
/*! Will map the file at the path specified by \a name to an area in memory.
|
||||||
* The file will be mirrored beginning at the specified \a offset. The \a offset
|
The file will be mirrored beginning at the specified \a offset. The \a offset
|
||||||
* and \a size arguments have to be page aligned.
|
and \a size arguments have to be page aligned.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static area_id
|
static area_id
|
||||||
_vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec,
|
_vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec,
|
||||||
size_t size, uint32 protection, uint32 mapping, const char *path,
|
size_t size, uint32 protection, uint32 mapping, const char *path,
|
||||||
@@ -1827,13 +1826,10 @@ _vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec
|
|||||||
|
|
||||||
// ToDo: this only works for file systems that use the file cache
|
// ToDo: this only works for file systems that use the file cache
|
||||||
status = vfs_get_vnode_cache(vnode, &cache, false);
|
status = vfs_get_vnode_cache(vnode, &cache, false);
|
||||||
|
if (status < B_OK) {
|
||||||
vfs_put_vnode(vnode);
|
vfs_put_vnode(vnode);
|
||||||
// we don't need this vnode anymore - if the above call was
|
|
||||||
// successful, the store already has a ref to it
|
|
||||||
|
|
||||||
if (status < B_OK)
|
|
||||||
return status;
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
mutex_lock(&cache->lock);
|
mutex_lock(&cache->lock);
|
||||||
|
|
||||||
@@ -1842,6 +1838,10 @@ _vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec
|
|||||||
|
|
||||||
mutex_unlock(&cache->lock);
|
mutex_unlock(&cache->lock);
|
||||||
|
|
||||||
|
vfs_put_vnode(vnode);
|
||||||
|
// we don't need this vnode anymore - if the above call was
|
||||||
|
// successful, the store already has a ref to it
|
||||||
|
|
||||||
if (status < B_OK || mapping == REGION_PRIVATE_MAP) {
|
if (status < B_OK || mapping == REGION_PRIVATE_MAP) {
|
||||||
// map_backing_store() cannot know we no longer need the ref
|
// map_backing_store() cannot know we no longer need the ref
|
||||||
vm_cache_release_ref(cache);
|
vm_cache_release_ref(cache);
|
||||||
@@ -2539,6 +2539,9 @@ vm_map_page(vm_area *area, vm_page *page, addr_t address, uint32 protection)
|
|||||||
area->mappings.Add(mapping);
|
area->mappings.Add(mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (page->usage_count < 0)
|
||||||
|
page->usage_count = 1;
|
||||||
|
|
||||||
if (page->state != PAGE_STATE_MODIFIED)
|
if (page->state != PAGE_STATE_MODIFIED)
|
||||||
vm_page_set_state(page, PAGE_STATE_ACTIVE);
|
vm_page_set_state(page, PAGE_STATE_ACTIVE);
|
||||||
|
|
||||||
|
|||||||
@@ -80,16 +80,14 @@ page_hash_func(void *_p, const void *_key, uint32 range)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*! Acquires a pseudo reference to a cache yet unreferenced by the caller. The
|
/*! Acquires a reference to a cache yet unreferenced by the caller. The
|
||||||
caller must make sure, that the cache is not deleted, e.g. by holding the
|
caller must make sure, that the cache is not deleted, e.g. by holding the
|
||||||
cache's source cache lock or by holding the page cache table lock while the
|
cache's source cache lock or by holding the page cache table lock while the
|
||||||
cache is still referred to by a page. To get a real reference, the caller
|
cache is still referred to by a page.
|
||||||
must subsequently call vm_cache_acquire_ref() and decrement the cache's ref
|
Returns \c true, if the reference could be acquired.
|
||||||
count manually afterwards.
|
|
||||||
Returns \c true, if the pseudo reference could be acquired.
|
|
||||||
*/
|
*/
|
||||||
static inline bool
|
static inline bool
|
||||||
acquire_unreferenced_cache_pseudo_ref(vm_cache* cache)
|
acquire_unreferenced_cache_ref(vm_cache* cache)
|
||||||
{
|
{
|
||||||
while (true) {
|
while (true) {
|
||||||
int32 count = cache->ref_count;
|
int32 count = cache->ref_count;
|
||||||
@@ -102,6 +100,73 @@ acquire_unreferenced_cache_pseudo_ref(vm_cache* cache)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
delete_cache(vm_cache *cache)
|
||||||
|
{
|
||||||
|
if (cache->areas != NULL)
|
||||||
|
panic("cache %p to be deleted still has areas", cache);
|
||||||
|
if (!list_is_empty(&cache->consumers))
|
||||||
|
panic("cache %p to be deleted still has consumers", cache);
|
||||||
|
|
||||||
|
#if DEBUG_CACHE_LIST
|
||||||
|
int state = disable_interrupts();
|
||||||
|
acquire_spinlock(&sDebugCacheListLock);
|
||||||
|
|
||||||
|
if (cache->debug_previous)
|
||||||
|
cache->debug_previous->debug_next = cache->debug_next;
|
||||||
|
if (cache->debug_next)
|
||||||
|
cache->debug_next->debug_previous = cache->debug_previous;
|
||||||
|
if (cache == gDebugCacheList)
|
||||||
|
gDebugCacheList = cache->debug_next;
|
||||||
|
|
||||||
|
release_spinlock(&sDebugCacheListLock);
|
||||||
|
restore_interrupts(state);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// delete the cache's backing store
|
||||||
|
cache->store->ops->destroy(cache->store);
|
||||||
|
|
||||||
|
// free all of the pages in the cache
|
||||||
|
vm_page *page = cache->page_list;
|
||||||
|
while (page) {
|
||||||
|
vm_page *oldPage = page;
|
||||||
|
int state;
|
||||||
|
|
||||||
|
page = page->cache_next;
|
||||||
|
|
||||||
|
if (!oldPage->mappings.IsEmpty() || oldPage->wired_count != 0) {
|
||||||
|
panic("remove page %p from cache %p: page still has mappings!\n",
|
||||||
|
oldPage, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove it from the hash table
|
||||||
|
state = disable_interrupts();
|
||||||
|
acquire_spinlock(&sPageCacheTableLock);
|
||||||
|
|
||||||
|
hash_remove(sPageCacheTable, oldPage);
|
||||||
|
oldPage->cache = NULL;
|
||||||
|
// TODO: we also need to remove all of the page's mappings!
|
||||||
|
|
||||||
|
release_spinlock(&sPageCacheTableLock);
|
||||||
|
restore_interrupts(state);
|
||||||
|
|
||||||
|
TRACE(("vm_cache_release_ref: freeing page 0x%lx\n",
|
||||||
|
oldPage->physical_page_number));
|
||||||
|
vm_page_set_state(oldPage, PAGE_STATE_FREE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the ref to the source
|
||||||
|
if (cache->source)
|
||||||
|
vm_cache_remove_consumer(cache->source, cache);
|
||||||
|
|
||||||
|
mutex_destroy(&cache->lock);
|
||||||
|
free(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
status_t
|
status_t
|
||||||
vm_cache_init(kernel_args *args)
|
vm_cache_init(kernel_args *args)
|
||||||
{
|
{
|
||||||
@@ -181,9 +246,6 @@ vm_cache_acquire_ref(vm_cache *cache)
|
|||||||
if (cache == NULL)
|
if (cache == NULL)
|
||||||
panic("vm_cache_acquire_ref: passed NULL\n");
|
panic("vm_cache_acquire_ref: passed NULL\n");
|
||||||
|
|
||||||
if (cache->store->ops->acquire_ref != NULL)
|
|
||||||
cache->store->ops->acquire_ref(cache->store);
|
|
||||||
|
|
||||||
atomic_add(&cache->ref_count, 1);
|
atomic_add(&cache->ref_count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,8 +253,6 @@ vm_cache_acquire_ref(vm_cache *cache)
|
|||||||
void
|
void
|
||||||
vm_cache_release_ref(vm_cache *cache)
|
vm_cache_release_ref(vm_cache *cache)
|
||||||
{
|
{
|
||||||
vm_page *page;
|
|
||||||
|
|
||||||
TRACE(("vm_cache_release_ref: cacheRef %p, ref will be %ld\n",
|
TRACE(("vm_cache_release_ref: cacheRef %p, ref will be %ld\n",
|
||||||
cache, cache->ref_count - 1));
|
cache, cache->ref_count - 1));
|
||||||
|
|
||||||
@@ -200,10 +260,6 @@ vm_cache_release_ref(vm_cache *cache)
|
|||||||
panic("vm_cache_release_ref: passed NULL\n");
|
panic("vm_cache_release_ref: passed NULL\n");
|
||||||
|
|
||||||
if (atomic_add(&cache->ref_count, -1) != 1) {
|
if (atomic_add(&cache->ref_count, -1) != 1) {
|
||||||
// the store ref is only released on the "working" refs, not
|
|
||||||
// on the initial one (this is vnode specific)
|
|
||||||
if (cache->store->ops->release_ref)
|
|
||||||
cache->store->ops->release_ref(cache->store);
|
|
||||||
#if 0
|
#if 0
|
||||||
{
|
{
|
||||||
// count min references to see if everything is okay
|
// count min references to see if everything is okay
|
||||||
@@ -237,64 +293,7 @@ vm_cache_release_ref(vm_cache *cache)
|
|||||||
|
|
||||||
// delete this cache
|
// delete this cache
|
||||||
|
|
||||||
if (cache->areas != NULL)
|
delete_cache(cache);
|
||||||
panic("cache %p to be deleted still has areas", cache);
|
|
||||||
if (!list_is_empty(&cache->consumers))
|
|
||||||
panic("cache %p to be deleted still has consumers", cache);
|
|
||||||
|
|
||||||
#if DEBUG_CACHE_LIST
|
|
||||||
int state = disable_interrupts();
|
|
||||||
acquire_spinlock(&sDebugCacheListLock);
|
|
||||||
|
|
||||||
if (cache->debug_previous)
|
|
||||||
cache->debug_previous->debug_next = cache->debug_next;
|
|
||||||
if (cache->debug_next)
|
|
||||||
cache->debug_next->debug_previous = cache->debug_previous;
|
|
||||||
if (cache == gDebugCacheList)
|
|
||||||
gDebugCacheList = cache->debug_next;
|
|
||||||
|
|
||||||
release_spinlock(&sDebugCacheListLock);
|
|
||||||
restore_interrupts(state);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// delete the cache's backing store
|
|
||||||
cache->store->ops->destroy(cache->store);
|
|
||||||
|
|
||||||
// free all of the pages in the cache
|
|
||||||
page = cache->page_list;
|
|
||||||
while (page) {
|
|
||||||
vm_page *oldPage = page;
|
|
||||||
int state;
|
|
||||||
|
|
||||||
page = page->cache_next;
|
|
||||||
|
|
||||||
if (!oldPage->mappings.IsEmpty() || oldPage->wired_count != 0) {
|
|
||||||
panic("remove page %p from cache %p: page still has mappings!\n",
|
|
||||||
oldPage, cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove it from the hash table
|
|
||||||
state = disable_interrupts();
|
|
||||||
acquire_spinlock(&sPageCacheTableLock);
|
|
||||||
|
|
||||||
hash_remove(sPageCacheTable, oldPage);
|
|
||||||
oldPage->cache = NULL;
|
|
||||||
// TODO: we also need to remove all of the page's mappings!
|
|
||||||
|
|
||||||
release_spinlock(&sPageCacheTableLock);
|
|
||||||
restore_interrupts(state);
|
|
||||||
|
|
||||||
TRACE(("vm_cache_release_ref: freeing page 0x%lx\n",
|
|
||||||
oldPage->physical_page_number));
|
|
||||||
vm_page_set_state(oldPage, PAGE_STATE_FREE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the ref to the source
|
|
||||||
if (cache->source)
|
|
||||||
vm_cache_remove_consumer(cache->source, cache);
|
|
||||||
|
|
||||||
mutex_destroy(&cache->lock);
|
|
||||||
free(cache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -307,16 +306,10 @@ vm_cache_acquire_page_cache_ref(vm_page* page)
|
|||||||
if (cache == NULL)
|
if (cache == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
// get a pseudo reference
|
// get a reference
|
||||||
if (!acquire_unreferenced_cache_pseudo_ref(cache))
|
if (!acquire_unreferenced_cache_ref(cache))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
locker.Unlock();
|
|
||||||
|
|
||||||
// turn it into a real reference
|
|
||||||
vm_cache_acquire_ref(cache);
|
|
||||||
atomic_add(&cache->ref_count, -1);
|
|
||||||
|
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +534,9 @@ vm_cache_remove_consumer(vm_cache *cache, vm_cache *consumer)
|
|||||||
list_remove_item(&cache->consumers, consumer);
|
list_remove_item(&cache->consumers, consumer);
|
||||||
consumer->source = NULL;
|
consumer->source = NULL;
|
||||||
|
|
||||||
|
if (cache->store->ops->release_ref)
|
||||||
|
cache->store->ops->release_ref(cache->store);
|
||||||
|
|
||||||
if (cache->areas == NULL && cache->source != NULL
|
if (cache->areas == NULL && cache->source != NULL
|
||||||
&& !list_is_empty(&cache->consumers)
|
&& !list_is_empty(&cache->consumers)
|
||||||
&& cache->consumers.link.next == cache->consumers.link.prev) {
|
&& cache->consumers.link.next == cache->consumers.link.prev) {
|
||||||
@@ -549,14 +545,7 @@ vm_cache_remove_consumer(vm_cache *cache, vm_cache *consumer)
|
|||||||
|
|
||||||
consumer = (vm_cache *)list_get_first_item(&cache->consumers);
|
consumer = (vm_cache *)list_get_first_item(&cache->consumers);
|
||||||
|
|
||||||
bool merge = acquire_unreferenced_cache_pseudo_ref(consumer);
|
bool merge = acquire_unreferenced_cache_ref(consumer);
|
||||||
if (merge) {
|
|
||||||
// We managed to increment the reference count, but that's not a
|
|
||||||
// full reference. We get a real one now and decrement the ref count
|
|
||||||
// again.
|
|
||||||
vm_cache_acquire_ref(consumer);
|
|
||||||
atomic_add(&consumer->ref_count, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case we managed to grab a reference to the consumerRef,
|
// In case we managed to grab a reference to the consumerRef,
|
||||||
// this doesn't guarantee that we get the cache we wanted
|
// this doesn't guarantee that we get the cache we wanted
|
||||||
@@ -696,6 +685,9 @@ vm_cache_add_consumer_locked(vm_cache *cache, vm_cache *consumer)
|
|||||||
list_add_item(&cache->consumers, consumer);
|
list_add_item(&cache->consumers, consumer);
|
||||||
|
|
||||||
vm_cache_acquire_ref(cache);
|
vm_cache_acquire_ref(cache);
|
||||||
|
|
||||||
|
if (cache->store->ops->acquire_ref != NULL)
|
||||||
|
cache->store->ops->acquire_ref(cache->store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -714,6 +706,9 @@ vm_cache_insert_area_locked(vm_cache *cache, vm_area *area)
|
|||||||
area->cache_prev = NULL;
|
area->cache_prev = NULL;
|
||||||
cache->areas = area;
|
cache->areas = area;
|
||||||
|
|
||||||
|
if (cache->store->ops->acquire_ref != NULL)
|
||||||
|
cache->store->ops->acquire_ref(cache->store);
|
||||||
|
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,6 +725,9 @@ vm_cache_remove_area(vm_cache *cache, vm_area *area)
|
|||||||
if (cache->areas == area)
|
if (cache->areas == area)
|
||||||
cache->areas = area->cache_next;
|
cache->areas = area->cache_next;
|
||||||
|
|
||||||
|
if (cache->store->ops->release_ref)
|
||||||
|
cache->store->ops->release_ref(cache->store);
|
||||||
|
|
||||||
mutex_unlock(&cache->lock);
|
mutex_unlock(&cache->lock);
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
* Distributed under the terms of the NewOS License.
|
* Distributed under the terms of the NewOS License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <KernelExport.h>
|
#include <KernelExport.h>
|
||||||
#include <OS.h>
|
#include <OS.h>
|
||||||
@@ -16,15 +19,14 @@
|
|||||||
#include <condition_variable.h>
|
#include <condition_variable.h>
|
||||||
#include <kernel.h>
|
#include <kernel.h>
|
||||||
#include <thread.h>
|
#include <thread.h>
|
||||||
|
#include <util/AutoLock.h>
|
||||||
#include <vm.h>
|
#include <vm.h>
|
||||||
#include <vm_address_space.h>
|
#include <vm_address_space.h>
|
||||||
|
#include <vm_low_memory.h>
|
||||||
#include <vm_priv.h>
|
#include <vm_priv.h>
|
||||||
#include <vm_page.h>
|
#include <vm_page.h>
|
||||||
#include <vm_cache.h>
|
#include <vm_cache.h>
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
//#define TRACE_VM_PAGE
|
//#define TRACE_VM_PAGE
|
||||||
#ifdef TRACE_VM_PAGE
|
#ifdef TRACE_VM_PAGE
|
||||||
@@ -53,6 +55,7 @@ static vm_page *sPages;
|
|||||||
static addr_t sPhysicalPageOffset;
|
static addr_t sPhysicalPageOffset;
|
||||||
static size_t sNumPages;
|
static size_t sNumPages;
|
||||||
|
|
||||||
|
static ConditionVariable<page_queue> sFreePageCondition;
|
||||||
static spinlock sPageLock;
|
static spinlock sPageLock;
|
||||||
|
|
||||||
static sem_id modified_pages_available;
|
static sem_id modified_pages_available;
|
||||||
@@ -414,8 +417,8 @@ static int dump_free_page_table(int argc, char **argv)
|
|||||||
static status_t
|
static status_t
|
||||||
set_page_state_nolock(vm_page *page, int pageState)
|
set_page_state_nolock(vm_page *page, int pageState)
|
||||||
{
|
{
|
||||||
page_queue *from_q = NULL;
|
page_queue *fromQueue = NULL;
|
||||||
page_queue *to_q = NULL;
|
page_queue *toQueue = NULL;
|
||||||
|
|
||||||
switch (page->state) {
|
switch (page->state) {
|
||||||
case PAGE_STATE_BUSY:
|
case PAGE_STATE_BUSY:
|
||||||
@@ -423,16 +426,16 @@ set_page_state_nolock(vm_page *page, int pageState)
|
|||||||
case PAGE_STATE_INACTIVE:
|
case PAGE_STATE_INACTIVE:
|
||||||
case PAGE_STATE_WIRED:
|
case PAGE_STATE_WIRED:
|
||||||
case PAGE_STATE_UNUSED:
|
case PAGE_STATE_UNUSED:
|
||||||
from_q = &sActivePageQueue;
|
fromQueue = &sActivePageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_MODIFIED:
|
case PAGE_STATE_MODIFIED:
|
||||||
from_q = &sModifiedPageQueue;
|
fromQueue = &sModifiedPageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_FREE:
|
case PAGE_STATE_FREE:
|
||||||
from_q = &sFreePageQueue;
|
fromQueue = &sFreePageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_CLEAR:
|
case PAGE_STATE_CLEAR:
|
||||||
from_q = &sClearPageQueue;
|
fromQueue = &sClearPageQueue;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
panic("vm_page_set_state: vm_page %p in invalid state %d\n", page, page->state);
|
panic("vm_page_set_state: vm_page %p in invalid state %d\n", page, page->state);
|
||||||
@@ -442,10 +445,6 @@ set_page_state_nolock(vm_page *page, int pageState)
|
|||||||
if (page->cache != NULL)
|
if (page->cache != NULL)
|
||||||
panic("free page %p has cache", page);
|
panic("free page %p has cache", page);
|
||||||
}
|
}
|
||||||
if (pageState == PAGE_STATE_CLEAR || pageState == PAGE_STATE_FREE) {
|
|
||||||
if (page->cache != NULL)
|
|
||||||
panic("to be freed page %p has cache", page);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (pageState) {
|
switch (pageState) {
|
||||||
case PAGE_STATE_BUSY:
|
case PAGE_STATE_BUSY:
|
||||||
@@ -453,22 +452,31 @@ set_page_state_nolock(vm_page *page, int pageState)
|
|||||||
case PAGE_STATE_INACTIVE:
|
case PAGE_STATE_INACTIVE:
|
||||||
case PAGE_STATE_WIRED:
|
case PAGE_STATE_WIRED:
|
||||||
case PAGE_STATE_UNUSED:
|
case PAGE_STATE_UNUSED:
|
||||||
to_q = &sActivePageQueue;
|
toQueue = &sActivePageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_MODIFIED:
|
case PAGE_STATE_MODIFIED:
|
||||||
to_q = &sModifiedPageQueue;
|
toQueue = &sModifiedPageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_FREE:
|
case PAGE_STATE_FREE:
|
||||||
to_q = &sFreePageQueue;
|
toQueue = &sFreePageQueue;
|
||||||
break;
|
break;
|
||||||
case PAGE_STATE_CLEAR:
|
case PAGE_STATE_CLEAR:
|
||||||
to_q = &sClearPageQueue;
|
toQueue = &sClearPageQueue;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
panic("vm_page_set_state: invalid target state %d\n", pageState);
|
panic("vm_page_set_state: invalid target state %d\n", pageState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageState == PAGE_STATE_CLEAR || pageState == PAGE_STATE_FREE) {
|
||||||
|
if (sFreePageQueue.count + sClearPageQueue.count == 0)
|
||||||
|
sFreePageCondition.NotifyAll();
|
||||||
|
|
||||||
|
if (page->cache != NULL)
|
||||||
|
panic("to be freed page %p has cache", page);
|
||||||
|
}
|
||||||
|
|
||||||
page->state = pageState;
|
page->state = pageState;
|
||||||
move_page_to_queue(from_q, to_q, page);
|
move_page_to_queue(fromQueue, toQueue, page);
|
||||||
|
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
@@ -661,6 +669,82 @@ write_page(vm_page *page, bool fsReenter)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
page_thief(void* /*unused*/, int32 level)
|
||||||
|
{
|
||||||
|
uint32 steal;
|
||||||
|
int32 score;
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
default:
|
||||||
|
case B_LOW_MEMORY_NOTE:
|
||||||
|
steal = 10;
|
||||||
|
score = -20;
|
||||||
|
break;
|
||||||
|
case B_LOW_MEMORY_WARNING:
|
||||||
|
steal = 50;
|
||||||
|
score = -5;
|
||||||
|
break;
|
||||||
|
case B_LOW_MEMORY_CRITICAL:
|
||||||
|
steal = 500;
|
||||||
|
score = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm_page* page = NULL;
|
||||||
|
InterruptsSpinLocker locker(sPageLock);
|
||||||
|
|
||||||
|
while (steal > 0) {
|
||||||
|
if (!locker.IsLocked())
|
||||||
|
locker.Lock();
|
||||||
|
|
||||||
|
// find a candidate to steal from the inactive queue
|
||||||
|
|
||||||
|
for (int32 i = sActivePageQueue.count; i-- > 0;) {
|
||||||
|
// move page to the head of the queue so that we don't
|
||||||
|
// scan it again directly
|
||||||
|
page = dequeue_page(&sActivePageQueue);
|
||||||
|
enqueue_page(&sActivePageQueue, page);
|
||||||
|
|
||||||
|
if (page->state == PAGE_STATE_INACTIVE
|
||||||
|
&& page->usage_count <= score)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page == NULL) {
|
||||||
|
if (score == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
score = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
locker.Unlock();
|
||||||
|
|
||||||
|
// try to lock the page's cache
|
||||||
|
|
||||||
|
vm_cache* cache = vm_cache_acquire_page_cache_ref(page);
|
||||||
|
if (cache == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (mutex_trylock(&cache->lock) != B_OK
|
||||||
|
|| page->state != PAGE_STATE_INACTIVE) {
|
||||||
|
vm_cache_release_ref(cache);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can now steal this page
|
||||||
|
|
||||||
|
vm_cache_remove_page(cache, page);
|
||||||
|
vm_page_set_state(page, PAGE_STATE_FREE);
|
||||||
|
steal--;
|
||||||
|
|
||||||
|
mutex_unlock(&cache->lock);
|
||||||
|
vm_cache_release_ref(cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark - private kernel API
|
// #pragma mark - private kernel API
|
||||||
|
|
||||||
|
|
||||||
@@ -904,6 +988,11 @@ vm_page_init_post_thread(kernel_args *args)
|
|||||||
tid = thread_create_kernel_thread("pageout daemon", &pageout_daemon, B_FIRST_REAL_TIME_PRIORITY + 1);
|
tid = thread_create_kernel_thread("pageout daemon", &pageout_daemon, B_FIRST_REAL_TIME_PRIORITY + 1);
|
||||||
thread_resume_thread(tid);
|
thread_resume_thread(tid);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
new (&sFreePageCondition) ConditionVariable<page_queue>;
|
||||||
|
sFreePageCondition.Publish(&sFreePageQueue, "free page");
|
||||||
|
|
||||||
|
register_low_memory_handler(page_thief, NULL, 0);
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,6 +1056,9 @@ vm_mark_page_range_inuse(addr_t startPage, addr_t length)
|
|||||||
vm_page *
|
vm_page *
|
||||||
vm_page_allocate_page(int pageState)
|
vm_page_allocate_page(int pageState)
|
||||||
{
|
{
|
||||||
|
// TODO: we may want to have a "canWait" argument
|
||||||
|
|
||||||
|
ConditionVariableEntry<page_queue> freeConditionEntry;
|
||||||
page_queue *queue;
|
page_queue *queue;
|
||||||
page_queue *otherQueue;
|
page_queue *otherQueue;
|
||||||
|
|
||||||
@@ -983,33 +1075,43 @@ vm_page_allocate_page(int pageState)
|
|||||||
return NULL; // invalid
|
return NULL; // invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu_status state = disable_interrupts();
|
InterruptsSpinLocker locker(sPageLock);
|
||||||
acquire_spinlock(&sPageLock);
|
|
||||||
|
|
||||||
vm_page *page = dequeue_page(queue);
|
vm_page *page = NULL;
|
||||||
if (page == NULL) {
|
while (true) {
|
||||||
#ifdef DEBUG
|
page = dequeue_page(queue);
|
||||||
if (queue->count != 0)
|
|
||||||
panic("queue %p corrupted, count = %d\n", queue, queue->count);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// if the primary queue was empty, grap the page from the
|
|
||||||
// secondary queue
|
|
||||||
page = dequeue_page(otherQueue);
|
|
||||||
if (page == NULL) {
|
if (page == NULL) {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
if (otherQueue->count != 0) {
|
if (queue->count != 0)
|
||||||
panic("other queue %p corrupted, count = %d\n", otherQueue,
|
panic("queue %p corrupted, count = %d\n", queue, queue->count);
|
||||||
otherQueue->count);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ToDo: issue "someone" to free up some pages for us, and go into
|
// if the primary queue was empty, grap the page from the
|
||||||
// wait state until that's done
|
// secondary queue
|
||||||
panic("vm_allocate_page: out of memory! page state = %d\n",
|
page = dequeue_page(otherQueue);
|
||||||
pageState);
|
if (page == NULL) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (otherQueue->count != 0) {
|
||||||
|
panic("other queue %p corrupted, count = %d\n", otherQueue,
|
||||||
|
otherQueue->count);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
freeConditionEntry.Add(&sFreePageQueue);
|
||||||
|
vm_low_memory(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (page != NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// we need to wait until new pages become available
|
||||||
|
locker.Unlock();
|
||||||
|
|
||||||
|
freeConditionEntry.Wait();
|
||||||
|
|
||||||
|
locker.Lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page->cache != NULL)
|
if (page->cache != NULL)
|
||||||
panic("supposed to be free page %p has cache\n", page);
|
panic("supposed to be free page %p has cache\n", page);
|
||||||
|
|
||||||
@@ -1018,8 +1120,7 @@ vm_page_allocate_page(int pageState)
|
|||||||
|
|
||||||
enqueue_page(&sActivePageQueue, page);
|
enqueue_page(&sActivePageQueue, page);
|
||||||
|
|
||||||
release_spinlock(&sPageLock);
|
locker.Unlock();
|
||||||
restore_interrupts(state);
|
|
||||||
|
|
||||||
// if needed take the page from the free queue and zero it out
|
// if needed take the page from the free queue and zero it out
|
||||||
if (pageState == PAGE_STATE_CLEAR && oldPageState != PAGE_STATE_CLEAR)
|
if (pageState == PAGE_STATE_CLEAR && oldPageState != PAGE_STATE_CLEAR)
|
||||||
|
|||||||
Reference in New Issue
Block a user