EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: memory management in waveformRecord
From: Michael Davidsaver <[email protected]>
To: "[email protected]" <[email protected]>
Date: Thu, 24 Jan 2013 18:58:19 -0500
First a question:

Does anyone know of any code which manually post monitors for the VAL field of a waveformRecord (or aai and aao)? Something like "db_post_events(prec, prec->bptr, DBE_VALUE)". Such code would be broken (become a no-op) by the change I am proposing for Base 3.15.

https://code.launchpad.net/~epics-core/epics-base/array-opt


This change would make it possible to safely replace the array buffer pointer for the waveform, aai, and aao recordtypes. This is effectively generalizing the stratagy used by the sscanRecord (and perhaps others) and making it easier to use.

http://www.aps.anl.gov/epics/tech-talk/2013/msg00037.php

Please consider the following as a draft of the documentation describing how to use this feature. Suggestions or additions and clarifications are welcomed.


The basic rules are:

1) BPTR and the memory it is currently pointing to can only be accessed while the record is locked. 2) BPTR must always point to a piece of memory large enough to accommodate the maximum number of elements.

#1 means that it is only save to read, write, or de-reference the BPTR field from a device support function, or after manually calling dbScanLock(). #2 means that BPTR can never be set to NULL, and when replacing BPTR, the replacement must be allocated large enough for the worst case.

It is feasible to use this feature to avoid one array copy when moving array data into, or out of one of these recordtypes.

Note: The following examples make inefficient use of malloc() and free(). This is done to make clear where new memory appears. In reality a free list should be used. Also, since free() is used there no need for special handling of the initial buffer allocated by the waveformRecord, in other cases care must be taken to cleanup and replace it in init_record().

For example, a waveformRecord device support for a digitizer might look like

struct devicePvt {
  epicsMutexId lock;
  void *nextBuffer;
  IOSCANPVT scan;
  epicsUInt32 maxbytes, numbytes;
};

We create a structure to communicate between a worker thread and our device support function. This structure must be guarded with a Mutex. We assume that member 'maxbytes' is initialized to "dbValueSize(prec->ftvl)*prec->nelm" in the init_record() device support function, and never changed. If is also assumed that init_record() checks that maxbytes >= 1024.

static void internalThread(void *raw) {
  struct devicePvt *pvt=raw;
  while(1) {
    int wake = 1;
    void * temp=mallocMustSucceed(pvt->maxbytes);
fillFromDevice(temp); /* copy data from device to temp buffer (eg. DMA) */
    epicsMutexMustLock(pvt->lock);
    if(pvt->nextBuffer) {
      free(pvt->nextBuffer);
      wake = 0;
    }
    pvt->nextBuffer = temp;
    pvt->numbytes = 1024;
    epicsMutexUnlock(pvt->lock);
    if(wake)
      scanIoRequest(pvt->scan);
}


The worker thread fills an array with a fictional function named fillFromDevice() which will write all 1024 bytes of the temporary buffer. The protocol for communicating with device support to place the temporary pointer into the shared structure, after checking to see if the previous pointer was removed. If the device support did consume the previous value, then a 'I/O Intr' scan request is queued to wake it up again.

static void read_wf(waveformRecord *prec) {
  struct devicePvt *pvt=prec->dpvt;
  epicsUInt32 nbytes;
  void *buf;
  epicsMutexMustLock(pvt->lock);
  buf = pvt->nextBuffer;
  pvt->nextBuffer = NULL;
  nbytes = pvt->numbytes;
  epicsMutexUnlock(pvt->lock);
  if(buf) {
    if(prec->bptr)
      free(prec->bptr);
    prec->bptr = buf;
    prec->nord = nbytes/dbValueSize(prec->ftvl);
  }
  return 0;
}

All that the device support does is to cleanup, and replace, the pointer in BPTR. Remember to write NORD each time to handle changing array size, and to protect against someone issuing a caput (waveform VAL field is writable).



It is also possible to move data in the other direction, from a record into a driver.

struct devicePvt {
  epicsMutexId lock;
  void *nextBuffer;
  epicsEventId wakeup;
  epicsUInt32 numbytes;
  SOCKET sd;
};

Here we have a similar shared structure with the IOSCAN replaced with an Event which will wakeup up the worker. Here we imagine that our device is a socket.

static void write_wf(waveformRecord *prec) {
  struct devicePvt *pvt=prec->dpvt;
  void *temp = mallocMustSucceed(dbValueSize(prec->ftvl)*prec->nelm);
  epicsMutexMustLock(pvt->lock);
  if(!pvt->nextBuffer) {
    pvt->nextBuffer = prec->bptr;
    prec->bptr = temp;
    temp = NULL;
    pvt->numbytes = prec->nord*dbValueSize(prec->ftvl);
    prec->nord  = 0;
  } else
    free(temp);
  epicsMutexUnlock(pvt->lock);
  epicsEventMustTrigger(pvt->wakeup);
  return 0;
}

If the previous buffer has been consumed, the device support write function will move BPTR to the shared structure and replace it with a pointer to newly allocated (uninitialized) memory. The fact that BPTR now points to uninitialized memory is acceptable because NORD is set to 0.

static void internalThread(void *raw) {
  struct devicePvt *pvt=raw;
  while(1) {
    int ret;
    void *temp;
    epicsUInt32 count;
    epicsEventMustWait(pvt->wakeup);
    epicsMutexMustLock(pvt->lock);
    temp = pvt->nextBuffer;
    pvt->nextBuffer = NULL;
    count = pvt->numbytes;
    epicsMutexUnlock(pvt->lock);
    if(!temp)
      continue;
    ret = send(pvt->sd, temp, count);
    assert(ret==count);
    free(temp);
  }
}

The worker takes a pointer from the shared structure and passes it to the send() syscall.



Navigate by Date:
Prev: RTEMS driver for CAEN V6533N Hu, Yong
Next: copying archived data from 32-bit to 64-bit machine Pierrick Hanlet
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
Navigate by Thread:
Prev: RTEMS driver for CAEN V6533N Hu, Yong
Next: copying archived data from 32-bit to 64-bit machine Pierrick Hanlet
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  <20132014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
ANJ, 20 Apr 2015 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·