mirror of
https://github.com/pennmush/pennmush
synced 2026-08-12 22:23:05 -04:00
Leave the function marked as deprecated to avoid breaking third-party code that uses it. Functions that return static buffers must die!
2951 lines
87 KiB
C
2951 lines
87 KiB
C
/**
|
|
* \file chunk.c
|
|
*
|
|
* \brief Chunk memory management system
|
|
*
|
|
* <h3>Synopsis:</h3>
|
|
* The chunk memory management system has three goals: to reduce overall
|
|
* memory consumption, to improve locality of reference, and to allow
|
|
* less-used sections of memory to be paged out to disk. These three
|
|
* goals are accomplished by implementing an allocation management layer
|
|
* on top of malloc(), with significantly reduced overhead and the ability
|
|
* to rearrange allocations to actively control fragmentation and increase
|
|
* locality.
|
|
*
|
|
*
|
|
* <h3>Basic operation:</h3>
|
|
* The managed memory pool is divided into regions of approximately 64KB.
|
|
* These regions contain variable-size chunks representing allocated and
|
|
* available (free) memory. No individual allocation may be larger than
|
|
* will fit in a single region, and no allocation may be smaller than one
|
|
* byte. Each chunk has between two and four bytes of overhead (indicating
|
|
* the used/free status, the size of the chunk, and the number of
|
|
* dereferences for the chunk), and each region has additional overhead
|
|
* of about 42 bytes.
|
|
*
|
|
* Allocations are made with the chunk_create() call, which is given
|
|
* the size of the data, the data value to be stored, and an initial
|
|
* dereference count to be assigned to the chunk. Once created, the
|
|
* value of a chunk cannot be changed; the storage is immutable.
|
|
* chunk_create() returns an integral reference value that can be
|
|
* used to retrieve or free the allocation.
|
|
*
|
|
* Allocations are accessed with the chunk_fetch(), chunk_len(), and
|
|
* chunk_derefs() calls. Each of these if given a reference (as
|
|
* returned by chunk_create()), and chunk_fetch() is additionally
|
|
* given a buffer and length to fill with the allocated value. Both
|
|
* chunk_fetch() and chunk_len() increment a chunk's dereference
|
|
* count (up to the maximum of 255), which is used in migration to
|
|
* improve locality.
|
|
*
|
|
* Allocations are freed with the chunk_delete() call, which also
|
|
* requires a reference as input.
|
|
*
|
|
* Finally, allocations are allowed to rearrange themselves with the
|
|
* chunk_migration() call. chunk_migration() takes an array of
|
|
* pointers to chunk references as input, and examines each of the
|
|
* indicated chunks to see which need to be moved to improve the
|
|
* distribution of allocations. If any allocations are moved, then
|
|
* the references to the moved allocations are updated in place
|
|
* (hence the array of pointers to references, instead of just an
|
|
* array of references). Migration may be done incrementally by
|
|
* submitting only a portion of the allocations with each call to
|
|
* chunk_migration(); however, _all_ allocations made with chunk_create()
|
|
* must eventually be submitted for migration in order to maintain the
|
|
* memory pool in a non-fragmented state.
|
|
*
|
|
*
|
|
* <h3>Migration:</h3>
|
|
* Under normal conditions, extended use of this chunk allocation system
|
|
* would lead to a significantly fragmented datastore, unless there was
|
|
* some means to defragment the storage arena. In the long run, this could
|
|
* be very bad, leading to quite a mess. Calling chunk_migration() gives
|
|
* the allocator permission to move allocations around both to defragment
|
|
* the arena and to improve locality of reference (by making sure that
|
|
* all the infrequently used chunks are segregated from the chunks in
|
|
* active use). Of course, moving all the allocated chunks at once would
|
|
* be a slow and painful process. Instead, migration may be done
|
|
* incrementally, giving permission to move a small number of chunks
|
|
* at any one time, and spreading out the cost of defragmenting the
|
|
* data store.
|
|
*
|
|
* Just because you give permission to move a chunk doesn't mean that it
|
|
* will be moved. The chunk may be perfectly happy where it is, with
|
|
* no need to move it elsewhere. Chunks are only moved when their
|
|
* personal happiness would be improved by a move. In general, maximizing
|
|
* the happiness of individual chunks will improve the happiness of the
|
|
* whole.
|
|
*
|
|
* There are two things that factor into a chunk's happiness.
|
|
* The things that make a chunk unhappy are:
|
|
* <ul>
|
|
* <li> Having a dereference count different from the region average.
|
|
* The greater the difference, the more unhappy the chunk is.
|
|
* <li> Being in a sparsely populated region. The fewer chunks in a
|
|
* region, the more unhappy the chunks in it.
|
|
* </ul>
|
|
* Neither of these factors are absolute; both of them have different
|
|
* weights that add into a general unhappiness for the chunk. The lower
|
|
* the unhappiness, the better.
|
|
*
|
|
* Over time and usage, the dereference counts for chunks will increase
|
|
* and eventually reach a maximum value of 255. (The count is limited
|
|
* by the fact that it's stored in a single byte for each chunk.) If
|
|
* this is left unchecked, eventually all chunks would have a dereference
|
|
* count of 255, and the counts would be useless for improving locality.
|
|
* To counteract this, when the average dereference count for a certain
|
|
* number of regions exceeds 128, the 'migration period' is incremented
|
|
* and all chunk dereference counts are halved. The critical number of
|
|
* regions is determined based on the cache size and the total number of
|
|
* regions. If you're not using forking dumps, then period change should
|
|
* be controlled primarily by the frequency of database dumps (which end
|
|
* up incrementing the dereference count on all chunks, and thus all
|
|
* regions). Given a dump frequency of once per hour (the default), there
|
|
* should be a period change about every 2.6 days.
|
|
*
|
|
*
|
|
* <h3>Statistics:</h3>
|
|
* The chunk memory management system keeps several statistics about
|
|
* the allocation pool, both to maintain good operation through active
|
|
* encouragement of locality, and to satisfy the curiosity of people
|
|
* using the system (and its designer ;-)). These statistics are
|
|
* reported (in PennMUSH) through the use of the \@stats command,
|
|
* with /chunks switch.
|
|
*
|
|
* \@stats/chunks generates output similar to this:
|
|
* \verbatim
|
|
* Chunks: 99407 allocated ( 8372875 bytes, 223808 ( 2%) overhead)
|
|
* 74413 short ( 1530973 bytes, 148826 ( 9%)
|
|
* overhead)
|
|
* 24994 medium ( 6841902 bytes, 74982 ( 1%)
|
|
* overhead)
|
|
* 0 long ( 0 bytes, 0 ( 0%)
|
|
* overhead)
|
|
* 128 free ( 1319349 bytes, 23058 ( 1%)
|
|
* fragmented)
|
|
* Regions: 147 total, 16 cached
|
|
* Paging: 158686 out, 158554 in
|
|
* Storage: 9628500 total (86% saturation)
|
|
*
|
|
* Period: 1 ( 5791834 accesses so far, 1085 chunks at max)
|
|
* Migration: 245543 moves this period
|
|
* 145536 slide
|
|
* 45 away
|
|
* 30719 fill exact
|
|
* 69243 fill inexact
|
|
* \endverbatim
|
|
*
|
|
* First, the number of allocated chunks is given, along with their
|
|
* total size and overhead. Then, the allocated chunks are broken up
|
|
* by size-range; short chunks (2 to 63 bytes) with two bytes of
|
|
* overhead each, medium chunks (64 to 8191 bytes) with three bytes of
|
|
* overhead each, and long chunks (8192 to ~64K bytes) with four bytes
|
|
* of overhead each. Rounding out the individual chunk statistics is
|
|
* the number of free chunks, their total size, and the amount of
|
|
* fragmented free space (free space not in the largest free chunk for
|
|
* its region is considered fragmented).
|
|
*
|
|
* After that, the total amount of storage (in memory or on disk) used
|
|
* is given, along with the saturation rate (where saturation is
|
|
* indicated by what fraction of the used space is actually allocated
|
|
* in chunks).
|
|
*
|
|
* Next comes statistics on regions: the number of regions in use and
|
|
* the number held in the memory cache. All regions not in the cache
|
|
* are paged out to disk. Paging statistics follow, listing the
|
|
* number of times a region has been moved out of or into memory
|
|
* cache.
|
|
*
|
|
* Finally comes statistics on migration and the migration period.
|
|
* The period number is listed, along with the total number of
|
|
* dereferences in the period and how many chunks have the maximum
|
|
* dereference count of 255. Then the amount of migration movement is
|
|
* listed, both in total and broken up by category. Slides occur when
|
|
* an allocation is shifted to the other side of a neighboring free
|
|
* space. Away moves are made when an allocation is extremely unhappy
|
|
* where it is, and is pushed out to somewhere else. Fills are when
|
|
* an allocation is moved in order to fill in a free space; the space
|
|
* can be either exactly filled by the move, or inexactly filled
|
|
* (leaving some remaining free space).
|
|
*
|
|
*
|
|
* <h3>Histograms:</h3>
|
|
* The chunk memory management system can also display a few
|
|
* histograms about itself. These histograms are reported (in PennMUSH)
|
|
* through the use of the \@stats command, with the /regions, /freespace,
|
|
* or /paging switches.
|
|
*
|
|
* All of \@stats/regions, \@stats/freespace, and \@stats/paging produce
|
|
* histograms vs. region average dereference count. The histograms
|
|
* use buckets four counts wide, so all regions from 0-3 will be in
|
|
* the first bucket, 4-7 in the second, etc., up to 252-255 in the
|
|
* last bucket. If the heights of the buckets are significantly
|
|
* different, then the highest spikes will be allowed to extend off
|
|
* the top of the histogram (with their real values labeled in
|
|
* parenthesis next to them).
|
|
*
|
|
* \@stats/regions is a histogram of how many regions at each count
|
|
* currently exist. In a healthy game, there should be a large spike
|
|
* at some dereference count between 64 and 128 (representing the
|
|
* largely unused portion of the database), a lesser spike at 255
|
|
* (representing the portion of the database that's used very frequently),
|
|
* and a smattering of regions at other counts, with either new areas
|
|
* of the database (below the large spike) or semi-frequently used
|
|
* areas (above the large spike). New migration periods occur when
|
|
* the large spike would pass 128, at which point everything is halved
|
|
* and the spike is pushed back down to 64.
|
|
*
|
|
* \@stats/freespace is a histogram of how much free space exists in
|
|
* regions at each dereference count. This histogram is included
|
|
* to aid in diagnosis of the cause for dropping saturation rates.
|
|
*
|
|
* \@stats/paging is a histogram of the number of regions being paged
|
|
* in or out at each dereference count. As of this writing, a very
|
|
* unhealthy behaviour is observed, wherein the histogram shows a
|
|
* trapeziod between 64 and 128, drowning out most of the rest of the
|
|
* chart. This indicates that as time goes on, the attributes
|
|
* associated with a single low-use object are getting scattered
|
|
* randomly throughout all the low-use regions, and thus when dumps
|
|
* occur (with their linear enumeration of all attributes on objects)
|
|
* the low-use regions thrash in and out of cache. This can be very
|
|
* detrimental to dump performance. Something will have to be done
|
|
* to fix this tendency of migration. Healthy behaviour will make
|
|
* some other pattern in the paging histogram which has not yet been
|
|
* determined.
|
|
*/
|
|
|
|
#include "copyrite.h"
|
|
#include "chunk.h"
|
|
|
|
#define _XOPEN_SOURCE 600
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <sys/types.h>
|
|
#ifdef WIN32
|
|
#include <wtypes.h>
|
|
#include <io.h>
|
|
#else
|
|
#ifndef __USE_UNIX98
|
|
#define __USE_UNIX98
|
|
#endif /* __USE_UNIX98 */
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#include "command.h"
|
|
#include "conf.h"
|
|
#include "dbdefs.h"
|
|
#include "externs.h"
|
|
#include "intrface.h"
|
|
#include "log.h"
|
|
#include "mymalloc.h"
|
|
#include "notify.h"
|
|
#include "strutil.h"
|
|
|
|
#ifdef WIN32
|
|
#pragma warning(disable : 4761) /* disable warning re conversion */
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#define PRIdS "I"
|
|
#else
|
|
#define PRIdS "t"
|
|
#endif
|
|
|
|
/* Data format is a 16 bit length field , followed by 16 bits reserved
|
|
for future use (And to ensure 4-byte alignment), followed by the
|
|
data. Derefs are not used. */
|
|
|
|
static chunk_reference_t
|
|
acm_chunk_create(char const *data, uint16_t len,
|
|
uint8_t derefs __attribute__((__unused__)))
|
|
{
|
|
uint8_t *chunk;
|
|
|
|
chunk = mush_malloc(len + 4, "chunk");
|
|
|
|
memset(chunk, 0, 4);
|
|
memcpy(chunk, &len, 2);
|
|
memcpy(chunk + 4, data, len);
|
|
|
|
return (uintptr_t) chunk;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_delete(chunk_reference_t reference)
|
|
{
|
|
if (reference)
|
|
mush_free((void *) reference, "chunk");
|
|
}
|
|
|
|
static uint16_t
|
|
acm_chunk_fetch(chunk_reference_t reference, char *buffer, uint16_t buffer_len)
|
|
{
|
|
uint16_t len;
|
|
|
|
if (!reference)
|
|
return 0;
|
|
|
|
memcpy(&len, (void *) reference, 2);
|
|
|
|
if (buffer_len >= len) {
|
|
memcpy(buffer, ((uint8_t *) reference) + 4, len);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static uint16_t
|
|
acm_chunk_len(chunk_reference_t reference)
|
|
{
|
|
uint16_t len;
|
|
if (!reference)
|
|
return 0;
|
|
memcpy(&len, (void *) reference, 2);
|
|
return len;
|
|
}
|
|
|
|
static uint8_t
|
|
acm_chunk_derefs(chunk_reference_t reference __attribute__((__unused__)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_migration(int count __attribute__((__unused__)),
|
|
chunk_reference_t **references __attribute__((__unused__)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
static int
|
|
acm_chunk_num_swapped(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_init(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_stats(dbref player,
|
|
enum chunk_stats_type which __attribute__((__unused__)))
|
|
{
|
|
notify(player,
|
|
T("Attribute storage stats are not supported for malloc scheme."));
|
|
}
|
|
|
|
static void
|
|
acm_chunk_new_period(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static int
|
|
acm_chunk_fork_file(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_fork_parent(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_fork_child(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
acm_chunk_fork_done(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* A whole bunch of debugging #defines. */
|
|
/** Basic debugging stuff - are assertions checked? */
|
|
#define CHUNK_DEBUG
|
|
/** Paranoid people check for region validity after every operation
|
|
* that modifies a region. */
|
|
#define CHUNK_PARANOID
|
|
/** Log all moves and slides during migration. */
|
|
#undef DEBUG_CHUNK_MIGRATE
|
|
/** Log creation of regions. */
|
|
#undef DEBUG_CHUNK_REGION_CREATE
|
|
/** Log paging of regions. */
|
|
#undef DEBUG_CHUNK_PAGING
|
|
/** Log all mallocs. */
|
|
#undef DEBUG_CHUNK_MALLOC
|
|
|
|
/** For debugging, we keep a rolling log of debug messages.
|
|
* These get dumped to disk if we're about to panic.
|
|
*/
|
|
#define ROLLING_LOG_SIZE 200
|
|
#define ROLLING_LOG_ENTRY_LEN 1024
|
|
|
|
/* debug... */
|
|
#ifdef CHUNK_DEBUG
|
|
#define ASSERT(x) assert(x)
|
|
#else /* CHUNK_DEBUG */
|
|
static int ignore; /**< Used to shut up compiler warnings when not asserting */
|
|
#define ASSERT(x) ignore++
|
|
#endif /* CHUNK_DEBUG */
|
|
|
|
/*
|
|
* Sizes, limits, etc.
|
|
*/
|
|
/** Region size, including header.
|
|
* This is a little less than 64K to allow for malloc overhead without
|
|
* spilling into next page */
|
|
#define REGION_SIZE 65500
|
|
|
|
/** Region capacity.
|
|
* This is the size minus the fixed region overhead.
|
|
*/
|
|
#define REGION_CAPACITY (REGION_SIZE - FIRST_CHUNK_OFFSET_IN_REGION)
|
|
|
|
/** Maximum chunk length.
|
|
* This is fairly arbitrary, but must be less than
|
|
* REGION_CAPACITY (it must fit in a region).
|
|
*/
|
|
#define MAX_CHUNK_LEN (16384 - 1)
|
|
|
|
/** Number of oddballs tracked in regions.
|
|
* This is used to figure out when we should pull regions in because
|
|
* we have an opportunity to migrate chunks that don't match.
|
|
* Relatively arbitrary; too low means you don't move things out
|
|
* enough, but boosting it too high wastes memory.
|
|
*/
|
|
#define NUM_ODDBALLS 10
|
|
|
|
/** Minimum disagreement to be an oddball.
|
|
* This is used to figure out when we should pull regions in because
|
|
* we have an opportunity to migrate chunks that don't match.
|
|
* Relatively arbitrary; too low means you don't move things out
|
|
* enough, but boosting it too high wastes migration time.
|
|
*/
|
|
#define ODDBALL_THRESHOLD 8
|
|
|
|
/*
|
|
* FIXME: pulling config variables out of my left ear. Fix later.
|
|
*/
|
|
/** How much space is initially allocated for the in-memory region array? */
|
|
#define FIXME_INIT_REGION_LEN 20
|
|
/** How much does the region array grow by each time it has to grow? */
|
|
#define FIXME_REGION_ARRAY_INCREMENT 10
|
|
|
|
/** Limit for when being a nearly-empty region counts against being
|
|
* a good region. This is exponential: an empty region gets a penalty
|
|
* of 1 << LONLINESS_LIMIT. A near-empty region gets a penalty of
|
|
* 1 << (LONLINESS_LIMIT - used_count).
|
|
*
|
|
* Rationale: we don't want to reuse empty regions (or make new regions)
|
|
* for trivialities.
|
|
*/
|
|
#define LONLINESS_LIMIT 5
|
|
|
|
/** Free space limit for when we consider making new regions.
|
|
* The total free space must be less than this percent of capacity.
|
|
*
|
|
* Rationale: we don't want to waste memory with lots of extra regions.
|
|
*/
|
|
#define FREE_PERCENT_LIMIT 2
|
|
|
|
/** Bias for allocating chunks in a region that's already in memory.
|
|
* Actually, this is a bias against allocating in swapped-out regions,
|
|
* but that's a nit...
|
|
*
|
|
* Rationale: reduce the amount of paging during migration.
|
|
*/
|
|
#define IN_MEMORY_BIAS 4
|
|
|
|
/*
|
|
* Structures and Accessor Macros
|
|
*/
|
|
/*
|
|
* What a chunk_reference_t looks like from the inside
|
|
*/
|
|
/** Get the region from a chunk_reference_t. */
|
|
#define ChunkReferenceToRegion(ref) ((ref) >> 16)
|
|
/** Get the offset from a chunk_reference_t. */
|
|
#define ChunkReferenceToOffset(ref) ((ref) &0xFFFF)
|
|
/** Make a chunk_reference_t from a region and offset. */
|
|
#define ChunkReference(region, offset) \
|
|
((chunk_reference_t)(((region) << 16) | (offset)))
|
|
|
|
/** Sentinel value used to mark unused cache regions. */
|
|
#define INVALID_REGION_ID 0xffff
|
|
|
|
/**
|
|
* \verbatim
|
|
* The chunk headers look like this:
|
|
*
|
|
* Short:
|
|
* byte 0 byte 1
|
|
* 76543210 76543210
|
|
* +--------+ +--------+
|
|
* |ft len | | deref |
|
|
* +--------+ +--------+
|
|
* ||\__6_/ \___8__/
|
|
* || | `----- deref count (decays)
|
|
* || `--------------- length of data
|
|
* |`------------------- tag bit (0 for short)
|
|
* `-------------------- free flag (0 for allocated, 1 for free)
|
|
*
|
|
* Medium:
|
|
* byte 0 byte 1 byte 2
|
|
* 76543210 76543210 76543210
|
|
* +--------+ +--------+ +--------+
|
|
* |ftg lenM| | deref | | lenL |
|
|
* +--------+ +--------+ +--------+
|
|
* |||\_5_/ \___8__/ \___8__/
|
|
* ||| | | `----- length, least significant
|
|
* ||| | `---------------- deref count (decays)
|
|
* ||| `-------------------------- length, most significant
|
|
* ||`----------------------------- tag bit (0 for medium)
|
|
* |`------------------------------ tag bit (1 for medium/long)
|
|
* `------------------------------- free flag
|
|
*
|
|
* Long:
|
|
* byte 0 byte 1 byte 2 byte 3
|
|
* 76543210 76543210 76543210 76543210
|
|
* +--------+ +--------+ +--------+ +--------+
|
|
* |ftg | | deref | | lenM | | lenL |
|
|
* +--------+ +--------+ +--------+ +--------+
|
|
* ||| \___8__/ \_______16________/
|
|
* ||| | `----------- length of data
|
|
* ||| `--------------------------- deref count (decays)
|
|
* ||`---------------------------------------- tag bit (1 for long)
|
|
* |`----------------------------------------- tag bit (1 for medium/long)
|
|
* `------------------------------------------ free flag
|
|
*
|
|
*
|
|
* Note in particular that the dereference count is always in the second
|
|
* byte of a chunk, to simplify the access logic.
|
|
* \endverbatim
|
|
*/
|
|
|
|
/*
|
|
* Fields in chunk headers
|
|
*/
|
|
#define CHUNK_FREE_MASK 0x80
|
|
#define CHUNK_FREE 0x80
|
|
#define CHUNK_USED 0x00
|
|
|
|
#define CHUNK_TAG1_MASK 0x40
|
|
#define CHUNK_TAG1_SHORT 0x00
|
|
#define CHUNK_TAG1_MEDIUM 0x40
|
|
#define CHUNK_TAG1_LONG 0x40
|
|
|
|
#define CHUNK_TAG2_MASK 0x20
|
|
#define CHUNK_TAG2_MEDIUM 0x00
|
|
#define CHUNK_TAG2_LONG 0x20
|
|
|
|
#define CHUNK_SHORT_LEN_MASK 0x3F
|
|
#define CHUNK_SHORT_LEN_OFFSET 0
|
|
|
|
#define CHUNK_MEDIUM_LEN_MSB_MASK 0x1F
|
|
#define CHUNK_MEDIUM_LEN_LSB_MASK 0xFF
|
|
#define CHUNK_MEDIUM_LEN_MSB_OFFSET 0
|
|
#define CHUNK_MEDIUM_LEN_LSB_OFFSET 2
|
|
|
|
#define CHUNK_LONG_LEN_MSB_MASK 0xFF
|
|
#define CHUNK_LONG_LEN_LSB_MASK 0xFF
|
|
#define CHUNK_LONG_LEN_MSB_OFFSET 2
|
|
#define CHUNK_LONG_LEN_LSB_OFFSET 3
|
|
|
|
#define CHUNK_DEREF_OFFSET 1
|
|
|
|
#define CHUNK_DEREF_MAX 0xFF
|
|
|
|
#define CHUNK_SHORT_DATA_OFFSET 2
|
|
#define CHUNK_MEDIUM_DATA_OFFSET 3
|
|
#define CHUNK_LONG_DATA_OFFSET 4
|
|
|
|
#define MIN_CHUNK_LEN 1
|
|
#define MIN_REMNANT_LEN (CHUNK_SHORT_DATA_OFFSET + MIN_CHUNK_LEN)
|
|
|
|
#define MAX_SHORT_CHUNK_LEN CHUNK_SHORT_LEN_MASK
|
|
#define MAX_MEDIUM_CHUNK_LEN \
|
|
((CHUNK_MEDIUM_LEN_MSB_MASK << 8) | CHUNK_MEDIUM_LEN_LSB_MASK)
|
|
#define MAX_LONG_CHUNK_LEN (REGION_CAPACITY - CHUNK_LONG_DATA_OFFSET)
|
|
|
|
static int
|
|
LenToFullLen(int len)
|
|
{
|
|
return (len + ((len > MAX_SHORT_CHUNK_LEN) ? (len > MAX_MEDIUM_CHUNK_LEN)
|
|
? CHUNK_LONG_DATA_OFFSET
|
|
: CHUNK_MEDIUM_DATA_OFFSET
|
|
: CHUNK_SHORT_DATA_OFFSET));
|
|
}
|
|
|
|
static inline char *ChunkPointer(uint16_t, uint16_t);
|
|
|
|
/*
|
|
* Functions for probing and manipulating chunk headers
|
|
*/
|
|
static inline uint16_t
|
|
CPLenShort(const char *cptr)
|
|
{
|
|
return cptr[CHUNK_SHORT_LEN_OFFSET] & CHUNK_SHORT_LEN_MASK;
|
|
}
|
|
|
|
static inline uint16_t
|
|
CPLenMedium(const char *cptr)
|
|
{
|
|
return ((cptr[CHUNK_MEDIUM_LEN_MSB_OFFSET] & CHUNK_MEDIUM_LEN_MSB_MASK)
|
|
<< 8) +
|
|
(cptr[CHUNK_MEDIUM_LEN_LSB_OFFSET] & CHUNK_MEDIUM_LEN_LSB_MASK);
|
|
}
|
|
|
|
static inline uint16_t
|
|
CPLenLong(const char *cptr)
|
|
{
|
|
return ((cptr[CHUNK_LONG_LEN_MSB_OFFSET] & CHUNK_LONG_LEN_MSB_MASK) << 8) +
|
|
(cptr[CHUNK_LONG_LEN_LSB_OFFSET] & CHUNK_LONG_LEN_LSB_MASK);
|
|
}
|
|
|
|
static uint16_t
|
|
CPLen(const char *cptr)
|
|
{
|
|
if (*cptr & CHUNK_TAG1_MASK) {
|
|
if (*cptr & CHUNK_TAG2_MASK)
|
|
return CPLenLong(cptr);
|
|
else
|
|
return CPLenMedium(cptr);
|
|
} else
|
|
return CPLenShort(cptr);
|
|
}
|
|
|
|
static inline uint16_t
|
|
ChunkLen(uint16_t region, uint16_t offset)
|
|
{
|
|
return CPLen(ChunkPointer(region, offset));
|
|
}
|
|
|
|
static inline uint16_t
|
|
CPFullLen(const char *cptr)
|
|
{
|
|
if (*cptr & CHUNK_TAG1_MASK) {
|
|
if (*cptr & CHUNK_TAG2_MASK)
|
|
return CPLenLong(cptr) + CHUNK_LONG_DATA_OFFSET;
|
|
else
|
|
return CPLenMedium(cptr) + CHUNK_MEDIUM_DATA_OFFSET;
|
|
} else
|
|
return CPLenShort(cptr) + CHUNK_SHORT_DATA_OFFSET;
|
|
}
|
|
|
|
static inline uint16_t
|
|
ChunkFullLen(uint16_t region, uint16_t offset)
|
|
{
|
|
return CPFullLen(ChunkPointer(region, offset));
|
|
}
|
|
|
|
static inline bool
|
|
ChunkIsFree(uint16_t region, uint16_t offset)
|
|
{
|
|
return (*ChunkPointer(region, offset) & CHUNK_FREE_MASK) == CHUNK_FREE;
|
|
}
|
|
|
|
static inline bool
|
|
ChunkIsShort(uint16_t region, uint16_t offset)
|
|
{
|
|
return (*ChunkPointer(region, offset) & CHUNK_TAG1_MASK) == CHUNK_TAG1_SHORT;
|
|
}
|
|
|
|
static inline bool
|
|
ChunkIsMedium(uint16_t region, uint16_t offset)
|
|
{
|
|
return (*ChunkPointer(region, offset) &
|
|
(CHUNK_TAG1_MASK | CHUNK_TAG2_MASK)) ==
|
|
(CHUNK_TAG1_MEDIUM | CHUNK_TAG2_MEDIUM);
|
|
}
|
|
|
|
static inline bool
|
|
ChunkIsLong(uint16_t region, uint16_t offset)
|
|
{
|
|
return (*ChunkPointer(region, offset) &
|
|
(CHUNK_TAG1_MASK | CHUNK_TAG2_MASK)) ==
|
|
(CHUNK_TAG1_LONG | CHUNK_TAG2_LONG);
|
|
}
|
|
|
|
static inline uint8_t
|
|
ChunkDerefs(uint16_t region, uint16_t offset)
|
|
{
|
|
return ChunkPointer(region, offset)[CHUNK_DEREF_OFFSET];
|
|
}
|
|
|
|
static void
|
|
SetChunkDerefs(uint16_t region, uint16_t offset, uint8_t derefs)
|
|
{
|
|
ChunkPointer(region, offset)[CHUNK_DEREF_OFFSET] = derefs;
|
|
}
|
|
|
|
static char *
|
|
CPDataPtr(char *cptr)
|
|
{
|
|
if (*cptr & CHUNK_TAG1_MASK) {
|
|
if (*cptr & CHUNK_TAG2_MASK)
|
|
return cptr + CHUNK_LONG_DATA_OFFSET;
|
|
else
|
|
return cptr + CHUNK_MEDIUM_DATA_OFFSET;
|
|
} else
|
|
return cptr + CHUNK_SHORT_DATA_OFFSET;
|
|
}
|
|
|
|
static inline char *
|
|
ChunkDataPtr(uint16_t region, uint16_t offset)
|
|
{
|
|
return CPDataPtr(ChunkPointer(region, offset));
|
|
}
|
|
|
|
static inline uint16_t
|
|
ChunkNextFree(uint16_t region, uint16_t offset)
|
|
{
|
|
return (ChunkDataPtr(region, offset)[0] << 8) + ChunkDerefs(region, offset);
|
|
}
|
|
|
|
/* 0 for no, 1 for yes with room, 2 for exact */
|
|
static int
|
|
FitsInSpace(int size, int capacity)
|
|
{
|
|
if (size == capacity)
|
|
return 2;
|
|
else
|
|
return size <= capacity - MIN_REMNANT_LEN;
|
|
}
|
|
|
|
/** Region info that gets paged out with its region.
|
|
* This is at the start of the region;
|
|
* the rest of the 64K bytes of the region contain chunks.
|
|
*/
|
|
typedef struct region_header {
|
|
uint16_t region_id; /**< will be INVALID_REGION_ID if not in use */
|
|
uint16_t first_free; /**< offset of 1st free chunk */
|
|
struct region_header *prev; /**< linked list prev for LRU cache */
|
|
struct region_header *next; /**< linked list next for LRU cache */
|
|
} RegionHeader;
|
|
|
|
#define FIRST_CHUNK_OFFSET_IN_REGION sizeof(RegionHeader)
|
|
|
|
/** In-memory (never paged) region info. */
|
|
typedef struct region {
|
|
uint16_t used_count; /**< number of used chunks */
|
|
uint16_t free_count; /**< number of free chunks */
|
|
uint16_t free_bytes; /**< number of free bytes (with headers) */
|
|
uint16_t largest_free_chunk; /**< largest single free chunk */
|
|
uint32_t total_derefs; /**< total of all used chunk derefs */
|
|
uint32_t period_last_touched; /**< "this" period, for deref counts;
|
|
we don't page in regions to update
|
|
counts on period change! */
|
|
RegionHeader *in_memory; /**< cache entry; NULL if paged out */
|
|
uint16_t oddballs[NUM_ODDBALLS]; /**< chunk offsets with odd derefs */
|
|
} Region;
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
|
|
/** Swap File */
|
|
#ifdef WIN32
|
|
typedef HANDLE fd_type;
|
|
static HANDLE swap_fd;
|
|
#else
|
|
typedef int fd_type;
|
|
static int swap_fd;
|
|
static int swap_fd_child = -1;
|
|
static char child_filename[300];
|
|
#endif
|
|
|
|
/** Deref scale control.
|
|
* When the deref counts get too big, the current period is incremented
|
|
* and all derefs are divided by 2. */
|
|
static uint32_t curr_period;
|
|
|
|
/*
|
|
* Info about all regions
|
|
*/
|
|
static uint32_t region_count; /**< regions in use */
|
|
static uint32_t region_array_len; /**< length of regions array */
|
|
static Region *regions; /**< regions array, realloced as (rarely) needed */
|
|
|
|
/*
|
|
* regions presently in memory
|
|
*/
|
|
static uint32_t cached_region_count; /**< number of regions in cache */
|
|
static RegionHeader *cache_head; /**< most recently used region */
|
|
static RegionHeader *cache_tail; /**< least recently used region */
|
|
|
|
/*
|
|
* statistics
|
|
*/
|
|
static int stat_used_short_count; /**< How many short chunks? */
|
|
static int stat_used_short_bytes; /**< How much space in short chunks? */
|
|
static int stat_used_medium_count; /**< How many medium chunks? */
|
|
static int stat_used_medium_bytes; /**< How much space in medium chunks? */
|
|
static int stat_used_long_count; /**< How many long chunks? */
|
|
static int stat_used_long_bytes; /**< How much space in long chunks? */
|
|
static int stat_deref_count; /**< Dereferences this period */
|
|
static int stat_deref_maxxed; /**< Number of chunks with max derefs */
|
|
/** histogram for average derefs of regions being paged in/out */
|
|
static int stat_paging_histogram[CHUNK_DEREF_MAX + 1];
|
|
static int stat_page_out; /**< Number of page-outs */
|
|
static int stat_page_in; /**< Number of page-ins */
|
|
static int stat_migrate_slide; /**< Number of slide migrations */
|
|
static int stat_migrate_move; /**< Number of move migrations */
|
|
static int stat_migrate_away; /**< Number of chunk evictions */
|
|
static int stat_create; /**< Number of chunk creations */
|
|
static int stat_delete; /**< Number of chunk deletions */
|
|
|
|
/*
|
|
* migration globals that are used for holding relevant data...
|
|
*/
|
|
static int m_count; /**< The used length for the arrays. */
|
|
static chunk_reference_t **m_references; /**< The passed-in references array. */
|
|
|
|
#ifdef CHUNK_PARANOID
|
|
/** Log of recent actions for debug purposes */
|
|
static char rolling_log[ROLLING_LOG_SIZE][ROLLING_LOG_ENTRY_LEN];
|
|
static int rolling_pos;
|
|
static int noisy_log = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Forward decls
|
|
*/
|
|
static void find_oddballs(uint16_t region);
|
|
|
|
/*
|
|
* Lookup functions
|
|
*/
|
|
|
|
static inline char *
|
|
ChunkPointer(uint16_t region, uint16_t offset)
|
|
{
|
|
return ((char *) (regions[region].in_memory)) + offset;
|
|
}
|
|
|
|
static uint8_t
|
|
RegionDerefs(uint16_t region)
|
|
{
|
|
if (regions[region].used_count)
|
|
return (regions[region].total_derefs >>
|
|
(curr_period - regions[region].period_last_touched)) /
|
|
regions[region].used_count;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t
|
|
RegionDerefsWithChunk(uint16_t region, uint16_t derefs)
|
|
{
|
|
return ((regions[region].total_derefs >>
|
|
(curr_period - regions[region].period_last_touched)) +
|
|
derefs) /
|
|
(regions[region].used_count + 1);
|
|
}
|
|
|
|
/*
|
|
* Debug routines
|
|
*/
|
|
/** Add a message to the rolling log. */
|
|
static void
|
|
debug_log(char const *format, ...)
|
|
{
|
|
#ifdef CHUNK_PARANOID
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
mush_vsnprintf(rolling_log[rolling_pos], ROLLING_LOG_ENTRY_LEN, format, args);
|
|
va_end(args);
|
|
|
|
rolling_log[rolling_pos][ROLLING_LOG_ENTRY_LEN - 1] = '\0';
|
|
if (noisy_log)
|
|
do_rawlog(LT_TRACE, "%s\n", rolling_log[rolling_pos]);
|
|
rolling_pos = (rolling_pos + 1) % ROLLING_LOG_SIZE;
|
|
#else
|
|
if (format)
|
|
return; /* shut up the compiler warning */
|
|
#endif
|
|
}
|
|
|
|
#ifdef CHUNK_PARANOID
|
|
/** Dump the rolling log. */
|
|
static void
|
|
dump_debug_log(FILE *fp)
|
|
{
|
|
int j;
|
|
fputs("Recent chunk activity:\n", fp);
|
|
j = rolling_pos;
|
|
do {
|
|
if (rolling_log[j][0]) {
|
|
fputs(rolling_log[j], fp);
|
|
fputc('\n', fp);
|
|
rolling_log[j][0] = '\0';
|
|
}
|
|
j = (j + 1) % ROLLING_LOG_SIZE;
|
|
} while (j != rolling_pos);
|
|
fputs("End of recent chunk activity.\n", fp);
|
|
fflush(fp);
|
|
}
|
|
|
|
/** Test if a chunk is migratable. */
|
|
static int
|
|
migratable(uint16_t region, uint16_t offset)
|
|
{
|
|
chunk_reference_t ref = ChunkReference(region, offset);
|
|
int j;
|
|
|
|
for (j = 0; j < m_count; j++)
|
|
if (m_references[j][0] == ref)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/** Give a detailed map of a region.
|
|
* Lists pertinent region information, and all the chunks in the region.
|
|
* Does not print the contents of the chunks (which would probably be
|
|
* unreadable, anyway).
|
|
* \param region the region to display.
|
|
* \param fp the FILE* to output to.
|
|
*/
|
|
static void
|
|
debug_dump_region(uint16_t region, FILE *fp)
|
|
{
|
|
Region *rp = regions + region;
|
|
RegionHeader *rhp;
|
|
uint16_t offset, count;
|
|
|
|
ASSERT(region < region_count);
|
|
rhp = rp->in_memory;
|
|
|
|
fprintf(fp, "region: id:%04x period:%-8x deref:%-8x (%-2x per chunk)\n",
|
|
region, (unsigned int) rp->period_last_touched,
|
|
(unsigned int) rp->total_derefs, RegionDerefs(region));
|
|
fprintf(fp, " #used:%-4x #free:%-4x fbytes:%-4x hole:%-4x ",
|
|
rp->used_count, rp->free_count, rp->free_bytes,
|
|
rp->largest_free_chunk);
|
|
if (rhp)
|
|
fprintf(fp, "first:%-4x h_id:%-4x\n", rhp->first_free, rhp->region_id);
|
|
else
|
|
fprintf(fp, "PAGED\n");
|
|
fflush(fp);
|
|
|
|
if (rhp) {
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += ChunkFullLen(region, offset)) {
|
|
fprintf(fp, "chunk:%c%4s %-6s off:%04x full:%04x ",
|
|
migratable(region, offset) ? '*' : ' ',
|
|
ChunkIsFree(region, offset) ? "FREE" : "",
|
|
ChunkIsShort(region, offset)
|
|
? "SHORT"
|
|
: (ChunkIsMedium(region, offset) ? "MEDIUM" : "LONG"),
|
|
offset, ChunkFullLen(region, offset));
|
|
if (ChunkIsFree(region, offset)) {
|
|
fprintf(fp, "next:%04x\n", ChunkNextFree(region, offset));
|
|
} else {
|
|
fprintf(fp, "doff:%04" PRIdS "x len:%04x ",
|
|
ChunkDataPtr(region, offset) - (char *) rhp,
|
|
ChunkLen(region, offset));
|
|
count = ChunkDerefs(region, offset);
|
|
if (count == 0xFF) {
|
|
fprintf(fp, "deref:many\n");
|
|
} else {
|
|
fprintf(fp, "deref:%04x\n", count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Make sure a chunk is real.
|
|
* Detect bogus chunk references handed to the system.
|
|
* \param region the region to verify.
|
|
* \param offset the offset to verify.
|
|
*/
|
|
static void
|
|
verify_used_chunk(uint16_t region, uint16_t offset)
|
|
{
|
|
uint16_t pos;
|
|
|
|
ASSERT(region < region_count);
|
|
|
|
for (pos = FIRST_CHUNK_OFFSET_IN_REGION; pos < REGION_SIZE;
|
|
pos += ChunkFullLen(region, pos)) {
|
|
if (pos == offset) {
|
|
if (ChunkIsFree(region, pos))
|
|
mush_panic("Invalid reference to free chunk as used");
|
|
return;
|
|
}
|
|
}
|
|
mush_panic("Invalid reference to non-chunk as used");
|
|
}
|
|
|
|
/** Verify that a region is sane.
|
|
* Do a thorough consistency check on a region, verifying all the region
|
|
* totals, making sure the counts are consistent, and that all the space
|
|
* in the region is accounted for.
|
|
* \param region the region to verify.
|
|
* \return true if the region is valid.
|
|
*/
|
|
static int
|
|
region_is_valid(uint16_t region)
|
|
{
|
|
int result;
|
|
Region *rp;
|
|
RegionHeader *rhp;
|
|
int used_count;
|
|
int free_count;
|
|
int free_bytes;
|
|
int largest_free;
|
|
unsigned int total_derefs;
|
|
int len;
|
|
int was_free;
|
|
int dump;
|
|
uint16_t next_free;
|
|
uint16_t offset;
|
|
|
|
if (region >= region_count) {
|
|
do_rawlog(LT_ERR, "region 0x%04x is not valid: region_count is 0x%04x",
|
|
region, (unsigned int) region_count);
|
|
return 0;
|
|
}
|
|
result = 1;
|
|
|
|
rp = regions + region;
|
|
if (rp->used_count > REGION_SIZE / MIN_REMNANT_LEN) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: chunk count is ludicrous: 0x%04x",
|
|
region, rp->used_count);
|
|
result = 0;
|
|
}
|
|
if (rp->free_count > REGION_SIZE / MIN_REMNANT_LEN) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: free count is ludicrous: 0x%04x",
|
|
region, rp->free_count);
|
|
result = 0;
|
|
}
|
|
if (rp->largest_free_chunk > rp->free_bytes) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: largest free chunk > free bytes:"
|
|
" 0x%04x > 0x%04x",
|
|
region, rp->largest_free_chunk, rp->free_bytes);
|
|
result = 0;
|
|
}
|
|
if (!rp->in_memory)
|
|
return result;
|
|
|
|
rhp = rp->in_memory;
|
|
|
|
if (rhp->region_id != region) {
|
|
do_rawlog(LT_ERR, "region 0x%04x is not valid: region in cache is 0x%04x",
|
|
region, rhp->region_id);
|
|
result = 0;
|
|
}
|
|
dump = 0;
|
|
used_count = 0;
|
|
total_derefs = 0;
|
|
free_count = 0;
|
|
free_bytes = 0;
|
|
largest_free = 0;
|
|
was_free = 0;
|
|
next_free = rhp->first_free;
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += len) {
|
|
if (was_free && ChunkIsFree(region, offset)) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: uncoalesced free chunk:"
|
|
" 0x%04x (see map)",
|
|
region, offset);
|
|
result = 0;
|
|
dump = 1;
|
|
}
|
|
len = ChunkFullLen(region, offset);
|
|
was_free = ChunkIsFree(region, offset);
|
|
if (was_free) {
|
|
free_count++;
|
|
free_bytes += len;
|
|
if (largest_free < len)
|
|
largest_free = len;
|
|
if (next_free != offset) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: free chain broken:"
|
|
" 0x%04x, expecting 0x%04x (see map)",
|
|
region, offset, next_free);
|
|
result = 0;
|
|
dump = 1;
|
|
}
|
|
next_free = ChunkNextFree(region, offset);
|
|
} else {
|
|
used_count++;
|
|
total_derefs += ChunkDerefs(region, offset);
|
|
if (ChunkIsMedium(region, offset) &&
|
|
ChunkLen(region, offset) <= MAX_SHORT_CHUNK_LEN) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: medium chunk too small:"
|
|
" 0x%04x (see map)",
|
|
region, offset);
|
|
result = 0;
|
|
dump = 1;
|
|
}
|
|
if (ChunkIsLong(region, offset) &&
|
|
ChunkLen(region, offset) <= MAX_MEDIUM_CHUNK_LEN) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: long chunk too small:"
|
|
" 0x%04x (see map)",
|
|
region, offset);
|
|
result = 0;
|
|
dump = 1;
|
|
}
|
|
}
|
|
}
|
|
if (offset != REGION_SIZE) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: last chunk past bounds (see map)",
|
|
region);
|
|
result = 0;
|
|
}
|
|
if (next_free != 0) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: free chain unterminated:"
|
|
" expecting 0x%04x (see map)",
|
|
region, next_free);
|
|
result = 0;
|
|
dump = 1;
|
|
}
|
|
if (rp->used_count != used_count) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: used count is wrong:"
|
|
" 0x%04x should be 0x%04x",
|
|
region, rp->used_count, used_count);
|
|
result = 0;
|
|
}
|
|
if (rp->total_derefs != total_derefs) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: total derefs is wrong:"
|
|
" 0x%04x should be 0x%04x",
|
|
region, (unsigned int) rp->total_derefs, total_derefs);
|
|
result = 0;
|
|
}
|
|
if (rp->free_count != free_count) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: free count is wrong:"
|
|
" 0x%04x should be 0x%04x",
|
|
region, rp->free_count, free_count);
|
|
result = 0;
|
|
}
|
|
if (rp->free_bytes != free_bytes) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: free bytes is wrong:"
|
|
" 0x%04x should be 0x%04x",
|
|
region, rp->free_bytes, free_bytes);
|
|
result = 0;
|
|
}
|
|
if (rp->largest_free_chunk != largest_free) {
|
|
do_rawlog(LT_ERR,
|
|
"region 0x%04x is not valid: largest free is wrong:"
|
|
" 0x%04x should be 0x%04x",
|
|
region, rp->largest_free_chunk, largest_free);
|
|
result = 0;
|
|
}
|
|
if (dump) {
|
|
debug_dump_region(region, lookup_log(LT_TRACE)->fp);
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Utility Routines - Chunks
|
|
*/
|
|
/** Write a used chunk.
|
|
* \param region the region to put the chunk in.
|
|
* \param offset the offset to put the chunk at.
|
|
* \param full_len the length for the chunk, including headers.
|
|
* \param data the externally supplied data for the chunk.
|
|
* \param data_len the length of the externally supplied data.
|
|
* \param derefs the deref count to set on the chunk.
|
|
*/
|
|
static void
|
|
write_used_chunk(uint16_t region, uint16_t offset, uint16_t full_len,
|
|
char const *data, uint16_t data_len, uint8_t derefs)
|
|
{
|
|
char *cptr = ChunkPointer(region, offset);
|
|
if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
|
|
/* chunk is short */
|
|
cptr[0] =
|
|
full_len - CHUNK_SHORT_DATA_OFFSET + CHUNK_USED + CHUNK_TAG1_SHORT;
|
|
cptr[CHUNK_DEREF_OFFSET] = derefs;
|
|
memcpy(cptr + CHUNK_SHORT_DATA_OFFSET, data, data_len);
|
|
} else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
|
|
/* chunk is medium */
|
|
uint16_t len = full_len - CHUNK_MEDIUM_DATA_OFFSET;
|
|
cptr[0] = (len >> 8) + CHUNK_USED + CHUNK_TAG1_MEDIUM + CHUNK_TAG2_MEDIUM;
|
|
cptr[CHUNK_DEREF_OFFSET] = derefs;
|
|
cptr[CHUNK_MEDIUM_LEN_LSB_OFFSET] = len & 0xff;
|
|
memcpy(cptr + CHUNK_MEDIUM_DATA_OFFSET, data, data_len);
|
|
} else {
|
|
/* chunk is long */
|
|
uint16_t len = full_len - CHUNK_LONG_DATA_OFFSET;
|
|
cptr[0] = CHUNK_USED + CHUNK_TAG1_LONG + CHUNK_TAG2_LONG;
|
|
cptr[CHUNK_DEREF_OFFSET] = derefs;
|
|
cptr[CHUNK_LONG_LEN_MSB_OFFSET] = len >> 8;
|
|
cptr[CHUNK_LONG_LEN_LSB_OFFSET] = len & 0xff;
|
|
memcpy(cptr + CHUNK_LONG_DATA_OFFSET, data, data_len);
|
|
}
|
|
}
|
|
|
|
/** Write a free chunk.
|
|
* \param region the region to put the chunk in.
|
|
* \param offset the offset to put the chunk at.
|
|
* \param full_len the length for the chunk, including headers.
|
|
* \param next the offset for the next free chunk.
|
|
*/
|
|
static void
|
|
write_free_chunk(uint16_t region, uint16_t offset, uint16_t full_len,
|
|
uint16_t next)
|
|
{
|
|
char *cptr = ChunkPointer(region, offset);
|
|
if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
|
|
/* chunk is short */
|
|
cptr[0] =
|
|
full_len - CHUNK_SHORT_DATA_OFFSET + CHUNK_FREE + CHUNK_TAG1_SHORT;
|
|
cptr[CHUNK_SHORT_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
} else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
|
|
/* chunk is medium */
|
|
uint16_t len = full_len - CHUNK_MEDIUM_DATA_OFFSET;
|
|
cptr[0] = (len >> 8) + CHUNK_FREE + CHUNK_TAG1_MEDIUM + CHUNK_TAG2_MEDIUM;
|
|
cptr[CHUNK_MEDIUM_LEN_LSB_OFFSET] = len & 0xff;
|
|
cptr[CHUNK_MEDIUM_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
} else {
|
|
/* chunk is long */
|
|
uint16_t len = full_len - CHUNK_LONG_DATA_OFFSET;
|
|
cptr[0] = CHUNK_FREE + CHUNK_TAG1_LONG + CHUNK_TAG2_LONG;
|
|
cptr[CHUNK_LONG_LEN_MSB_OFFSET] = len >> 8;
|
|
cptr[CHUNK_LONG_LEN_LSB_OFFSET] = len & 0xff;
|
|
cptr[CHUNK_LONG_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
}
|
|
}
|
|
|
|
/** Write the next pointer for a free chunk.
|
|
* \param region the region of the chunk to write in.
|
|
* \param offset the offset of the chunk to write in.
|
|
* \param next the offset for the next free chunk.
|
|
*/
|
|
static void
|
|
write_next_free(uint16_t region, uint16_t offset, uint16_t next)
|
|
{
|
|
char *cptr = ChunkPointer(region, offset);
|
|
if (ChunkIsShort(region, offset)) {
|
|
/* chunk is short */
|
|
cptr[CHUNK_SHORT_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
} else if (ChunkIsMedium(region, offset)) {
|
|
/* chunk is medium */
|
|
cptr[CHUNK_MEDIUM_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
} else {
|
|
/* chunk is long */
|
|
cptr[CHUNK_LONG_DATA_OFFSET] = next >> 8;
|
|
cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
|
|
}
|
|
}
|
|
|
|
/** Combine neighboring free chunks, if possible.
|
|
* The left-hand candidate chunk is passed in.
|
|
* \param region the region of the chunks to coalesce.
|
|
* \param offset the offset of the left-hand chunk to coalesce.
|
|
*/
|
|
static void
|
|
coalesce_frees(uint16_t region, uint16_t offset)
|
|
{
|
|
Region *rp = regions + region;
|
|
uint16_t full_len, next;
|
|
full_len = ChunkFullLen(region, offset);
|
|
next = ChunkNextFree(region, offset);
|
|
if (offset + full_len == next) {
|
|
full_len += ChunkFullLen(region, next);
|
|
next = ChunkNextFree(region, next);
|
|
write_free_chunk(region, offset, full_len, next);
|
|
rp->free_count--;
|
|
if (rp->largest_free_chunk < full_len)
|
|
rp->largest_free_chunk = full_len;
|
|
}
|
|
}
|
|
|
|
/** Free a used chunk.
|
|
* \param region the region of the chunk to free.
|
|
* \param offset the offset of the chunk to free.
|
|
*/
|
|
static void
|
|
free_chunk(uint16_t region, uint16_t offset)
|
|
{
|
|
Region *rp = regions + region;
|
|
uint16_t full_len, left;
|
|
|
|
full_len = ChunkFullLen(region, offset);
|
|
rp->total_derefs -= ChunkDerefs(region, offset);
|
|
rp->used_count--;
|
|
rp->free_count++;
|
|
rp->free_bytes += full_len;
|
|
if (rp->largest_free_chunk < full_len)
|
|
rp->largest_free_chunk = full_len;
|
|
|
|
if (ChunkIsShort(region, offset)) {
|
|
/* chunk is short */
|
|
stat_used_short_count--;
|
|
stat_used_short_bytes -= full_len;
|
|
} else if (ChunkIsMedium(region, offset)) {
|
|
/* chunk is medium */
|
|
stat_used_medium_count--;
|
|
stat_used_medium_bytes -= full_len;
|
|
} else {
|
|
/* chunk is long */
|
|
stat_used_long_count--;
|
|
stat_used_long_bytes -= full_len;
|
|
}
|
|
|
|
left = rp->in_memory->first_free;
|
|
if (!left) {
|
|
write_free_chunk(region, offset, full_len, 0);
|
|
rp->largest_free_chunk = full_len;
|
|
rp->in_memory->first_free = offset;
|
|
} else if (left > offset) {
|
|
write_free_chunk(region, offset, full_len, left);
|
|
rp->in_memory->first_free = offset;
|
|
left = 0;
|
|
} else {
|
|
uint16_t next;
|
|
next = ChunkNextFree(region, left);
|
|
while (next && next < offset) {
|
|
left = next;
|
|
next = ChunkNextFree(region, left);
|
|
}
|
|
write_free_chunk(region, offset, full_len, next);
|
|
write_next_free(region, left, offset);
|
|
}
|
|
coalesce_frees(region, offset);
|
|
if (left)
|
|
coalesce_frees(region, left);
|
|
}
|
|
|
|
/** Find the largest free chunk in a region.
|
|
* \param region the region to search for a large hole in.
|
|
* \return the size of the largest free chunk.
|
|
*/
|
|
static uint16_t
|
|
largest_hole(uint16_t region)
|
|
{
|
|
uint16_t size;
|
|
uint16_t offset;
|
|
size = 0;
|
|
for (offset = regions[region].in_memory->first_free; offset;
|
|
offset = ChunkNextFree(region, offset))
|
|
if (size < ChunkFullLen(region, offset))
|
|
size = ChunkFullLen(region, offset);
|
|
return size;
|
|
}
|
|
|
|
/** Allocate a used chunk out of a free hole.
|
|
* This possibly splits the hole into two chunks, and it maintains the
|
|
* free list. It does NOT write the used chunk; that must be done by
|
|
* caller.
|
|
* \param region the region to allocate in.
|
|
* \param offset the offset of the hole to use.
|
|
* \param full_len the length (including headers) of the space to allocate.
|
|
* \param align the alignment to use: 0 = easiest, 1 = left, 2 = right.
|
|
* \return the offset of the allocated space.
|
|
*/
|
|
static uint16_t
|
|
split_hole(uint16_t region, uint16_t offset, uint16_t full_len, int align)
|
|
{
|
|
Region *rp = regions + region;
|
|
uint16_t hole_len = ChunkFullLen(region, offset);
|
|
|
|
rp->used_count++;
|
|
if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
|
|
/* chunk is short */
|
|
stat_used_short_count++;
|
|
stat_used_short_bytes += full_len;
|
|
} else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
|
|
/* chunk is medium */
|
|
stat_used_medium_count++;
|
|
stat_used_medium_bytes += full_len;
|
|
} else {
|
|
/* chunk is long */
|
|
stat_used_long_count++;
|
|
stat_used_long_bytes += full_len;
|
|
}
|
|
|
|
if (hole_len == full_len) {
|
|
rp->free_count--;
|
|
rp->free_bytes -= full_len;
|
|
if (rp->in_memory->first_free == offset)
|
|
rp->in_memory->first_free = ChunkNextFree(region, offset);
|
|
else {
|
|
uint16_t hole;
|
|
for (hole = rp->in_memory->first_free; hole;
|
|
hole = ChunkNextFree(region, hole))
|
|
if (ChunkNextFree(region, hole) == offset)
|
|
break;
|
|
ASSERT(hole);
|
|
write_next_free(region, hole, ChunkNextFree(region, offset));
|
|
}
|
|
if (rp->largest_free_chunk == hole_len)
|
|
rp->largest_free_chunk = largest_hole(region);
|
|
return offset;
|
|
}
|
|
|
|
ASSERT(hole_len >= full_len + MIN_REMNANT_LEN);
|
|
if (!align) {
|
|
if (rp->in_memory->first_free == offset)
|
|
align = 1;
|
|
else
|
|
align = 2;
|
|
}
|
|
if (align == 1) {
|
|
rp->free_bytes -= full_len;
|
|
write_free_chunk(region, offset + full_len, hole_len - full_len,
|
|
ChunkNextFree(region, offset));
|
|
if (rp->in_memory->first_free == offset)
|
|
rp->in_memory->first_free += full_len;
|
|
else {
|
|
uint16_t hole;
|
|
for (hole = rp->in_memory->first_free; hole;
|
|
hole = ChunkNextFree(region, hole))
|
|
if (ChunkNextFree(region, hole) == offset)
|
|
break;
|
|
ASSERT(hole);
|
|
write_next_free(region, hole, offset + full_len);
|
|
}
|
|
if (rp->largest_free_chunk == hole_len)
|
|
rp->largest_free_chunk = largest_hole(region);
|
|
return offset;
|
|
} else {
|
|
rp->free_bytes -= full_len;
|
|
write_free_chunk(region, offset, hole_len - full_len,
|
|
ChunkNextFree(region, offset));
|
|
if (rp->largest_free_chunk == hole_len)
|
|
rp->largest_free_chunk = largest_hole(region);
|
|
return offset + hole_len - full_len;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Utility Routines - Cache
|
|
*/
|
|
|
|
/** Read a region from a file.
|
|
* \param fd file to read from
|
|
* \param rhp region buffer to use
|
|
* \param region region to read
|
|
*/
|
|
static void
|
|
read_cache_region(fd_type fd, RegionHeader *rhp, uint16_t region)
|
|
{
|
|
off_t file_offset = region * REGION_SIZE;
|
|
int j;
|
|
char *pos;
|
|
size_t remaining;
|
|
ssize_t done = 0;
|
|
|
|
debug_log("read_cache_region %04x", region);
|
|
|
|
#ifndef HAVE_PREAD
|
|
/* Try to seek up to 3 times... */
|
|
for (j = 0; j < 3; j++)
|
|
#ifdef WIN32
|
|
if (SetFilePointer(fd, file_offset, NULL, FILE_BEGIN) !=
|
|
INVALID_SET_FILE_POINTER)
|
|
break;
|
|
#else
|
|
if (lseek(fd, file_offset, SEEK_SET) == file_offset)
|
|
break;
|
|
#endif
|
|
if (j >= 3)
|
|
#ifdef WIN32
|
|
mush_panicf("chunk swap file seek, GetLastError %lu", GetLastError());
|
|
#else
|
|
mush_panicf("chunk swap file seek, errno %d: %s", errno, strerror(errno));
|
|
#endif
|
|
#endif /* !HAVE_PREAD */
|
|
pos = (char *) rhp;
|
|
remaining = REGION_SIZE;
|
|
for (j = 0; j < 10; j++) {
|
|
#if defined(HAVE_PREAD)
|
|
done = pread(fd, pos, remaining, file_offset);
|
|
#elif defined(WIN32)
|
|
DWORD rfbytes;
|
|
if (ReadFile(fd, pos, remaining, &rfbytes, NULL))
|
|
done = (ssize_t) rfbytes;
|
|
else
|
|
done = -1;
|
|
#else
|
|
done = read(fd, pos, remaining);
|
|
#endif
|
|
if (done >= 0) {
|
|
remaining -= done;
|
|
pos += done;
|
|
file_offset += done;
|
|
if (!remaining)
|
|
return;
|
|
}
|
|
}
|
|
#ifdef WIN32
|
|
mush_panicf("chunk swap file read, %lu remaining, GetLastError %lu",
|
|
(unsigned long) remaining, GetLastError());
|
|
#else
|
|
mush_panicf("chunk swap file read, %lu remaining, errno %d: %s",
|
|
(unsigned long) remaining, errno, strerror(errno));
|
|
#endif
|
|
}
|
|
|
|
/** Write a region to a file.
|
|
* \param fd file to write to
|
|
* \param rhp region buffer to use
|
|
* \param region region to write
|
|
*/
|
|
static void
|
|
write_cache_region(fd_type fd, RegionHeader *rhp, uint16_t region)
|
|
{
|
|
off_t file_offset = region * REGION_SIZE;
|
|
int j;
|
|
char *pos;
|
|
size_t remaining;
|
|
ssize_t done;
|
|
|
|
debug_log("write_cache_region %04x", region);
|
|
|
|
#ifndef HAVE_PWRITE
|
|
/* Try to seek up to 3 times... */
|
|
for (j = 0; j < 3; j++)
|
|
#ifdef WIN32
|
|
if (SetFilePointer(fd, file_offset, NULL, FILE_BEGIN) !=
|
|
INVALID_SET_FILE_POINTER)
|
|
break;
|
|
#else
|
|
if (lseek(fd, file_offset, SEEK_SET) == file_offset)
|
|
break;
|
|
#endif
|
|
if (j >= 3)
|
|
#ifdef WIN32
|
|
mush_panicf("chunk swap file seek, GetLastError %lu", GetLastError());
|
|
#else
|
|
mush_panicf("chunk swap file seek, errno %d: %s", errno, strerror(errno));
|
|
#endif
|
|
#endif /* !HAVE_PWRITE */
|
|
pos = (char *) rhp;
|
|
remaining = REGION_SIZE;
|
|
|
|
/* SW: I'm not sure why Talek has this loop so many times -- the
|
|
only recoverable way the writes should fail is if they're
|
|
interrupted by a signal and can't be restarted for some
|
|
reason. */
|
|
for (j = 0; j < 10; j++) {
|
|
|
|
#if defined(HAVE_PWRITE)
|
|
done = pwrite(fd, pos, remaining, file_offset);
|
|
#elif defined(WIN32)
|
|
DWORD wfbytes;
|
|
if (WriteFile(fd, pos, remaining, &wfbytes, NULL))
|
|
done = (ssize_t) wfbytes;
|
|
else
|
|
done = -1;
|
|
#else
|
|
done = write(fd, pos, remaining);
|
|
#endif
|
|
if (done >= 0) {
|
|
remaining -= done;
|
|
pos += done;
|
|
file_offset += done;
|
|
if (!remaining)
|
|
return;
|
|
}
|
|
}
|
|
#ifdef WIN32
|
|
mush_panicf("chunk swap file write, %lu remaining, GetLastError %lu",
|
|
(unsigned long) remaining, GetLastError());
|
|
#else
|
|
mush_panicf("chunk swap file write, %lu remaining, errno %d: %s",
|
|
(unsigned long) remaining, errno, strerror(errno));
|
|
#endif
|
|
}
|
|
|
|
/** Update cache position to stave off recycling.
|
|
* \param rhp the cached region to keep around.
|
|
*/
|
|
static void
|
|
touch_cache_region(RegionHeader *rhp)
|
|
{
|
|
debug_log("touch_cache_region %04x", rhp->region_id);
|
|
|
|
if (cache_head == rhp)
|
|
return;
|
|
if (cache_tail == rhp)
|
|
cache_tail = rhp->prev;
|
|
if (rhp->prev)
|
|
rhp->prev->next = rhp->next;
|
|
if (rhp->next)
|
|
rhp->next->prev = rhp->prev;
|
|
|
|
if (cache_head)
|
|
cache_head->prev = rhp;
|
|
rhp->next = cache_head;
|
|
rhp->prev = NULL;
|
|
cache_head = rhp;
|
|
if (!cache_tail)
|
|
cache_tail = rhp;
|
|
}
|
|
|
|
/** Find space in the cache.
|
|
* This is likely to require paging out something.
|
|
* \return a pointer to an available cache region.
|
|
*/
|
|
static RegionHeader *
|
|
find_available_cache_region(void)
|
|
{
|
|
RegionHeader *rhp;
|
|
|
|
debug_log("find_available_cache_region");
|
|
|
|
if (!cache_tail ||
|
|
cached_region_count * REGION_SIZE < (unsigned) CHUNK_CACHE_MEMORY) {
|
|
/* first use ... normal case if empty ... so allocate space */
|
|
#ifdef DEBUG_CHUNK_MALLOC
|
|
do_rawlog(LT_TRACE, "CHUNK: malloc()ing a cache region");
|
|
#endif
|
|
rhp = mush_malloc(REGION_SIZE, "chunk region cache buffer");
|
|
if (!rhp) {
|
|
mush_panic("chunk region cache buffer allocation failure");
|
|
}
|
|
cached_region_count++;
|
|
rhp->region_id = INVALID_REGION_ID;
|
|
rhp->prev = NULL;
|
|
rhp->next = NULL;
|
|
return rhp;
|
|
}
|
|
if (cache_tail->region_id == INVALID_REGION_ID)
|
|
return cache_tail;
|
|
|
|
rhp = cache_tail;
|
|
/* page the current occupant out */
|
|
find_oddballs(rhp->region_id);
|
|
#ifdef DEBUG_CHUNK_PAGING
|
|
do_rawlog(LT_TRACE, "CHUNK: Paging out region %04x (offset %08x)",
|
|
rhp->region_id, (unsigned) file_offset);
|
|
#endif
|
|
write_cache_region(swap_fd, rhp, rhp->region_id);
|
|
/* keep statistics */
|
|
stat_paging_histogram[RegionDerefs(rhp->region_id)]++;
|
|
stat_page_out++;
|
|
|
|
/* mark the paged out region as not in memory */
|
|
regions[rhp->region_id].in_memory = NULL;
|
|
/* mark it not in use for sanity check reasons */
|
|
rhp->region_id = INVALID_REGION_ID;
|
|
|
|
return rhp;
|
|
}
|
|
|
|
/** Bring a paged out region back into memory.
|
|
* If neccessary, make room by paging another region out.
|
|
* \param region the region to bring in.
|
|
*/
|
|
static void
|
|
bring_in_region(uint16_t region)
|
|
{
|
|
Region *rp = regions + region;
|
|
RegionHeader *rhp, *prev, *next;
|
|
uint32_t offset;
|
|
unsigned int shift;
|
|
|
|
debug_log("bring_in_region %04x", region);
|
|
|
|
ASSERT(region < region_count);
|
|
if (rp->in_memory)
|
|
return;
|
|
rhp = find_available_cache_region();
|
|
ASSERT(rhp->region_id == INVALID_REGION_ID);
|
|
|
|
/* This is cheesy, but I _really_ don't want to do dual data structures */
|
|
prev = rhp->prev;
|
|
next = rhp->next;
|
|
|
|
/* page it in */
|
|
#ifdef DEBUG_CHUNK_PAGING
|
|
do_rawlog(LT_TRACE, "CHUNK: Paging in region %04x (offset %08x)", region,
|
|
(unsigned) file_offset);
|
|
#endif
|
|
read_cache_region(swap_fd, rhp, region);
|
|
/* link the region to its cache entry */
|
|
rp->in_memory = rhp;
|
|
|
|
/* touch the cache entry */
|
|
rhp->prev = prev;
|
|
rhp->next = next;
|
|
touch_cache_region(rhp);
|
|
|
|
/* make derefs current */
|
|
if (rp->period_last_touched != curr_period) {
|
|
shift = curr_period - rp->period_last_touched;
|
|
if (shift > 8) {
|
|
rp->total_derefs = 0;
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += ChunkFullLen(region, offset)) {
|
|
SetChunkDerefs(region, offset, 0);
|
|
}
|
|
} else {
|
|
rp->total_derefs = 0;
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += ChunkFullLen(region, offset)) {
|
|
if (ChunkIsFree(region, offset))
|
|
continue;
|
|
SetChunkDerefs(region, offset, ChunkDerefs(region, offset) >> shift);
|
|
rp->total_derefs += ChunkDerefs(region, offset);
|
|
}
|
|
}
|
|
rp->period_last_touched = curr_period;
|
|
}
|
|
|
|
/* keep statistics */
|
|
stat_page_in++;
|
|
stat_paging_histogram[RegionDerefs(region)]++;
|
|
}
|
|
|
|
/*
|
|
* Utility Routines - Regions
|
|
*/
|
|
/** Create a new region.
|
|
* Recycle an empty region if possible.
|
|
* \return the region id for the new region.
|
|
*/
|
|
static uint16_t
|
|
create_region(void)
|
|
{
|
|
uint16_t region;
|
|
|
|
for (region = 0; region < region_count; region++)
|
|
if (regions[region].used_count == 0)
|
|
break;
|
|
if (region >= region_count) {
|
|
if (region_count >= region_array_len) {
|
|
/* need to grow the regions array */
|
|
region_array_len += FIXME_REGION_ARRAY_INCREMENT;
|
|
#ifdef DEBUG_CHUNK_MALLOC
|
|
do_rawlog(LT_TRACE, "CHUNK: realloc()ing region array");
|
|
#endif
|
|
regions = (Region *) realloc(regions, region_array_len * sizeof(Region));
|
|
if (!regions)
|
|
mush_panic("chunk: region array realloc failure");
|
|
}
|
|
region = region_count;
|
|
region_count++;
|
|
regions[region].in_memory = NULL;
|
|
}
|
|
|
|
regions[region].used_count = 0;
|
|
regions[region].free_count = 1;
|
|
regions[region].free_bytes = REGION_CAPACITY;
|
|
regions[region].largest_free_chunk = regions[region].free_bytes;
|
|
regions[region].total_derefs = 0;
|
|
regions[region].period_last_touched = curr_period;
|
|
if (!regions[region].in_memory)
|
|
regions[region].in_memory = find_available_cache_region();
|
|
regions[region].in_memory->region_id = region;
|
|
regions[region].in_memory->first_free = FIRST_CHUNK_OFFSET_IN_REGION;
|
|
write_free_chunk(region, FIRST_CHUNK_OFFSET_IN_REGION,
|
|
regions[region].free_bytes, 0);
|
|
|
|
touch_cache_region(regions[region].in_memory);
|
|
return region;
|
|
}
|
|
|
|
/** Find the oddball chunks in a region.
|
|
* \param region the region to search in.
|
|
*/
|
|
static void
|
|
find_oddballs(uint16_t region)
|
|
{
|
|
Region *rp = regions + region;
|
|
int j, d1, d2;
|
|
uint16_t offset, len;
|
|
int mean;
|
|
|
|
for (j = 0; j < NUM_ODDBALLS; j++)
|
|
rp->oddballs[j] = 0;
|
|
|
|
mean = RegionDerefs(region);
|
|
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += len) {
|
|
len = ChunkFullLen(region, offset);
|
|
if (ChunkIsFree(region, offset))
|
|
continue;
|
|
d1 = abs(mean - ChunkDerefs(region, offset));
|
|
if (d1 < ODDBALL_THRESHOLD)
|
|
continue;
|
|
j = NUM_ODDBALLS;
|
|
while (j--) {
|
|
if (!rp->oddballs[j])
|
|
continue;
|
|
d2 = abs(mean - ChunkDerefs(region, rp->oddballs[j]));
|
|
if (d1 < d2)
|
|
break;
|
|
if (j < NUM_ODDBALLS - 1)
|
|
rp->oddballs[j + 1] = rp->oddballs[j];
|
|
}
|
|
j++;
|
|
if (j >= NUM_ODDBALLS)
|
|
continue;
|
|
rp->oddballs[j] = offset;
|
|
}
|
|
}
|
|
|
|
/** Find the best region to hold a chunk.
|
|
* This is done by going through all the known regions and getting
|
|
* prospective unhappiness ratings if the chunk was placed there.
|
|
* The region with the least unhappiness wins. Note that this may
|
|
* well be a new region, if all existing regions are either full or
|
|
* sufficiently unhappy.
|
|
* \param full_len the size of the chunk, including headers.
|
|
* \param derefs the number of dereferences on the chunk.
|
|
* \param old_region the region the chunk was in before (if any).
|
|
* \return the region id for the least unhappy region.
|
|
*/
|
|
static uint16_t
|
|
find_best_region(uint16_t full_len, int derefs, uint16_t old_region)
|
|
{
|
|
uint16_t best_region, region;
|
|
int best_score, score;
|
|
int free_bytes;
|
|
Region *rp;
|
|
|
|
best_region = INVALID_REGION_ID;
|
|
best_score = INT_MAX;
|
|
free_bytes = 0;
|
|
for (region = 0; region < region_count; region++) {
|
|
rp = regions + region;
|
|
free_bytes += rp->free_bytes;
|
|
if (!FitsInSpace(full_len, rp->largest_free_chunk) &&
|
|
!(rp->free_count == 2 &&
|
|
rp->free_bytes - rp->largest_free_chunk == full_len))
|
|
continue;
|
|
|
|
if (region == old_region)
|
|
score = derefs - RegionDerefs(region);
|
|
else
|
|
score = derefs - RegionDerefsWithChunk(region, derefs);
|
|
if (score < 0)
|
|
score = -score;
|
|
if (!rp->in_memory)
|
|
score += IN_MEMORY_BIAS;
|
|
if (rp->used_count <= LONLINESS_LIMIT)
|
|
score += 1 << (LONLINESS_LIMIT - rp->used_count);
|
|
|
|
if (best_score > score) {
|
|
best_score = score;
|
|
best_region = region;
|
|
}
|
|
}
|
|
|
|
if (best_region == INVALID_REGION_ID) {
|
|
#ifdef DEBUG_CHUNK_REGION_CREATE
|
|
do_rawlog(LT_TRACE, "find_best_region had to create region %04x", region);
|
|
#endif
|
|
best_region = create_region();
|
|
} else if (best_score > (1 << LONLINESS_LIMIT) + IN_MEMORY_BIAS &&
|
|
(free_bytes * 100 / (REGION_CAPACITY * region_count)) <
|
|
FREE_PERCENT_LIMIT) {
|
|
#ifdef DEBUG_CHUNK_REGION_CREATE
|
|
do_rawlog(LT_TRACE, "find_best_region chose to create region %04x", region);
|
|
#endif
|
|
best_region = create_region();
|
|
}
|
|
return best_region;
|
|
}
|
|
|
|
/** Find the best offset in a region to hold a chunk.
|
|
* \param full_len the length of the chunk, including headers.
|
|
* \param region the region to allocate in.
|
|
* \param old_region the region the chunk was in before (if any).
|
|
* \param old_offset the offset the chunk was at before (if any).
|
|
*/
|
|
static uint16_t
|
|
find_best_offset(uint16_t full_len, uint16_t region, uint16_t old_region,
|
|
uint16_t old_offset)
|
|
{
|
|
uint16_t fits, offset;
|
|
|
|
bring_in_region(region);
|
|
|
|
fits = 0;
|
|
for (offset = regions[region].in_memory->first_free; offset;
|
|
offset = ChunkNextFree(region, offset)) {
|
|
if (region == old_region) {
|
|
if (offset > old_offset)
|
|
break;
|
|
if (offset + ChunkFullLen(region, offset) == old_offset)
|
|
return fits ? fits : offset;
|
|
}
|
|
if (ChunkFullLen(region, offset) == full_len)
|
|
return offset;
|
|
if (!fits && ChunkFullLen(region, offset) >= full_len + MIN_REMNANT_LEN)
|
|
fits = offset;
|
|
}
|
|
|
|
return fits;
|
|
}
|
|
|
|
/*
|
|
* Utility Routines - Statistics and debugging
|
|
*/
|
|
/** Compile a histogram for the region dereferences.
|
|
* \return histogram data for the regions.
|
|
*/
|
|
static int *
|
|
chunk_regionhist(void)
|
|
{
|
|
static int histogram[CHUNK_DEREF_MAX + 1];
|
|
unsigned int j;
|
|
|
|
for (j = 0; j <= CHUNK_DEREF_MAX; j++)
|
|
histogram[j] = 0;
|
|
for (j = 0; j < region_count; j++) {
|
|
histogram[RegionDerefs(j)]++;
|
|
}
|
|
return histogram;
|
|
}
|
|
|
|
/** Compile a histogram for the region free space.
|
|
* \return histogram data for the free space.
|
|
*/
|
|
static int const *
|
|
chunk_freehist(void)
|
|
{
|
|
static int histogram[CHUNK_DEREF_MAX + 1];
|
|
unsigned int j;
|
|
|
|
for (j = 0; j <= CHUNK_DEREF_MAX; j++)
|
|
histogram[j] = 0;
|
|
for (j = 0; j < region_count; j++) {
|
|
histogram[RegionDerefs(j)] += regions[j].free_bytes;
|
|
}
|
|
return histogram;
|
|
}
|
|
|
|
/** Display statistics to a player, or dump them to a log
|
|
*/
|
|
__attribute__((__format__(__printf__, 2, 3))) void WIN32_CDECL
|
|
STAT_OUT(dbref player, const char *fmt, ...)
|
|
{
|
|
char buff[BUFFER_LEN];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
mush_vsnprintf(buff, sizeof buff, fmt, args);
|
|
va_end(args);
|
|
if (GoodObject(player)) {
|
|
notify(player, buff);
|
|
} else {
|
|
do_rawlog(LT_TRACE, "%s", buff);
|
|
}
|
|
}
|
|
|
|
/** Display the stats summary page.
|
|
* \param player the player to display it to, or NOTHING to log it.
|
|
*/
|
|
static void
|
|
chunk_statistics(dbref player)
|
|
{
|
|
int overhead;
|
|
int free_count = 0;
|
|
int free_bytes = 0;
|
|
int free_large = 0;
|
|
int used_count = 0;
|
|
int used_bytes = 0;
|
|
uint16_t rid;
|
|
|
|
for (rid = 0; rid < region_count; rid++) {
|
|
free_count += regions[rid].free_count;
|
|
free_bytes += regions[rid].free_bytes;
|
|
free_large += regions[rid].largest_free_chunk;
|
|
used_count += regions[rid].used_count;
|
|
}
|
|
used_bytes = (REGION_CAPACITY * region_count) - free_bytes;
|
|
|
|
if (!GoodObject(player)) {
|
|
do_rawlog(LT_TRACE, "---- Chunk statistics");
|
|
}
|
|
overhead = stat_used_short_count * CHUNK_SHORT_DATA_OFFSET +
|
|
stat_used_medium_count * CHUNK_MEDIUM_DATA_OFFSET +
|
|
stat_used_long_count * CHUNK_LONG_DATA_OFFSET;
|
|
STAT_OUT(player,
|
|
"Chunks: %10d allocated (%10d bytes, %10d (%2d%%) overhead)", used_count,
|
|
used_bytes, overhead, used_bytes ? overhead * 100 / used_bytes : 0);
|
|
overhead = stat_used_short_count * CHUNK_SHORT_DATA_OFFSET;
|
|
STAT_OUT(player,
|
|
" %10d short (%10d bytes, %10d (%2d%%) overhead)",
|
|
stat_used_short_count, stat_used_short_bytes, overhead,
|
|
stat_used_short_bytes ? overhead * 100 / stat_used_short_bytes : 0);
|
|
overhead = stat_used_medium_count * CHUNK_MEDIUM_DATA_OFFSET;
|
|
STAT_OUT(player,
|
|
" %10d medium (%10d bytes, %10d (%2d%%) overhead)",
|
|
stat_used_medium_count, stat_used_medium_bytes, overhead,
|
|
stat_used_medium_bytes ? overhead * 100 / stat_used_medium_bytes : 0);
|
|
overhead = stat_used_long_count * CHUNK_LONG_DATA_OFFSET;
|
|
STAT_OUT(player,
|
|
" %10d long (%10d bytes, %10d (%2d%%) overhead)",
|
|
stat_used_long_count, stat_used_long_bytes, overhead,
|
|
stat_used_long_bytes ? overhead * 100 / stat_used_long_bytes : 0);
|
|
STAT_OUT(player,
|
|
" %10d free (%10d bytes, %10d (%2d%%) fragmented)",
|
|
free_count, free_bytes, free_bytes - free_large,
|
|
free_bytes ? (free_bytes - free_large) * 100 / free_bytes : 0);
|
|
overhead = region_count * REGION_SIZE + region_array_len * sizeof(Region);
|
|
STAT_OUT(player, "Storage: %10d total (%2d%% saturation)", overhead,
|
|
used_bytes * 100 / overhead);
|
|
STAT_OUT(player, "Regions: %10d total, %8d cached", (int) region_count,
|
|
(int) cached_region_count);
|
|
STAT_OUT(player,
|
|
"Paging: %10d out, %10d in", stat_page_out, stat_page_in);
|
|
STAT_OUT(player, " ");
|
|
STAT_OUT(player, "Period: %10d (%10d accesses so far, %10d chunks at max)",
|
|
(int) curr_period, stat_deref_count, stat_deref_maxxed);
|
|
STAT_OUT(player, "Activity: %10d creates, %10d deletes this period",
|
|
stat_create, stat_delete);
|
|
STAT_OUT(player, "Migration: %10d moves this period",
|
|
stat_migrate_slide + stat_migrate_move);
|
|
STAT_OUT(player, " %10d slide %10d move", stat_migrate_slide,
|
|
stat_migrate_move);
|
|
STAT_OUT(player, " %10d in region%10d out of region",
|
|
stat_migrate_slide + stat_migrate_move - stat_migrate_away,
|
|
stat_migrate_away);
|
|
}
|
|
|
|
/** Show just the page counts.
|
|
* \param player the player to display it to, or NOTHING to log it.
|
|
*/
|
|
static void
|
|
chunk_page_stats(dbref player)
|
|
{
|
|
STAT_OUT(player,
|
|
"Paging: %10d out, %10d in", stat_page_out, stat_page_in);
|
|
}
|
|
|
|
/** Display the per-region stats.
|
|
* \param player the player to display it to, or NOTHING to log it.
|
|
*/
|
|
static void
|
|
chunk_region_statistics(dbref player)
|
|
{
|
|
uint16_t rid;
|
|
|
|
if (!GoodObject(player)) {
|
|
do_rawlog(LT_TRACE, "---- Region statistics");
|
|
}
|
|
for (rid = 0; rid < region_count; rid++) {
|
|
STAT_OUT(player, "region:%4d #used:%5d #free:%5d "
|
|
"fbytes:%04x largest:%04x deref:%3d",
|
|
rid, regions[rid].used_count, regions[rid].free_count,
|
|
regions[rid].free_bytes, regions[rid].largest_free_chunk,
|
|
(int) RegionDerefs(rid));
|
|
}
|
|
}
|
|
|
|
/** Display a histogram.
|
|
* \param player the player to display it to, or NOTHING to log it.
|
|
* \param histogram the histogram data to display.
|
|
* \param legend the legend for the histogram.
|
|
*/
|
|
static void
|
|
chunk_histogram(dbref player, int const *histogram, char const *legend)
|
|
{
|
|
int j, k, max, pen, ante;
|
|
char buffer[20][65];
|
|
char num[16];
|
|
|
|
max = pen = ante = 0;
|
|
for (j = 0; j < 64; j++) {
|
|
k = histogram[j * 4 + 0] + histogram[j * 4 + 1] + histogram[j * 4 + 2] +
|
|
histogram[j * 4 + 3];
|
|
if (max < k) {
|
|
ante = pen;
|
|
pen = max;
|
|
max = k;
|
|
} else if (pen < k) {
|
|
ante = pen;
|
|
pen = k;
|
|
} else if (ante < k) {
|
|
ante = k;
|
|
}
|
|
}
|
|
if (ante < max / 2) {
|
|
if (pen < max / 2 && ante >= pen / 2)
|
|
max = pen;
|
|
else
|
|
max = ante;
|
|
}
|
|
if (max == 0)
|
|
max = 1;
|
|
for (j = 0; j < 20; j++) {
|
|
for (k = 0; k < 64; k++)
|
|
buffer[j][k] = ' ';
|
|
buffer[j][64] = '\0';
|
|
}
|
|
for (j = 0; j < 64; j++) {
|
|
k = histogram[j * 4 + 0] + histogram[j * 4 + 1] + histogram[j * 4 + 2] +
|
|
histogram[j * 4 + 3];
|
|
k = k * 20 / max;
|
|
if (k >= 20)
|
|
k = 20;
|
|
while (k-- > 0)
|
|
buffer[k][j] = '*';
|
|
}
|
|
pen = 0;
|
|
for (j = 0; j < 64; j++) {
|
|
k = histogram[j * 4 + 0] + histogram[j * 4 + 1] + histogram[j * 4 + 2] +
|
|
histogram[j * 4 + 3];
|
|
if (k > max) {
|
|
snprintf(num, sizeof num, "(%d)", k);
|
|
if (j < 32) {
|
|
if (j < pen)
|
|
ante = 18;
|
|
else
|
|
ante = 19;
|
|
memcpy(buffer[ante] + j + 1, num, strlen(num));
|
|
pen = j + strlen(num) + 1;
|
|
} else {
|
|
if (j - (int) strlen(num) < pen)
|
|
ante = 18;
|
|
else
|
|
ante = 19;
|
|
memcpy(buffer[ante] + j - strlen(num), num, strlen(num));
|
|
pen = j;
|
|
}
|
|
}
|
|
}
|
|
STAT_OUT(player, "%s", "");
|
|
STAT_OUT(player, "%s", legend);
|
|
STAT_OUT(player, "%6d |%s", max, buffer[19]);
|
|
j = 19;
|
|
while (j-- > 1)
|
|
STAT_OUT(player, " |%s", buffer[j]);
|
|
STAT_OUT(player, " 0 |%s", buffer[0]);
|
|
for (j = 0, k = 2; j < 64; j++, k += 4)
|
|
buffer[0][j] = '-';
|
|
STAT_OUT(player, " +%s", buffer[0]);
|
|
STAT_OUT(player, " 0%31s%32d", "|", 255);
|
|
}
|
|
|
|
#undef STAT_OUT
|
|
|
|
/*
|
|
* Utility Routines - Migration
|
|
*/
|
|
|
|
static void
|
|
migrate_sort(void)
|
|
{
|
|
int j, k;
|
|
chunk_reference_t *t;
|
|
|
|
for (j = 1; j < m_count; j++) {
|
|
t = m_references[j];
|
|
for (k = j; k--;) {
|
|
if (m_references[k][0] < t[0])
|
|
break;
|
|
m_references[k + 1] = m_references[k];
|
|
}
|
|
m_references[k + 1] = t;
|
|
}
|
|
}
|
|
|
|
/** Slide an allocated chunk over into a neighboring free space.
|
|
* \param region the region of the free space.
|
|
* \param offset the offset of the free space.
|
|
* \param which the index (in the migration arrays) of the chunk to move.
|
|
*/
|
|
static void
|
|
migrate_slide(uint16_t region, uint16_t offset, int which)
|
|
{
|
|
Region *rp = regions + region;
|
|
uint16_t o_len, len, next, other, prev, o_off, o_oth;
|
|
|
|
debug_log("migrate_slide %d (%08x) to %04x%04x", which,
|
|
m_references[which][0], region, offset);
|
|
|
|
bring_in_region(region);
|
|
|
|
len = ChunkFullLen(region, offset);
|
|
next = ChunkNextFree(region, offset);
|
|
other = ChunkReferenceToOffset(m_references[which][0]);
|
|
o_len = ChunkFullLen(region, other);
|
|
|
|
o_off = offset;
|
|
o_oth = other;
|
|
if (other > offset) {
|
|
memmove(ChunkPointer(region, offset), ChunkPointer(region, other), o_len);
|
|
#ifdef DEBUG_CHUNK_MIGRATE
|
|
do_rawlog(LT_TRACE, "CHUNK: Sliding chunk %08x to %04x%04x",
|
|
m_references[which][0], region, offset);
|
|
#endif
|
|
m_references[which][0] = ChunkReference(region, offset);
|
|
other = offset + o_len;
|
|
} else {
|
|
prev = offset + len - o_len;
|
|
memmove(ChunkPointer(region, prev), ChunkPointer(region, other), o_len);
|
|
#ifdef DEBUG_CHUNK_MIGRATE
|
|
do_rawlog(LT_TRACE, "CHUNK: Sliding chunk %08x to %04x%04x",
|
|
m_references[which][0], region, prev);
|
|
#endif
|
|
m_references[which][0] = ChunkReference(region, prev);
|
|
}
|
|
write_free_chunk(region, other, len, next);
|
|
coalesce_frees(region, other);
|
|
if (rp->in_memory->first_free == offset) {
|
|
rp->in_memory->first_free = other;
|
|
} else {
|
|
prev = rp->in_memory->first_free;
|
|
while (prev && ChunkNextFree(region, prev) != offset)
|
|
prev = ChunkNextFree(region, prev);
|
|
write_next_free(region, prev, other);
|
|
coalesce_frees(region, prev);
|
|
}
|
|
|
|
stat_migrate_slide++;
|
|
|
|
#ifdef CHUNK_PARANOID
|
|
if (!region_is_valid(region)) {
|
|
struct log_stream *trace;
|
|
do_rawlog(LT_TRACE, "Invalid region after migrate_slide!");
|
|
do_rawlog(LT_TRACE, "Was moving %04x%04x to %04x%04x (became %08x)...",
|
|
region, o_oth, region, o_off,
|
|
(unsigned int) m_references[which][0]);
|
|
do_rawlog(LT_TRACE, "Chunk length %04x into hole length %04x", o_len, len);
|
|
trace = lookup_log(LT_TRACE);
|
|
debug_dump_region(region, trace->fp);
|
|
dump_debug_log(trace->fp);
|
|
mush_panic("Invalid region after migrate_slide!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Move an allocated chunk into a free hole.
|
|
* \param region the region of the free space.
|
|
* \param offset the offset of the free space.
|
|
* \param align the alignment to use: 0 = easiest, 1 = left, 2 = right.
|
|
* \param which the index (in the migration arrays) of the chunk to move.
|
|
*/
|
|
static void
|
|
migrate_move(uint16_t region, uint16_t offset, int which, int align)
|
|
{
|
|
Region *rp = regions + region;
|
|
uint16_t s_reg, s_off, s_len, o_off, length;
|
|
Region *srp;
|
|
|
|
debug_log("migrate_move %d (%08x) to %04x%04x, alignment %d", which,
|
|
m_references[which][0], region, offset, align);
|
|
|
|
s_reg = ChunkReferenceToRegion(m_references[which][0]);
|
|
s_off = ChunkReferenceToOffset(m_references[which][0]);
|
|
srp = regions + s_reg;
|
|
|
|
bring_in_region(region);
|
|
if (!srp->in_memory) {
|
|
touch_cache_region(rp->in_memory);
|
|
bring_in_region(s_reg);
|
|
touch_cache_region(rp->in_memory);
|
|
}
|
|
|
|
s_len = ChunkFullLen(s_reg, s_off);
|
|
length = ChunkFullLen(region, offset);
|
|
|
|
if (s_reg == region &&
|
|
(s_off + s_len == offset || offset + length == s_off)) {
|
|
migrate_slide(region, offset, which);
|
|
return;
|
|
}
|
|
#ifdef CHUNK_PARANOID
|
|
if (!FitsInSpace(s_len, ChunkFullLen(region, offset))) {
|
|
dump_debug_log(lookup_log(LT_TRACE)->fp);
|
|
mush_panicf("Trying to migrate into too small a hole: %04x into %04x!",
|
|
s_len, length);
|
|
}
|
|
#endif
|
|
|
|
o_off = offset;
|
|
offset = split_hole(region, offset, s_len, align);
|
|
memcpy(ChunkPointer(region, offset), ChunkPointer(s_reg, s_off), s_len);
|
|
#ifdef DEBUG_CHUNK_MIGRATE
|
|
do_rawlog(LT_TRACE, "CHUNK: moving chunk %08x to %04x%04x",
|
|
m_references[which][0], region, offset);
|
|
#endif
|
|
m_references[which][0] = ChunkReference(region, offset);
|
|
rp->total_derefs += ChunkDerefs(region, offset);
|
|
free_chunk(s_reg, s_off);
|
|
|
|
stat_migrate_move++;
|
|
|
|
#ifdef CHUNK_PARANOID
|
|
if (!region_is_valid(region)) {
|
|
do_rawlog(LT_TRACE, "Invalid region after migrate_move!");
|
|
do_rawlog(LT_TRACE, "Was moving %04x%04x to %04x%04x (became %04x%04x)...",
|
|
s_reg, s_off, region, o_off, region, offset);
|
|
do_rawlog(LT_TRACE, "Chunk length %04x into hole length %04x, alignment %d",
|
|
s_len, length, align);
|
|
debug_dump_region(region, lookup_log(LT_TRACE)->fp);
|
|
mush_panic("Invalid region after migrate_move!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
migrate_region(uint16_t region)
|
|
{
|
|
chunk_reference_t high, low;
|
|
int j, derefs;
|
|
uint16_t offset, length, best_region, best_offset;
|
|
|
|
bring_in_region(region);
|
|
|
|
high = ChunkReference(region, REGION_SIZE);
|
|
low = ChunkReference(region, 0);
|
|
|
|
for (j = 0; j < m_count; j++) {
|
|
if (low < m_references[j][0] && m_references[j][0] < high) {
|
|
offset = ChunkReferenceToOffset(m_references[j][0]);
|
|
derefs = ChunkDerefs(region, offset);
|
|
length = ChunkFullLen(region, offset);
|
|
best_region = find_best_region(length, derefs, region);
|
|
best_offset = find_best_offset(length, best_region, region, offset);
|
|
if (best_offset)
|
|
migrate_move(best_region, best_offset, j, 1);
|
|
if (best_region != region)
|
|
stat_migrate_away++;
|
|
}
|
|
}
|
|
migrate_sort();
|
|
}
|
|
|
|
static chunk_reference_t
|
|
acc_chunk_create(char const *data, uint16_t len, uint8_t derefs)
|
|
{
|
|
uint16_t full_len, region, offset;
|
|
|
|
if (len < MIN_CHUNK_LEN || len > MAX_CHUNK_LEN)
|
|
mush_panicf("Illegal chunk length requested: %d bytes", len);
|
|
|
|
full_len = LenToFullLen(len);
|
|
region = find_best_region(full_len, derefs, INVALID_REGION_ID);
|
|
offset = find_best_offset(full_len, region, INVALID_REGION_ID, 0);
|
|
if (!offset) {
|
|
region = create_region();
|
|
#ifdef DEBUG_CHUNK_REGION_CREATE
|
|
do_rawlog(LT_TRACE, "chunk_create created region %04x", region);
|
|
#endif
|
|
offset = FIRST_CHUNK_OFFSET_IN_REGION;
|
|
}
|
|
offset = split_hole(region, offset, full_len, 0);
|
|
write_used_chunk(region, offset, full_len, data, len, derefs);
|
|
regions[region].total_derefs += derefs;
|
|
touch_cache_region(regions[region].in_memory);
|
|
#ifdef CHUNK_PARANOID
|
|
if (!region_is_valid(region))
|
|
mush_panic("Invalid region after chunk_create!");
|
|
#endif
|
|
stat_create++;
|
|
return ChunkReference(region, offset);
|
|
}
|
|
|
|
static void
|
|
acc_chunk_delete(chunk_reference_t reference)
|
|
{
|
|
uint16_t region, offset;
|
|
region = ChunkReferenceToRegion(reference);
|
|
offset = ChunkReferenceToOffset(reference);
|
|
ASSERT(region < region_count);
|
|
bring_in_region(region);
|
|
#ifdef CHUNK_PARANOID
|
|
verify_used_chunk(region, offset);
|
|
#endif
|
|
free_chunk(region, offset);
|
|
touch_cache_region(regions[region].in_memory);
|
|
#ifdef CHUNK_PARANOID
|
|
if (!region_is_valid(region))
|
|
mush_panic("Invalid region after chunk_delete!");
|
|
#endif
|
|
stat_delete++;
|
|
}
|
|
|
|
static uint16_t
|
|
acc_chunk_fetch(chunk_reference_t reference, char *buffer, uint16_t buffer_len)
|
|
{
|
|
uint16_t region, offset, len;
|
|
region = ChunkReferenceToRegion(reference);
|
|
offset = ChunkReferenceToOffset(reference);
|
|
ASSERT(region < region_count);
|
|
bring_in_region(region);
|
|
#ifdef CHUNK_PARANOID
|
|
verify_used_chunk(region, offset);
|
|
#endif
|
|
len = ChunkLen(region, offset);
|
|
if (len <= buffer_len)
|
|
memcpy(buffer, ChunkDataPtr(region, offset), len);
|
|
touch_cache_region(regions[region].in_memory);
|
|
stat_deref_count++;
|
|
if (ChunkDerefs(region, offset) < CHUNK_DEREF_MAX) {
|
|
SetChunkDerefs(region, offset, ChunkDerefs(region, offset) + 1);
|
|
regions[region].total_derefs++;
|
|
if (ChunkDerefs(region, offset) == CHUNK_DEREF_MAX)
|
|
stat_deref_maxxed++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static uint16_t
|
|
acc_chunk_len(chunk_reference_t reference)
|
|
{
|
|
return acc_chunk_fetch(reference, NULL, 0);
|
|
}
|
|
|
|
static uint8_t
|
|
acc_chunk_derefs(chunk_reference_t reference)
|
|
{
|
|
uint16_t region, offset;
|
|
region = ChunkReferenceToRegion(reference);
|
|
offset = ChunkReferenceToOffset(reference);
|
|
ASSERT(region < region_count);
|
|
bring_in_region(region);
|
|
#ifdef CHUNK_PARANOID
|
|
verify_used_chunk(region, offset);
|
|
#endif
|
|
return ChunkDerefs(region, offset);
|
|
}
|
|
|
|
static void
|
|
acc_chunk_migration(int count, chunk_reference_t **references)
|
|
{
|
|
int k, l;
|
|
unsigned total;
|
|
uint16_t region, offset;
|
|
|
|
debug_log("*** chunk_migration starts, count = %d", count);
|
|
|
|
/* Before everything, see if we need a new period. */
|
|
total = 0;
|
|
for (region = 0; region < region_count; region++) {
|
|
if (RegionDerefs(region) > (CHUNK_DEREF_MAX / 2))
|
|
total++;
|
|
}
|
|
if (total > cached_region_count || total > region_count / 2)
|
|
chunk_new_period();
|
|
|
|
m_count = count;
|
|
m_references = references;
|
|
migrate_sort();
|
|
|
|
/* Go through each of the regions. */
|
|
for (region = 0; region < region_count; region++) {
|
|
/* Make sure we have something to migrate, in the region. */
|
|
for (k = 0; k < m_count; k++)
|
|
if (ChunkReferenceToRegion(m_references[k][0]) == region)
|
|
break;
|
|
if (k >= m_count)
|
|
continue;
|
|
|
|
if (!regions[region].in_memory) {
|
|
/* If not in memory, see if we've got an oddball. */
|
|
while (k < m_count) {
|
|
if (ChunkReferenceToRegion(m_references[k][0]) != region)
|
|
break;
|
|
offset = ChunkReferenceToOffset(m_references[k][0]);
|
|
for (l = 0; l < NUM_ODDBALLS; l++) {
|
|
if (regions[region].oddballs[l] == offset) {
|
|
/* Yup, have an oddball... that's worth bringing it in. */
|
|
bring_in_region(region);
|
|
goto do_migrate;
|
|
}
|
|
}
|
|
k++;
|
|
}
|
|
} else {
|
|
do_migrate:
|
|
/* It's in memory, so migrate it. */
|
|
migrate_region(region);
|
|
}
|
|
}
|
|
|
|
m_references = NULL;
|
|
m_count = 0;
|
|
|
|
debug_log("*** chunk_migration ends", count);
|
|
}
|
|
|
|
static int
|
|
acc_chunk_num_swapped(void)
|
|
{
|
|
int count;
|
|
uint16_t region;
|
|
count = 0;
|
|
for (region = 0; region < region_count; region++)
|
|
if (!regions[region].in_memory)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_init(void)
|
|
{
|
|
/* In any case, this assert should be in main code, not here */
|
|
ASSERT(BUFFER_LEN <= MAX_LONG_CHUNK_LEN);
|
|
|
|
#ifdef WIN32
|
|
swap_fd = CreateFile(CHUNK_SWAP_FILE, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
|
CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
|
if (swap_fd == INVALID_HANDLE_VALUE)
|
|
mush_panicf("Cannot open swap file: %lu", GetLastError());
|
|
#else
|
|
swap_fd = open(CHUNK_SWAP_FILE, O_RDWR | O_TRUNC | O_CREAT, 0600);
|
|
if (swap_fd < 0)
|
|
mush_panicf("Cannot open swap file: %s", strerror(errno));
|
|
#endif
|
|
curr_period = 0;
|
|
|
|
cached_region_count = 0;
|
|
cache_head = NULL;
|
|
cache_tail = NULL;
|
|
|
|
#ifdef HAVE_POSIX_FALLOCATE
|
|
/* Reserve some space for the swap file to start with. */
|
|
if (options.chunk_swap_initial > 0)
|
|
posix_fallocate(swap_fd, 0, (options.chunk_swap_initial * 1024));
|
|
#endif
|
|
|
|
region_count = 0;
|
|
region_array_len = FIXME_INIT_REGION_LEN;
|
|
#ifdef DEBUG_CHUNK_MALLOC
|
|
do_rawlog(LT_TRACE, "CHUNK: malloc()ing initial region array");
|
|
#endif
|
|
regions = mush_calloc(region_array_len, sizeof(Region), "chunk region list");
|
|
if (!regions)
|
|
mush_panic("cannot malloc space for chunk region list");
|
|
|
|
/*
|
|
command_add("@DEBUGCHUNK", CMD_T_ANY | CMD_T_GOD, 0, 0, 0,
|
|
switchmask("ALL BRIEF FULL"), cmd_debugchunk);
|
|
*/
|
|
do_rawlog(LT_TRACE, "CHUNK: chunk subsystem initialized");
|
|
}
|
|
|
|
static void
|
|
acc_chunk_stats(dbref player, enum chunk_stats_type which)
|
|
{
|
|
switch (which) {
|
|
case CSTATS_SUMMARY:
|
|
chunk_statistics(player);
|
|
break;
|
|
case CSTATS_REGIONG:
|
|
chunk_histogram(player, chunk_regionhist(),
|
|
"Chart number of regions (y) vs. references (x)");
|
|
break;
|
|
case CSTATS_PAGINGG:
|
|
chunk_histogram(player, stat_paging_histogram,
|
|
"Chart pages in/out (y) vs. references (x)");
|
|
break;
|
|
case CSTATS_FREESPACEG:
|
|
chunk_histogram(player, chunk_freehist(),
|
|
"Chart region free space (y) vs. references (x)");
|
|
break;
|
|
case CSTATS_REGION:
|
|
chunk_region_statistics(player);
|
|
break;
|
|
case CSTATS_PAGING:
|
|
chunk_page_stats(player);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
acc_chunk_new_period(void)
|
|
{
|
|
RegionHeader *rhp;
|
|
Region *rp;
|
|
uint16_t region, offset;
|
|
int shift;
|
|
|
|
#ifdef LOG_CHUNK_STATS
|
|
/* Log stats */
|
|
chunk_statistics(NOTHING);
|
|
#endif
|
|
|
|
/* Reset period info */
|
|
curr_period++;
|
|
stat_deref_count = 0;
|
|
stat_deref_maxxed = 0;
|
|
stat_migrate_slide = 0;
|
|
stat_migrate_move = 0;
|
|
stat_migrate_away = 0;
|
|
stat_create = 0;
|
|
stat_delete = 0;
|
|
|
|
/* make derefs current */
|
|
for (rhp = cache_head; rhp; rhp = rhp->next) {
|
|
region = rhp->region_id;
|
|
if (region == INVALID_REGION_ID)
|
|
continue;
|
|
rp = regions + region;
|
|
|
|
shift = curr_period - rp->period_last_touched;
|
|
if (shift > 8) {
|
|
rp->total_derefs = 0;
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += ChunkFullLen(region, offset)) {
|
|
SetChunkDerefs(region, offset, 0);
|
|
}
|
|
} else {
|
|
rp->total_derefs = 0;
|
|
for (offset = FIRST_CHUNK_OFFSET_IN_REGION; offset < REGION_SIZE;
|
|
offset += ChunkFullLen(region, offset)) {
|
|
if (ChunkIsFree(region, offset))
|
|
continue;
|
|
SetChunkDerefs(region, offset, ChunkDerefs(region, offset) >> shift);
|
|
rp->total_derefs += ChunkDerefs(region, offset);
|
|
}
|
|
}
|
|
rp->period_last_touched = curr_period;
|
|
}
|
|
}
|
|
|
|
#ifndef WIN32
|
|
static int
|
|
acc_chunk_fork_file(void)
|
|
{
|
|
unsigned int j;
|
|
RegionHeader *rhp, *prev, *next;
|
|
|
|
/* abort if already cloned */
|
|
if (swap_fd_child >= 0)
|
|
return 0;
|
|
|
|
j = 0;
|
|
for (;;) {
|
|
snprintf(child_filename, sizeof child_filename, "%s.%d", CHUNK_SWAP_FILE,
|
|
j);
|
|
swap_fd_child = open(child_filename, O_RDWR | O_EXCL | O_CREAT, 0600);
|
|
if (swap_fd_child >= 0)
|
|
break;
|
|
if (j >= 10)
|
|
return 0;
|
|
j++;
|
|
}
|
|
|
|
#ifdef HAVE_POSIX_FALLOCATE
|
|
/* Try to reserve all the space needed for the child's copy of the chunk file
|
|
* all at once. */
|
|
{
|
|
struct stat fsize;
|
|
if (fstat(swap_fd, &fsize) == 0)
|
|
posix_fallocate(swap_fd_child, 0, fsize.st_size);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_POSIX_FADVISE
|
|
posix_fadvise(swap_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
|
|
#endif
|
|
|
|
rhp = find_available_cache_region();
|
|
prev = rhp->prev;
|
|
next = rhp->next;
|
|
for (j = 0; j < region_count; j++) {
|
|
if (regions[j].in_memory)
|
|
continue;
|
|
|
|
read_cache_region(swap_fd, rhp, j);
|
|
write_cache_region(swap_fd_child, rhp, j);
|
|
}
|
|
rhp->region_id = INVALID_REGION_ID;
|
|
rhp->prev = prev;
|
|
rhp->next = next;
|
|
|
|
#ifdef HAVE_POSIX_FADVISE
|
|
posix_fadvise(swap_fd, 0, 0, POSIX_FADV_RANDOM);
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_parent(void)
|
|
{
|
|
if (swap_fd_child < 0)
|
|
return;
|
|
|
|
close(swap_fd_child);
|
|
swap_fd_child = -1;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_child(void)
|
|
{
|
|
if (swap_fd_child < 0)
|
|
return;
|
|
|
|
close(swap_fd);
|
|
|
|
#ifdef HAVE_POSIX_FADVISE
|
|
posix_fadvise(swap_fd_child, 0, 0, POSIX_FADV_RANDOM);
|
|
#endif
|
|
|
|
swap_fd = swap_fd_child;
|
|
swap_fd_child = -1;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_done(void)
|
|
{
|
|
if (swap_fd_child < 0)
|
|
close(swap_fd);
|
|
else
|
|
close(swap_fd_child);
|
|
|
|
unlink(child_filename);
|
|
swap_fd_child = -1;
|
|
}
|
|
|
|
#else
|
|
|
|
static int
|
|
acc_chunk_fork_file(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_parent(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_child(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void
|
|
acc_chunk_fork_done(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#endif /* !WIN32 */
|
|
|
|
struct ac_funcs {
|
|
chunk_reference_t (*chunk_create)(char const *, uint16_t, uint8_t);
|
|
void (*chunk_delete)(chunk_reference_t);
|
|
uint16_t (*fetch)(chunk_reference_t, char *, uint16_t);
|
|
uint16_t (*len)(chunk_reference_t);
|
|
uint8_t (*derefs)(chunk_reference_t);
|
|
void (*migration)(int, chunk_reference_t **);
|
|
int (*num_swapped)(void);
|
|
void (*init)(void);
|
|
void (*stats)(dbref, enum chunk_stats_type);
|
|
void (*new_period)(void);
|
|
int (*fork_file)(void);
|
|
void (*fork_parent)(void);
|
|
void (*fork_child)(void);
|
|
void (*fork_done)(void);
|
|
};
|
|
|
|
static struct ac_funcs malloc_interface = {
|
|
acm_chunk_create, acm_chunk_delete, acm_chunk_fetch,
|
|
acm_chunk_len, acm_chunk_derefs, acm_chunk_migration,
|
|
acm_chunk_num_swapped, acm_chunk_init, acm_chunk_stats,
|
|
acm_chunk_new_period, acm_chunk_fork_file, acm_chunk_fork_parent,
|
|
acm_chunk_fork_child, acm_chunk_fork_done};
|
|
|
|
static struct ac_funcs chunk_interface = {
|
|
acc_chunk_create, acc_chunk_delete, acc_chunk_fetch,
|
|
acc_chunk_len, acc_chunk_derefs, acc_chunk_migration,
|
|
acc_chunk_num_swapped, acc_chunk_init, acc_chunk_stats,
|
|
acc_chunk_new_period, acc_chunk_fork_file, acc_chunk_fork_parent,
|
|
acc_chunk_fork_child, acc_chunk_fork_done};
|
|
|
|
static struct ac_funcs *chunker = NULL;
|
|
/*
|
|
* Interface routines
|
|
*/
|
|
/** Allocate a chunk of storage.
|
|
* \param data the data to be stored.
|
|
* \param len the length of the data to be stored.
|
|
* \param derefs the deref count to set on the chunk.
|
|
* \return the chunk reference for retrieving (or deleting) the data.
|
|
*/
|
|
chunk_reference_t
|
|
chunk_create(char const *data, uint16_t len, uint8_t derefs)
|
|
{
|
|
return chunker->chunk_create(data, len, derefs);
|
|
}
|
|
|
|
/** Deallocate a chunk of storage.
|
|
* \param reference the reference to the chunk to be freed.
|
|
*/
|
|
void
|
|
chunk_delete(chunk_reference_t reference)
|
|
{
|
|
chunker->chunk_delete(reference);
|
|
}
|
|
|
|
/** Fetch a chunk of data.
|
|
* If the chunk is too large to fit in the supplied buffer, then
|
|
* the buffer will be left untouched. The length of the data is
|
|
* returned regardless; this can be used to resize the buffer
|
|
* (or just as information for further processing of the data).
|
|
* \param reference the reference to the chunk to be fetched.
|
|
* \param buffer the buffer to put the data into.
|
|
* \param buffer_len the length of the buffer.
|
|
* \return the length of the data.
|
|
*/
|
|
uint16_t
|
|
chunk_fetch(chunk_reference_t reference, char *buffer, uint16_t buffer_len)
|
|
{
|
|
return chunker->fetch(reference, buffer, buffer_len);
|
|
}
|
|
|
|
/** Get the length of a chunk.
|
|
* This is equivalent to calling chunk_fetch(reference, NULL, 0).
|
|
* It can be used to glean the proper size for a buffer to actually
|
|
* retrieve the data, if you're being stingy.
|
|
* \param reference the reference to the chunk to be queried.
|
|
* \return the length of the data.
|
|
*/
|
|
uint16_t
|
|
chunk_len(chunk_reference_t reference)
|
|
{
|
|
return chunker->len(reference);
|
|
}
|
|
|
|
/** Get the deref count of a chunk.
|
|
* This can be used to preserve the deref count across database saves
|
|
* or similar save and restore operations.
|
|
* \param reference the reference to the chunk to be queried.
|
|
* \return the deref count for data.
|
|
*/
|
|
uint8_t
|
|
chunk_derefs(chunk_reference_t reference)
|
|
{
|
|
return chunker->derefs(reference);
|
|
}
|
|
|
|
/** Migrate allocated chunks around.
|
|
*
|
|
* \param count the number of chunks to move.
|
|
* \param references an array of pointers to chunk references,
|
|
* which will be updated in place if necessary.
|
|
*/
|
|
void
|
|
chunk_migration(int count, chunk_reference_t **references)
|
|
{
|
|
chunker->migration(count, references);
|
|
}
|
|
|
|
/** Get the number of paged regions.
|
|
* Since the memory allocator cannot be reliably accessed from
|
|
* multiple processes if any of the chunks have been swapped out
|
|
* to disk, it's useful to check on the number of paged out regions
|
|
* before doing any forking maneuvers.
|
|
* \return the number of regions pages out.
|
|
*/
|
|
int
|
|
chunk_num_swapped(void)
|
|
{
|
|
return chunker->num_swapped();
|
|
}
|
|
|
|
/** Initialize chunk subsystem.
|
|
* Nothing to see here... just call it before using the subsystem.
|
|
*/
|
|
void
|
|
chunk_init(void)
|
|
{
|
|
if (options.use_chunk)
|
|
chunker = &chunk_interface;
|
|
else
|
|
chunker = &malloc_interface;
|
|
|
|
chunker->init();
|
|
}
|
|
|
|
/** Report statistics.
|
|
* Display either the statistics summary or one of the histograms.
|
|
* \param player the player to display it to, or NOTHING to log it.
|
|
* \param which what type of information to display.
|
|
*/
|
|
void
|
|
chunk_stats(dbref player, enum chunk_stats_type which)
|
|
{
|
|
chunker->stats(player, which);
|
|
}
|
|
|
|
/** Start a new migration period.
|
|
* This chops all the dereference counts in half. Since this is called
|
|
* from migration as needed, you probably shouldn't bother calling it
|
|
* yourself.
|
|
*/
|
|
void
|
|
chunk_new_period(void)
|
|
{
|
|
chunker->new_period();
|
|
}
|
|
|
|
#ifndef WIN32
|
|
/** Clone the chunkswap file for forking dumps.
|
|
* \retval 0 if unable to clone the swap file
|
|
* \retval 1 if swap file clone succeeded
|
|
*/
|
|
int
|
|
chunk_fork_file(void)
|
|
{
|
|
return chunker->fork_file();
|
|
}
|
|
|
|
/** Assert that we're the parent after fork.
|
|
*/
|
|
void
|
|
chunk_fork_parent(void)
|
|
{
|
|
chunker->fork_parent();
|
|
}
|
|
|
|
/** Assert that we're the child after fork.
|
|
*/
|
|
void
|
|
chunk_fork_child(void)
|
|
{
|
|
chunker->fork_child();
|
|
}
|
|
|
|
/** Assert that we're done with the cloned chunkswap file.
|
|
*/
|
|
void
|
|
chunk_fork_done(void)
|
|
{
|
|
chunker->fork_done();
|
|
}
|
|
|
|
#endif /* !WIN32 */
|