Table of Contents Previous Chapter Chapter 6: Runtime Database Access

Chapter 6: Runtime Database Access

1. Overview

This chapter describes routines for manipulating and accessing an initialized IOC database.

This chapter is divided into the following sections:

2. Database Include Files

Directory epics/share/epicsH contains a number of database related include files. A complete list is:

dbDefs.h, dbFldTypes.h, and dbBase.h are discussed in this section. dbAccess.h is discussed with runtime database access. The other include files are of interest to someone needing details about the database structures. See last chapter for details.

Fundamental Database Definitions

dbDefs.h

This file contains a number of database related definitions. The most important are:

dbFldTypes.h

This file defines the possible field types. A field's type is perhaps its most important attribute. Changing the possible field types is a fundamental change to the IOC software, because many IOC software components are aware of the field types.

The field types are:

A field of type DBF_STRING, ..., DBF_DOUBLE can be a scalar or an array. A DBF_STRING field contains a NULL terminated ascii string. The field types DBF_CHAR, ..., DBF_DOUBLE correspond to the standard C data types.

DBF_ENUM is used for enumerated items, which is analogous to the C language enumeration. An example of an enum field is field VAL of a multi bit binary record.

The field types DBF_ENUM, ..., DBF_DEVCHOICE all have an associated set of ASCII strings defining the choices. For a DBF_ENUM, the record support module supplies values and thus are not available for static database access. The database access routines locate the choice strings for the other types.

DBF_INLINK and DBF_OUTLINK specify link fields. A link field can refer to a signal located in a hardware module, to a field located in a database record in the same IOC, or to a field located in a record in another IOC. A DBF_FWDLINK can only refer to a record in the same IOC. Link fields are described in a later chapter.

DBF_INLINK (input), DBF_OUTLINK (output), and DBF_FWDLINK (forward) specify that the field is a link structure as defined in link.h. There are three classes of links:

  1. Constant - The value associated with the field is a floating point value initialized with a constant value. This is somewhat of a misnomer because constant link fields can be modified via dbPutField or dbPutLink.
  2. Hardware links - The link contains a data structure which describes a signal connected to a particular hardware bus. See link.h for a description of the bus types currently supported.
  3. Process Variable Links - This is one of three types:
    1. PV_LINK: The process variable name.
    2. DB_LINK: A reference to a process variable in the same IOC.
    3. CA_LINK: A reference to a variable located in another IOC.
DCT always creates a PV_LINK. When the IOC is initialized each PV_LINK is converted either to a DB_LINK or a CA_LINK.

DBF_NOACCESS fields are for private use by record processing routines.

dbBase.h

The database and all its associated structures are located via the following set of variables defined in structure dbBase (defined in dbBase.h):

 struct dbBase {
  struct choiceSet  *pchoiceCvt;
  struct arrChoiceSet  *pchoiceGbl;
  struct choiceRec  *pchoiceRec;
  struct devChoiceRec  *pchoiceDev;
  struct arrBrkTable  *pcvtTable;
  struct recDes  *precDes;
  struct recType  *precType;
  struct recHeader  *precHeader;
  struct recDevSup  *precDevSup;
  struct drvSup  *pdrvSup;
  struct recSup *precSup;
  struct pvd  *pdbPvd; /* DCT pvd - remove when DCT goes away */
  void  *ppvd;/* pointer to process variable directory*/
  char  *pdbName;/* pointer to database name*/
  struct sdrSum  *psdrSum;/* pointer to default sum */
  long  sdrFileSize; /*size of default.dctsdr file*/
  long  pvtSumFlag; /*internal use only*/
};

3. Runtime Database Access

With the exception of record and device support, all access to the database is via the channel or database access routines. Even record support routines access other records only via database or channel access. Channel Access, in turn, accesses the database via database access.

Perhaps the easiest way to describe the database access layer is to list and briefly describe the set of routines that constitute database access. This provides a good look at the facilities provided by the database. It may seem strange that the structure of the IOC database is not described at this point but this would result in confusing detail. The structure is explained in the last chapter.

Before describing database access, one caution must be mentioned. The only way to communicate with an IOC database from outside the IOC is via Channel Access. In addition, any special purpose software, i.e. any software not described in this document, should communicate with the database via Channel Access, not database access, even if it resides in the same IOC as the database. Since Channel Access provides network independent access to a database, it must ultimately call database access routines. The database access interface was changed in 1991, but Channel Access was never changed. Instead a module was written which translates old style database access calls to new. This interface between the old and new style database access calls is discussed in the last section of this chapter.

The database access routines are:

Database Request Types and Options

