diff --git a/headers/private/kernel/vm_cache.h b/headers/private/kernel/vm_cache.h index 10c5261f8f..955627ad47 100644 --- a/headers/private/kernel/vm_cache.h +++ b/headers/private/kernel/vm_cache.h @@ -27,6 +27,8 @@ void vm_cache_release_ref(vm_cache_ref *cache_ref); vm_page *vm_cache_lookup_page(vm_cache_ref *cacheRef, off_t page); void vm_cache_insert_page(vm_cache_ref *cacheRef, vm_page *page, off_t offset); void vm_cache_remove_page(vm_cache_ref *cacheRef, vm_page *page); +void vm_cache_remove_consumer(vm_cache_ref *cacheRef, vm_cache *consumer); +void vm_cache_add_consumer(vm_cache_ref *cacheRef, vm_cache *consumer); status_t vm_cache_write_modified(vm_cache_ref *ref, bool fsReenter); status_t vm_cache_set_minimal_commitment(vm_cache_ref *ref, off_t commitment); status_t vm_cache_resize(vm_cache_ref *cacheRef, off_t newSize); diff --git a/headers/private/kernel/vm_types.h b/headers/private/kernel/vm_types.h index 3ac0f6476b..bfd958015d 100644 --- a/headers/private/kernel/vm_types.h +++ b/headers/private/kernel/vm_types.h @@ -68,6 +68,9 @@ typedef struct vm_cache_ref { // vm_cache typedef struct vm_cache { + struct list_link consumer_link; + struct list consumers; + // list of caches that use this cache as a source vm_page *page_list; vm_cache_ref *ref; struct vm_cache *source; diff --git a/src/system/kernel/vm/vm.cpp b/src/system/kernel/vm/vm.cpp index d1365de922..9d34827a74 100644 --- a/src/system/kernel/vm/vm.cpp +++ b/src/system/kernel/vm/vm.cpp @@ -552,11 +552,10 @@ map_backing_store(vm_address_space *addressSpace, vm_store *store, void **_virtu newCache->temporary = 1; newCache->scan_skip = cache->scan_skip; - newCache->source = cache; - vm_cache_acquire_ref(cacheRef); + vm_cache_add_consumer(cacheRef, newCache); cache = newCache; - cacheRef = cache->ref; + cacheRef = newCache->ref; store = newStore; cache->virtual_size = offset + size; } @@ -1479,13 +1478,12 @@ vm_copy_on_write_area(vm_area *area) upperCache->temporary = 1; upperCache->scan_skip = lowerCache->scan_skip; upperCache->source = lowerCache; + list_add_item(&lowerCache->consumers, upperCache); upperCache->ref = upperCacheRef; upperCacheRef->cache = upperCache; - // we need to manually alter the ref_count - // ToDo: investigate a bit deeper if this is really correct - // (doesn't look like it, but it works) - lowerCacheRef->ref_count = upperCacheRef->ref_count; + // we need to manually alter the ref_count (divide it between the two) + lowerCacheRef->ref_count = upperCacheRef->ref_count - 1; upperCacheRef->ref_count = 1; // grab a ref to the cache object we're now linked to as a source diff --git a/src/system/kernel/vm/vm_cache.c b/src/system/kernel/vm/vm_cache.c index 149713fa99..24f93900d1 100644 --- a/src/system/kernel/vm/vm_cache.c +++ b/src/system/kernel/vm/vm_cache.c @@ -20,7 +20,8 @@ #include #include -#include +#include +#include //#define TRACE_VM_CACHE #ifdef TRACE_VM_CACHE @@ -73,11 +74,9 @@ page_hash_func(void *_p, const void *_key, uint32 range) status_t -vm_cache_init(kernel_args *ka) +vm_cache_init(kernel_args *args) { - vm_page p; - - page_cache_table = hash_init(PAGE_TABLE_SIZE, (int)&p.hash_next - (int)&p, + page_cache_table = hash_init(PAGE_TABLE_SIZE, offsetof(vm_page, hash_next), &page_compare_func, &page_hash_func); if (!page_cache_table) panic("vm_cache_init: cannot allocate memory for page cache hash table\n"); @@ -101,6 +100,7 @@ vm_cache_create(vm_store *store) if (cache == NULL) return NULL; + list_init(&cache->consumers); cache->page_list = NULL; cache->ref = NULL; cache->source = NULL; @@ -211,7 +211,7 @@ vm_cache_release_ref(vm_cache_ref *cache_ref) // remove the ref to the source if (cache_ref->cache->source) - vm_cache_release_ref(cache_ref->cache->source->ref); + vm_cache_remove_consumer(cache_ref->cache->source->ref, cache_ref->cache); mutex_destroy(&cache_ref->lock); free(cache_ref->cache); @@ -391,6 +391,56 @@ vm_cache_resize(vm_cache_ref *cacheRef, off_t newSize) } +/*! + Removes the \a consumer from the \a cacheRef's cache. + It will also release the reference to the cacheRef owned by the consumer. +*/ +void +vm_cache_remove_consumer(vm_cache_ref *cacheRef, vm_cache *consumer) +{ + vm_cache *cache = cacheRef->cache; + TRACE(("remove consumer vm cache %p from cache %p\n", consumer, cache)); + + mutex_lock(&cacheRef->lock); + + list_remove_item(&cache->consumers, consumer); + consumer->source = NULL; + + if (cacheRef->areas == NULL && cache->source != NULL + && !list_is_empty(&cache->consumers) + && cache->consumers.link.next == cache->consumers.link.prev) { + // The cache is not really needed anymore - it can be merged with its only + // consumer left. + // TODO! + TRACE(("merge vm cache %p (ref == %ld) with vm cache %p\n", + cache, cacheRef->ref_count, cache->consumers.link.next)); + } + + mutex_unlock(&cacheRef->lock); + vm_cache_release_ref(cacheRef); +} + + +/*! + Marks the \a cacheRef's cache as source of the \a consumer cache, + and adds the \a consumer to its list. + This also grabs a reference to the source cache. +*/ +void +vm_cache_add_consumer(vm_cache_ref *cacheRef, vm_cache *consumer) +{ + TRACE(("add consumer vm cache %p to cache %p\n", consumer, cacheRef->cache)); + mutex_lock(&cacheRef->lock); + + consumer->source = cacheRef->cache; + list_add_item(&cacheRef->cache->consumers, consumer); + + mutex_unlock(&cacheRef->lock); + + vm_cache_acquire_ref(cacheRef); +} + + status_t vm_cache_insert_area(vm_cache_ref *cache_ref, vm_area *area) {