diff options
Diffstat (limited to 'squashfs-tools/caches-queues-lists.c')
-rw-r--r-- | squashfs-tools/caches-queues-lists.c | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/squashfs-tools/caches-queues-lists.c b/squashfs-tools/caches-queues-lists.c new file mode 100644 index 0000000..f6bcba4 --- /dev/null +++ b/squashfs-tools/caches-queues-lists.c @@ -0,0 +1,647 @@ +/* + * Create a squashfs filesystem. This is a highly compressed read only + * filesystem. + * + * Copyright (c) 2013, 2014, 2019, 2021 + * Phillip Lougher <phillip@squashfs.org.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * caches-queues-lists.c + */ + +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "mksquashfs_error.h" +#include "caches-queues-lists.h" + +extern int add_overflow(int, int); +extern int multiply_overflow(int, int); + +#define TRUE 1 +#define FALSE 0 + +struct queue *queue_init(int size) +{ + struct queue *queue = malloc(sizeof(struct queue)); + + if(queue == NULL) + MEM_ERROR(); + + if(add_overflow(size, 1) || + multiply_overflow(size + 1, sizeof(void *))) + BAD_ERROR("Size too large in queue_init\n"); + + queue->data = malloc(sizeof(void *) * (size + 1)); + if(queue->data == NULL) + MEM_ERROR(); + + queue->size = size + 1; + queue->readp = queue->writep = 0; + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->empty, NULL); + pthread_cond_init(&queue->full, NULL); + + return queue; +} + + +void queue_put(struct queue *queue, void *data) +{ + int nextp; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + while((nextp = (queue->writep + 1) % queue->size) == queue->readp) + pthread_cond_wait(&queue->full, &queue->mutex); + + queue->data[queue->writep] = data; + queue->writep = nextp; + pthread_cond_signal(&queue->empty); + pthread_cleanup_pop(1); +} + + +void *queue_get(struct queue *queue) +{ + void *data; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + while(queue->readp == queue->writep) + pthread_cond_wait(&queue->empty, &queue->mutex); + + data = queue->data[queue->readp]; + queue->readp = (queue->readp + 1) % queue->size; + pthread_cond_signal(&queue->full); + pthread_cleanup_pop(1); + + return data; +} + + +int queue_empty(struct queue *queue) +{ + int empty; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + empty = queue->readp == queue->writep; + + pthread_cleanup_pop(1); + + return empty; +} + + +void queue_flush(struct queue *queue) +{ + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + queue->readp = queue->writep; + + pthread_cleanup_pop(1); +} + + +void dump_queue(struct queue *queue) +{ + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + printf("\tMax size %d, size %d%s\n", queue->size - 1, + queue->readp <= queue->writep ? queue->writep - queue->readp : + queue->size - queue->readp + queue->writep, + queue->readp == queue->writep ? " (EMPTY)" : + ((queue->writep + 1) % queue->size) == queue->readp ? + " (FULL)" : ""); + + pthread_cleanup_pop(1); +} + + +/* define seq queue hash tables */ +#define CALCULATE_SEQ_HASH(N) CALCULATE_HASH(N) + +/* Called with the seq queue mutex held */ +INSERT_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq) + +/* Called with the cache mutex held */ +REMOVE_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq); + + +struct seq_queue *seq_queue_init() +{ + struct seq_queue *queue = malloc(sizeof(struct seq_queue)); + if(queue == NULL) + MEM_ERROR(); + + memset(queue, 0, sizeof(struct seq_queue)); + + pthread_mutex_init(&queue->mutex, NULL); + pthread_cond_init(&queue->wait, NULL); + + return queue; +} + + +void seq_queue_put(struct seq_queue *queue, struct file_buffer *entry) +{ + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + insert_seq_hash_table(queue, entry); + + if(entry->fragment) + queue->fragment_count ++; + else + queue->block_count ++; + + if(entry->sequence == queue->sequence) + pthread_cond_signal(&queue->wait); + + pthread_cleanup_pop(1); +} + + +struct file_buffer *seq_queue_get(struct seq_queue *queue) +{ + /* + * Return next buffer from queue in sequence order (queue->sequence). If + * found return it, otherwise wait for it to arrive. + */ + int hash = CALCULATE_SEQ_HASH(queue->sequence); + struct file_buffer *entry; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + while(1) { + for(entry = queue->hash_table[hash]; entry; + entry = entry->seq_next) + if(entry->sequence == queue->sequence) + break; + + if(entry) { + /* + * found the buffer in the queue, decrement the + * appropriate count, and remove from hash list + */ + if(entry->fragment) + queue->fragment_count --; + else + queue->block_count --; + + remove_seq_hash_table(queue, entry); + + queue->sequence ++; + + break; + } + + /* entry not found, wait for it to arrive */ + pthread_cond_wait(&queue->wait, &queue->mutex); + } + + pthread_cleanup_pop(1); + + return entry; +} + + +void seq_queue_flush(struct seq_queue *queue) +{ + int i; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + for(i = 0; i < HASH_SIZE; i++) + queue->hash_table[i] = NULL; + + queue->fragment_count = queue->block_count = 0; + + pthread_cleanup_pop(1); +} + + +void dump_seq_queue(struct seq_queue *queue, int fragment_queue) +{ + int size; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex); + pthread_mutex_lock(&queue->mutex); + + size = fragment_queue ? queue->fragment_count : queue->block_count; + + printf("\tMax size unlimited, size %d%s\n", size, + size == 0 ? " (EMPTY)" : ""); + + pthread_cleanup_pop(1); +} + + +/* define cache hash tables */ +#define CALCULATE_CACHE_HASH(N) CALCULATE_HASH(llabs(N)) + +/* Called with the cache mutex held */ +INSERT_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash) + +/* Called with the cache mutex held */ +REMOVE_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash); + +/* define cache free list */ + +/* Called with the cache mutex held */ +INSERT_LIST(free, struct file_buffer) + +/* Called with the cache mutex held */ +REMOVE_LIST(free, struct file_buffer) + + +struct cache *cache_init(int buffer_size, int max_buffers, int noshrink_lookup, + int first_freelist) +{ + struct cache *cache = malloc(sizeof(struct cache)); + + if(cache == NULL) + MEM_ERROR(); + + cache->max_buffers = max_buffers; + cache->buffer_size = buffer_size; + cache->count = 0; + cache->used = 0; + cache->free_list = NULL; + + /* + * The cache will grow up to max_buffers in size in response to + * an increase in readhead/number of buffers in flight. But + * once the outstanding buffers gets returned, we can either elect + * to shrink the cache, or to put the freed blocks onto a free list. + * + * For the caches where we want to do lookup (fragment/writer), + * a don't shrink policy is best, for the reader cache it + * makes no sense to keep buffers around longer than necessary as + * we don't do any lookup on those blocks. + */ + cache->noshrink_lookup = noshrink_lookup; + + /* + * The default use freelist before growing cache policy behaves + * poorly with appending - with many duplicates the caches + * do not grow due to the fact that large queues of outstanding + * fragments/writer blocks do not occur, leading to small caches + * and un-uncessary performance loss to frequent cache + * replacement in the small caches. Therefore with appending + * change the policy to grow the caches before reusing blocks + * from the freelist + */ + cache->first_freelist = first_freelist; + + memset(cache->hash_table, 0, sizeof(struct file_buffer *) * 65536); + pthread_mutex_init(&cache->mutex, NULL); + pthread_cond_init(&cache->wait_for_free, NULL); + pthread_cond_init(&cache->wait_for_unlock, NULL); + + return cache; +} + + +struct file_buffer *cache_lookup(struct cache *cache, long long index) +{ + /* Lookup block in the cache, if found return with usage count + * incremented, if not found return NULL */ + int hash = CALCULATE_CACHE_HASH(index); + struct file_buffer *entry; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next) + if(entry->index == index) + break; + + if(entry) { + /* found the block in the cache, increment used count and + * if necessary remove from free list so it won't disappear + */ + if(entry->used == 0) { + remove_free_list(&cache->free_list, entry); + cache->used ++; + } + entry->used ++; + } + + pthread_cleanup_pop(1); + + return entry; +} + + +static struct file_buffer *cache_freelist(struct cache *cache) +{ + struct file_buffer *entry = cache->free_list; + + remove_free_list(&cache->free_list, entry); + + /* a block on the free_list is hashed */ + remove_cache_hash_table(cache, entry); + + cache->used ++; + return entry; +} + + +static struct file_buffer *cache_alloc(struct cache *cache) +{ + struct file_buffer *entry = malloc(sizeof(struct file_buffer) + + cache->buffer_size); + if(entry == NULL) + MEM_ERROR(); + + entry->cache = cache; + entry->free_prev = entry->free_next = NULL; + cache->count ++; + return entry; +} + + +static struct file_buffer *_cache_get(struct cache *cache, long long index, + int hash) +{ + /* Get a free block out of the cache indexed on index. */ + struct file_buffer *entry = NULL; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + while(1) { + if(cache->noshrink_lookup) { + /* first try to get a block from the free list */ + if(cache->first_freelist && cache->free_list) + entry = cache_freelist(cache); + else if(cache->count < cache->max_buffers) { + entry = cache_alloc(cache); + cache->used ++; + } else if(!cache->first_freelist && cache->free_list) + entry = cache_freelist(cache); + } else { /* shrinking non-lookup cache */ + if(cache->count < cache->max_buffers) { + entry = cache_alloc(cache); + if(cache->count > cache->max_count) + cache->max_count = cache->count; + } + } + + if(entry) + break; + + /* wait for a block */ + pthread_cond_wait(&cache->wait_for_free, &cache->mutex); + } + + /* initialise block and if hash is set insert into the hash table */ + entry->used = 1; + entry->locked = FALSE; + entry->wait_on_unlock = FALSE; + entry->error = FALSE; + if(hash) { + entry->index = index; + insert_cache_hash_table(cache, entry); + } + + pthread_cleanup_pop(1); + + return entry; +} + + +struct file_buffer *cache_get(struct cache *cache, long long index) +{ + return _cache_get(cache, index, 1); +} + + +struct file_buffer *cache_get_nohash(struct cache *cache) +{ + return _cache_get(cache, 0, 0); +} + + +void cache_hash(struct file_buffer *entry, long long index) +{ + struct cache *cache = entry->cache; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + entry->index = index; + insert_cache_hash_table(cache, entry); + + pthread_cleanup_pop(1); +} + + +void cache_block_put(struct file_buffer *entry) +{ + struct cache *cache; + + /* + * Finished with this cache entry, once the usage count reaches zero it + * can be reused. + * + * If noshrink_lookup is set, put the block onto the free list. + * As blocks remain accessible via the hash table they can be found + * getting a new lease of life before they are reused. + * + * if noshrink_lookup is not set then shrink the cache. + */ + + if(entry == NULL) + return; + + if(entry->cache == NULL) { + free(entry); + return; + } + + cache = entry->cache; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + entry->used --; + if(entry->used == 0) { + if(cache->noshrink_lookup) { + insert_free_list(&cache->free_list, entry); + cache->used --; + } else { + free(entry); + cache->count --; + } + + /* One or more threads may be waiting on this block */ + pthread_cond_signal(&cache->wait_for_free); + } + + pthread_cleanup_pop(1); +} + + +void dump_cache(struct cache *cache) +{ + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + if(cache->noshrink_lookup) + printf("\tMax buffers %d, Current size %d, Used %d, %s\n", + cache->max_buffers, cache->count, cache->used, + cache->free_list ? "Free buffers" : "No free buffers"); + else + printf("\tMax buffers %d, Current size %d, Maximum historical " + "size %d\n", cache->max_buffers, cache->count, + cache->max_count); + + pthread_cleanup_pop(1); +} + + +struct file_buffer *cache_get_nowait(struct cache *cache, long long index) +{ + struct file_buffer *entry = NULL; + /* + * block doesn't exist, create it, but return it with the + * locked flag set, so nothing tries to use it while it doesn't + * contain data. + * + * If there's no space in the cache then return NULL. + */ + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + /* first try to get a block from the free list */ + if(cache->first_freelist && cache->free_list) + entry = cache_freelist(cache); + else if(cache->count < cache->max_buffers) { + entry = cache_alloc(cache); + cache->used ++; + } else if(!cache->first_freelist && cache->free_list) + entry = cache_freelist(cache); + + if(entry) { + /* initialise block and insert into the hash table */ + entry->used = 1; + entry->locked = TRUE; + entry->wait_on_unlock = FALSE; + entry->error = FALSE; + entry->index = index; + insert_cache_hash_table(cache, entry); + } + + pthread_cleanup_pop(1); + + return entry; +} + + +struct file_buffer *cache_lookup_nowait(struct cache *cache, long long index, + char *locked) +{ + /* + * Lookup block in the cache, if found return it with the locked flag + * indicating whether it is currently locked. In both cases increment + * the used count. + * + * If it doesn't exist in the cache return NULL; + */ + int hash = CALCULATE_CACHE_HASH(index); + struct file_buffer *entry; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + /* first check if the entry already exists */ + for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next) + if(entry->index == index) + break; + + if(entry) { + if(entry->used == 0) { + remove_free_list(&cache->free_list, entry); + cache->used ++; + } + entry->used ++; + *locked = entry->locked; + } + + pthread_cleanup_pop(1); + + return entry; +} + + +void cache_wait_unlock(struct file_buffer *buffer) +{ + struct cache *cache = buffer->cache; + + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + while(buffer->locked) { + /* + * another thread is filling this in, wait until it + * becomes unlocked. Used has been incremented to ensure it + * doesn't get reused. By definition a block can't be + * locked and unused, and so we don't need to worry + * about it being on the freelist now, but, it may + * become unused when unlocked unless used is + * incremented + */ + buffer->wait_on_unlock = TRUE; + pthread_cond_wait(&cache->wait_for_unlock, &cache->mutex); + } + + pthread_cleanup_pop(1); +} + + +void cache_unlock(struct file_buffer *entry) +{ + struct cache *cache = entry->cache; + + /* + * Unlock this locked cache entry. If anything is waiting for this + * to become unlocked, wake it up. + */ + pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex); + pthread_mutex_lock(&cache->mutex); + + entry->locked = FALSE; + + if(entry->wait_on_unlock) { + entry->wait_on_unlock = FALSE; + pthread_cond_broadcast(&cache->wait_for_unlock); + } + + pthread_cleanup_pop(1); +} |