Before describing database access structures, it is necessary to describe database request types and request options. When dbPutField or dbGetField are called one of the arguments is a database request type. This argument has one of the following values:

The request types DBR_STRING, ..., DBR_DOUBLE correspond exactly to valid data types for database fields. DBR_ENUM corresponds to database fields that represent a set of choices or options. In particular it corresponds to the fields types DBF_ENUM, DBF_DEVCHOICE, DBF_CVTCHOICE, DBF_GBLCHOICE, and DBF_RECCHOICE. The complete set of database field types are defined in dbFldTypes.h.

dbGetField also accepts argument options which is a mask containing a bit for each additional type of information the caller desires. The complete set of options is:

dbAccess.h

Before describing the routines a few data structures must be described. The structures are defined in dbAccess.h. The first structure is dbAddr.

struct dbAddr{
struct dbCommon *precord: /* address of record */ void *pfield; /* address of field */ void *pfldDes; /* address of struct fldDes */ long no_elements; /* number of elements (arrays) */ short record_type; /* type of record being accessed */ short field_type; /* type of database field */ short field_size; /* size (bytes) of the field being accessed */ short special; /* special processing */ short choice_set; /* index of choiceSet GBLCHOICE & RECCHOICE*/ short dbr_field_type} /* field type as seen by database request DBR_STRING, ..., DBR_ENUM, DBR_NOACCESS*/
The file dbAccess.h contains macros for using options. A brief example should show how they are used. The following example defines a buffer to accept an array of up to ten float values. In addition it contains fields for options DBR_STATUS and DBR_TIME.

  struct buffer {
    DBRstatus
    DBRtime
    float  value[10];
  } buffer;
The associated dbGetField call is:

  long options,number_elements,status;
      ...
  options = DBR_STATUS | DBR_TIME
  number_elements = 10;
  status = dbGetField(paddr,DBR_FLOAT,&buffer,&options,&number_elements);
Consult dbAccess.h for a complete list of macros.

Structure dbAddr contains a field dbr_field_type. This field is the database request type that most closely matches the database field type. Using this request type will put the smallest load on the IOC.

Channel Access provides routines similar to dbGetField, and dbPutField. It provides remote access to dbGetField, dbPutField, and to the database monitors described below.

Database Access Routines

The most important goal of database access can be stated simply: Provide quick access to database records and fields within records. The basic rules are:

The routines described in this subsection are used by channel access, sequence programs, etc. Record processing routines, however, use the routines described in the next section rather then dbGetField and dbPutField.

dbNameToAddr

Locate a process variable, format:

  dbNameToAddr(
    char  *pname, /*ptr to process variable name */
    struct dbAddr  *paddr);
