410 lines
9.5 KiB
C++
410 lines
9.5 KiB
C++
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
|
#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 )
|
|
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;
|
|
}
|
|
|
|
|