libcw/cwNbMem.cpp

410 lines
9.5 KiB
C++
Raw Normal View History

//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
2020-01-27 22:51:56 +00:00
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwNbMem.h"
#include "cwThread.h"
/*
=======================
0000 void* mem - pointer to base of memory (also pointer to this block_t record)
0008 var_t* var - pointer to first var
0016 block_t* link - link to next block
0024 unsigned byteN - count of bytes following first
0028 unsigned - (reserved)
0032 unsigned byteN - data byte count (16)
0036 unsigned refN - reference count
0040 <----- first data word
0044
0048
0052
0056 unsigned byteN - data byte count (16)
0060 unsigned refN - reference count
0064 <----- first data word
0068
0072
0076
*/
namespace cw
{
namespace nbmem
{
typedef struct var_str
{
std::atomic<unsigned long long> refN; //
unsigned byteN; // byteN is the size of the data area of this variable (The total size is (sizeof(var_t) + byteN)
unsigned reserved; //
} var_t;
typedef struct block_str
{
char* mem; // base of data memory for this block | block |var0|data0|var1|data1| |varx|datax|
var_t* var; //
struct block_str* link; //
unsigned byteN; // mem bytes = sizeof(block_t) + byteN
unsigned res0; //
} block_t;
typedef struct nbmem_str
{
unsigned blockByteN; //
block_t* base; //
} nbmem_t;
nbmem_t* _handleToPtr( handle_t h )
{ return handleToPtr<handle_t,nbmem_t>(h); }
rc_t _destroy( nbmem_t* p )
{
rc_t rc = kOkRC;
block_t* b = p->base;
while( b!=nullptr )
{
block_t* b0 = b->link;
mem::release(b);
b = b0;
}
mem::release(p);
return rc;
}
block_t* _alloc_block( unsigned byteN )
{
unsigned blockByteN = byteN + sizeof(block_t);
char* mem = mem::allocZ<char>(blockByteN);
block_t* b = (block_t*)mem;
b->mem = mem;
b->byteN = byteN;
b->link = nullptr;
b->var = (var_t*)( mem + sizeof(block_t) );
b->var->byteN = byteN - sizeof(var_t);
b->var->refN = 0;
return b;
}
//
void* _alloc_var( var_t* v, unsigned byteN, char* dataAreaEnd )
{
//
while( (char*)v < dataAreaEnd )
{
char* b = (char*)v; // base of this variable memory
char* d = b + sizeof(var_t); // ptr to variable data area
// is this variable available?
if( v->refN == 0 && v->byteN > byteN )
{
v->refN = 1; // ATOMIC mark variable as unavailable - should set thread id NOT 1
// if there is more than sizeof(var_t) extra data space ...
if( v->byteN > byteN + sizeof(var_t) )
{
// ... then split the space with another variable
unsigned byte0N = v->byteN;
// setup the split var_t
var_t* v0 = (var_t*)(d + byteN);
v0->byteN = byte0N - (byteN + sizeof(var_t));
v0->refN = 0;
// update the leading var size - last
v->byteN = byteN; // ATOMIC release
}
return d;
}
// v is not available advance to the next var
v = (var_t*)(d + v->byteN);
}
return nullptr;
}
}
}
cw::rc_t cw::nbmem::create( handle_t& h, unsigned blockByteN )
{
rc_t rc = kOkRC;
if((rc = destroy(h)) != kOkRC )
return rc;
nbmem_t* p = mem::allocZ< nbmem_t >(1);
p->base = _alloc_block( blockByteN );
p->blockByteN = blockByteN;
h.set(p);
return rc;
}
cw::rc_t cw::nbmem::destroy( handle_t& h )
{
rc_t rc = kOkRC;
if( !h.isValid() )
return rc;
nbmem_t* p = _handleToPtr(h);
if((rc = _destroy(p)) != kOkRC )
return rc;
h.clear();
return rc;
}
void* cw::nbmem::alloc( handle_t h, unsigned byteN )
{
nbmem_t* p = _handleToPtr(h);
block_t* b = p->base;
void* result = nullptr;
// try to locate an available variable in the existing blocks
for(; b!=nullptr; b=b->link)
if((result = _alloc_var( b->var, byteN, b->mem + b->byteN )) != nullptr )
break;
// if no available var space was found
if( b == nullptr )
{
// the new block size must be at least as large as the requested variable size
unsigned blockByteN = std::max( p->blockByteN, byteN );
// allocate a new block
block_t* b = _alloc_block( blockByteN );
// try the var allocation again
result = _alloc_var( b->var, byteN, b->mem + b->byteN );
// link in the new block
b->link = p->base;
p->base = b; // ATOMIC
}
return result;
}
void cw::nbmem::release( handle_t h, void* v )
{
nbmem_t* p = _handleToPtr(h);
block_t* b = p->base;
for(; b!=nullptr; b=b->link)
{
// if the memory to free is in this block
if( (b->mem <= (char*)v) && ((char*)v < b->mem + (sizeof(block_t) + b->byteN)) )
{
// v points to data space - back up by the size of var_t to point to the block header
var_t* var = static_cast<var_t*>(v) - 1;
var->refN -= 1; // ATOMIC
// check for too many frees
if( var->refN < 0 )
{
var->refN = 0; // ATOMIC
cwLogError(kInvalidOpRC,"An nbmem memory block was doube freed.");
}
return;
}
}
cwLogError(kInvalidArgRC,"Invalid memory pointer passed to nbmem::release().");
}
void cw::nbmem::report( handle_t h )
{
nbmem_t* p = _handleToPtr(h);
unsigned i = 0;
block_t* b = p->base;
for(; b!=nullptr; b=b->link)
{
printf("mem:%p byteN:%i var:%p link:%p\n",b->mem,b->byteN,b->var,b->link);
var_t* v = b->var;
char* blockEnd = b->mem + sizeof(block_t) + b->byteN;
while( (char*)v < blockEnd )
{
void* data = (v+1);
if( v->refN > 0 )
{
unsigned* val = static_cast<unsigned *>(data);
var_t* var = static_cast<var_t*>(data) - 1;
printf("%i %i bN:%i ref:%li 0x%p\n",i,*val, var->byteN, var->refN, val);
i += 1;
}
v = (var_t*)( static_cast<char*>(data) + v->byteN);
}
}
}
cw::rc_t cw::nbmem::test()
{
unsigned preallocMemN = 64;
unsigned N = 64;
void* varA[ N ];
handle_t h;
rc_t rc;
// create a nbmem mgr object
if((rc = create( h, preallocMemN )) == kOkRC )
{
// allocate N blocks of memory of length sizeof(unsigned)
for(unsigned i=0; i<N; ++i)
{
unsigned* ip;
if((ip = (unsigned*)alloc(h,sizeof(unsigned))) != nullptr )
{
*ip = i; // set the data value
varA[i] = ip; // keep a ptr to the data block for eventual release
printf("%i alloc assign\n",i);
}
else
{
printf("alloc failed\n");
}
}
// report the state of the nbmem mgr and data values
report( h );
// free each of the memory blocks which were allocate above
for(unsigned i=0; i<N; ++i)
release(h,varA[i]);
destroy( h );
}
return rc;
}
namespace cw
{
namespace nbmem
{
struct text_ctx_str;
typedef struct nbm_thread_str
{
struct test_ctx_str* ctx;
void** varA;
unsigned varN;
thread::handle_t threadH;
} nbm_thread_t;
typedef struct test_ctx_str
{
nbm_thread_t* threadA;
unsigned threadN;
handle_t nbmH;
} test_ctx_t;
//
bool _test_thread_func( void* arg )
{
nbm_thread_t* r = static_cast<nbm_thread_t*>(arg);
// select a random variable index
unsigned idx = lround((double)rand() * (r->varN-1) / RAND_MAX);
cwAssert( 0 <= idx && idx < r->varN );
// if the variable has not been allocated then allocate it ...
if( r->varA[idx] == nullptr )
{
r->varA[idx] = alloc( r->ctx->nbmH, 10 );
}
else // ... otherwise free it
{
release( r->ctx->nbmH, r->varA[idx] );
r->varA[idx] = nullptr;
}
return true;
}
}
}
cw::rc_t cw::nbmem::test_multi_threaded()
{
unsigned preallocMemN = 1024;
unsigned threadN = 10;
unsigned threadVarN = 20;
rc_t rc = kOkRC;
test_ctx_t ctx;
ctx.threadN = threadN;
ctx.threadA = mem::allocZ<nbm_thread_t>(ctx.threadN);
if((rc = create( ctx.nbmH, preallocMemN )) != kOkRC )
goto errLabel;
// create ctx.threadN threads
for(unsigned i=0; i<ctx.threadN; ++i)
{
ctx.threadA[i].ctx = &ctx;
ctx.threadA[i].varA = mem::allocZ<void*>(threadVarN);
ctx.threadA[i].varN = threadVarN;
if((rc = thread::create(ctx.threadA[i].threadH, _test_thread_func, ctx.threadA + i, "nb_mem" )) != kOkRC )
2020-01-27 22:51:56 +00:00
break;
}
if( rc == kOkRC )
{
// start each thread
for(unsigned i=0; i<ctx.threadN; ++i)
thread::unpause(ctx.threadA[i].threadH);
char c;
while( c != 'q' )
{
c = (char)fgetc(stdin);
fflush(stdin);
switch( c )
{
case 'q':
break;
}
}
}
errLabel:
// destroy each thread
for(unsigned i=0; i<ctx.threadN; ++i)
if( ctx.threadA[i].threadH.isValid() )
thread::destroy( ctx.threadA[i].threadH );
mem::release(ctx.threadA);
return rc;
}