Given a process variable name, this routine locates the process variable and fills in the fields of structure dbAddr. The process variable name is of the form "<record_name>.<field_name>". For example the value field of a record with record name `sample_name" is "sample_name.VAL". Note that the name is case sensitive. All field names are all upper case letters.

dbNameToAddr locates a record via a process variable directory (PVD). It fills in a structure (dbAddr) describing the field. dbAddr contains the address of the record and also the field. Thus other routines can locate the record and field without a search. Although the PVD allows the record to be located via a hash algorithm and the field within a record via a binary search, it still takes about 80 microseconds (25MHz 68040) to located a process variable. Once located the dbAddr structure allows the process variable to be accessed directly.

dbCommonInit

This routine is called by iocInit to initialize fields in database common. It is only included for completeness.

dbGetField

Get values associated with a process variable, format:

  dbGetField(
    struct dbAddr  *paddr,
    short  dbrType,  /* DBR_xxx */
    void   *pbuffer,  /*addr of returned data */
    long   *options,  /*addr of options */
    long   *nRequest,  /*addr of number of elements */
    void   *pfl);  /*used by monitor routines */
Thus routine locks, calls dbGet, and unlocks.

dbGetLink

Get value from the field referenced by a database link, format:

dbGetLink(
    struct db_link  *pdbLink,  /*addr of database link  */
    struct dbCommon  *pdest,    /*addr of destination record  */
    short    dbrType,        /* DBR_xxx      */
    void    *pbuffer,        /*addr of returned data  */
    long    *options,        /*addr of options    */
    long    *nRequest);  /*addr of number of elements desired*/
This routine is called by database access itself and by record support and/or device support routines in order to get values from other database records via input links. It calls dbGet to obtain data and also implements the process passive and maximize severity link options.

dbFastLinkGet

  dbFastLinkGet(
    struct      link    *plink,
    struct      dbCommon  *precord,
    void      *pdest);
This routine gets a value from an input link to pdest. Do not call this routine unless you have a properly initialized Channel Access or database link. This routine is not intended to be called directly by record support, use recGblGetFastLink() instead.

dbGet

Get values associated with a process variable, format:

  dbGet(
    struct dbAddr      *paddr,
    short    dbrType,      /* DBR_xxx      */
    void    *pbuffer,      /*addr of returned data  
    long    *options,      /*addr of options    */
    long    *nRequest,  /*addr of number of elements  */
    void    *pfl);      /*used by monitor routines  */
Thus routine retrieves the data referenced by paddr and converts it to the format specified by dbrType.

"options" is a read/write field. Upon entry to dbGet, options specifies the desired options. When dbGetField returns, options specifies the options actually honored. If an option is not honored, the corresponding fields in buffer are filled with zeros.

"nRequest" is also a read/write field. Upon entry to dbGet it specifies the maximum number of data elements the caller is willing to receive. When dbGet returns it equals the actual number of elements returned. It is permissible to request zero elements. This is useful when only option data is desired.

"pfl" is a field used by the Channel Access monitor routines. All other users must set pfl=NULL.

dbGet calls one of a number of conversion routines in order to convert data from the DBF types to the DBR types. It calls record support routines for special cases such as arrays. For example, if the number of field elements is greater then 1 and record support routine get_array_info exists, then it is called. It returns two values: the current number of valid field elements and an offset. The number of valid elements may not match dbAddr.no_elements, which is really the maximum number of elements allowed. The offset is for use by records which implement circular buffers.

dbPutField

Change the value of a process variable, format:

  dbPutField(
    struct    dbAddr   *paddr,
    short    dbrType,      /* DBR_xxx*/
    void    *pbuffer,      /*addr of data  */
    long    nRequest);      /*number of elements to write*/
This routine is responsible for accepting data in one of the DBR_xxx formats, converting it as necessary, and modifying the database. Similar to dbGetField, this routine calls one of a number of conversion routines to do the actual conversion and relies on record support routines to handle arrays and other special cases.

It should be noted that routine dbPut does most of the work. The actual algorithm for dbPutField is:

  1. If the DISP field is TRUE then, unless it is the DISP field itself which is being modified, the field is not written.
  2. The record is locked.
  3. dbPut is called.
  4. If the dbPut is successful then:
    If this is the PROC field or if both of the following are TRUE: 1) the field is a process passive field, 2) the record is passive.
    1. If the record is already active ask for the record to be reprocessed when it completes.
    2. Call dbScanPassive after setting putf TRUE to show the process request came from dbPutField.
  5. The record is unlocked.

dbPutLink

Change the value referenced by a database link, format:

  dbPutLink(
    struct    db_link  *pdbLink,            /*addr of database link  */
    struct    dbCommon  *psource,            /*addr of source record  */ 
    short    dbrType,      /* DBR_xxx*/
    void    *pbuffer,      /*addr of data to write  */
    long    nRequest);  /*number of elements to write*/
This routine is called by database access itself and by record support and/or device support routines in order to put values into other database records via output links. It performs the following functions:

  1. Calls dbPut.
  2. Implements maximize severity.
  3. If the field being referenced is PROC or if both of the following are true: 1) process_passive is TRUE and 2) the record is passive then:
    1. If the record is already active because of a dbPutField request then ask for the record to be reprocessed when it completes.
    2. otherwise call dbScanPassive.

dbFastLinkPut

Fast putLink, forward:

  dbFastLinkPut(
    struct    link    *plink,
    struct    dbCommon  *precord,
    void    *psource);
This routine puts the value from psource to an output link. Do not call this routine unless you have a properly initialized Channel Access or database link. This routine is not intended to be called directly by record support, use recGblPutFastLink() instead.

dbPutNotify

Put And Notify When Complete, format:

  typedef struct putNotify{
    /*The following members MUST be set by user*/
    void      (*userCallback)(struct putNotify *);
    struct dbAddr        *paddr;  /*dbAddr set by dbName  ToAddr*/
    void      *pbuffer;  /*address of data*/
    long      nRequest;  /*number of elements to be written*/
    short      dbrType;        /*database request type*/
    void      *usrPvt;        /*for private use of user*/
    /*The following is status of request. Set by dbPutNotify*/
    long      status;
    /*The following are private to database access*/
    CALLBACK      callback;
    void      *list;  /*list of records for which to wait*/
    int      nwaiting;
    notifyCmd      cmd;
    unsigned char rescan; /*Should dbPutNotify be called again*/
  }PUTNOTIFY;
  long dbPutNotify(PUTNOTIFY *pputnotify);
  void dbNotifyCancel(PUTNOTIFY *pputnotify);
The following routine is used only by old database access.

  int dbPutNotifyMapType(PUTNOTIFY *pputnotify,short dbr_type)
The status value stored in PUTNOTIFY can be one of the following:

dbPutNotify is a request to notify the caller when all records that are processed as a result of the put complete processing. The complication occurs because of asynchronous records. The following is true:

  1. The user supplied callback is called when all processing is complete or when a S_db_Blocked is detected. If everything completes synchronously the callback routine will be called BEFORE dbPutNotify returns. The user supplied callback routine must not issue any calls that block such as Unix I/O requests.
  2. In general a set of records may need to be processed as a result of a single dbPutNotify. If database access detects that another dbPutNotify request has resulted in a record in the set being already active then the user callback is called with status=S_db_Blocked.
  3. If a record in the set is found to be active because of a dbPutField request then when the record completes a new dbPutNotify will be issued.
  4. If a record is found to be active for some other reason then nothing is done. This is what is done now and any attempt to do otherwise could easily cause existing databases to go into an infinite processing loop.
  5. It is expected that the caller will arrange a timeout in case the dbPutNotify takes too long. In this case the caller can call dbNotifyCancel.

dbPut

Put a value to a database field, format:

  dbPut(
    struct dbAddr  *paddr,
    short    dbrType,      /* DBR_xxx*/
    void    *pbuffer,      /*addr of data  */
    long    nRequest);  /*number of elements to write*/
This routine is responsible for accepting data in one of the DBR_xxx formats, converting it as necessary, and modifying the database. Similar to dbGet, this routine calls one of a number of conversion routines to do the actual conversion and relies on record support routines to handle arrays and other special cases.

dbBufferSize

Determine the buffer size for a dbGetField request, format:

  long dbBufferSize(
    short    dbrType,      /* DBR_xxx*/
    long    options,      /* options mask*/
    long    nRequest);    /* number of elements*/
This routine returns the number of bytes that will be returned to dbGetField if the request type, options, and number of elements are specified as given to dbBufferSize. Thus it can be used to allocate storage for buffers.

NOTE: This should become a Channel Access routine

dbValueSize

Determine the size a value field, format:

  dbValueSize(short  dbrType);      /* DBR_xxx*/
This routine returns the number of bytes for each element of type dbrType.

NOTE: This should become a Channel Access routine

dbScanPassive

Process record if it is passive, format:

  dbScanPassive(
    struct dbCommon  *pfrom,
    struct dbCommon  *pto);   /* addr of record*/
This request specifies the record requesting the scan, which may be NULL, and the record to be processed. If the record is passive and pact=FALSE then dbProcess is called. Note that this routine is called by dbGetLink, dbPutField, and by record processing routines for forward links. In addition to calling dbProcess this routine is responsible for creating lists needed for dbPutNotify.

dbProcess

Request that a database record be processed, format:

  dbProcess(struct dbCommom  *precord);  /* addr of record*/
Request that record be processed. Record processing is described in detail below.

Database Locking

Database locking is described in the next chapter. For now lets just state that a database record must be locked before it is modified and unlocked afterwards.

The routines provided are:

  dbScanLockInit(  /* called only by iocInit*/
    int  nset);  /* number of lock sets*/
  dbScanLock(struct dbCommon  *precord);  /*addr of record*/
  dbScanUnlock(struct dbCommon *precord);  /*addr of record*/

Channel Access Database Links

The routines described here are used to create and manipulate Channel Access connections from database input or output links. At IOC initialization an attempt is made to convert all process variable links to database links. For any link that fails, it is assumed that the link is a Channel Access link, i.e. a link to a process variable defined in another IOC. The routines described here are used to manage these links.

The routines provided are:

  dbCaAddInLink(
    struct link  *plink,
    void    *precord,
    char    *pfieldName);
  
  dbCaAddOutLink(
    struct link  *plink,
    void    *precord,
    char    *pfieldName);
  dbCaGetLink(
    struct link  *plink);
  dbCaPutLink(
    struct link  *plink);
For a description of these routines see:

Links in a Distributed database: Theory and Implementation,
Nicholas T. Karonis and Martin R. Kraimer, December 1991

4. Old Database Access Interface

Channel Access has not yet been modified to support the database access routines described above. The database access interface was changed because as more database field types and request options were defined the previous database access interface become harder and harder to modify. In order to make the transition to the new database access without obsoleting all software that used Channel Access an interface module was written. Thus module translates old database calls to new. Several of the Channel Access arguments directly map to database access arguments. Thus existing Channel Access clients use the old database access interface.

Since this manual concentrates on IOC software, this is not the place to describe the old database interface. Other documents describe it. The header file db_access.h also provides descriptive information.

 
Table of Contents Next Chapter