Chapter 17
Database Scanning

 17.1 Overview
 17.2 Scan Related Database Fields
  17.2.1 SCAN
  17.2.2 PHAS - Scan Phase
  17.2.3 EVNT - Named or Numbered Events
  17.2.4 PRIO - Scheduling Priority
 17.3 Scan Related Software Components
  17.3.1 menuScan.dbd
  17.3.2 dbScan.h
  17.3.3 Initializing And Controlling Database Scaning
  17.3.4 Adding And Deleting Records From Scan List
  17.3.5 Obtaining the scan period from the SCAN field
  17.3.6 Declaring and Triggering Database Events
  17.3.7 Interfacing to I/O Event Scanning
 17.4 Implementation Overview
  17.4.1 Definitions And Routines Common To All Scan Types
  17.4.2 Database Event Scanning
  17.4.3 I/O Event Scanning
  17.4.4 Periodic Scanning
  17.4.5 Scan Once

17.1 Overview

Database scanning is the mechanism for deciding when to process a record. Five types of scanning are possible:

This chapter explains database scanning in increasing order of detail. It first explains database fields involved with scanning. It next discusses the interface to the scanning system. The last section gives a brief overview of how the scanners are implemented.

17.2 Scan Related Database Fields

The following fields are normally set from within a database configuration tool. It is quite permissible however to change any of these scan-related fields of a record dynamically. For example, a display manager screen could tie a menu control to the SCAN field of a record and allow the operator to dynamically change the scan mechanism.

17.2.1 SCAN

This field, which specifies the scan mechanism, has an associated menu with the following choices:

Passive - Passively scanned.
Event - Event Scanned. The field EVNT specifies the event name or number.
I/O Intr - I/O Event scanned.
10 Second - Periodically scanned every 10 seconds
...
.1 Second - Periodically scanned every .1 seconds

17.2.2 PHAS - Scan Phase

This 16-bit integer field determines relative processing order for records that are in the same scan set. For example all records periodically scanned at a 2 second rate belong to the same scan set. All Event scanned records with the same EVNT belong to the same scan set, etc. For records in the same scan set, all records with PHAS=0 are processed before records with PHAS=1, which are processed before all records with PHAS=2, etc.

In general it is not a good idea to rely on PHAS to enforce processing order. It is better to use database links.

17.2.3 EVNT - Named or Numbered Events

This field is only used when SCAN is set to Event, when EVNT specifies the associated database event name or number. For named events the EVNT field should be set to the event name. Event names are compared using strcmp(), so case and leading/trailing spaces must all match. To use the numeric event trigger routine post_event() the EVNT field must hold an integer in the range 1...255.

17.2.4 PRIO - Scheduling Priority

This field can be used by any software component that needs to specify a scheduling priority. The Event and I/O event scan types use this field.

17.3 Scan Related Software Components

17.3.1 menuScan.dbd

This file holds the definition of the menu used by the field SCAN. The default definition is:

  menu(menuScan) {
      choice(menuScanPassive,"Passive")
      choice(menuScanEvent,"Event")
      choice(menuScanI_O_Intr,"I/O Intr")
      choice(menuScan10_second,"10 second")
      choice(menuScan5_second,"5 second")
      choice(menuScan2_second,"2 second")
      choice(menuScan1_second,"1 second")
      choice(menuScan_5_second,".5 second")
      choice(menuScan_2_second,".2 second")
      choice(menuScan_1_second,".1 second")
  }

The first three choices must appear in the order and location shown. The remaining definitions are for the periodic scan rates, which should appear in the order slowest to fastest (the order directly controls the thread priority assigned to the particular scan rate, and faster scan rates should be assigned higher thread priorities). At IOC initialization, the menu choice strings are read while the scan system is being initialized. The number of periodic scan rates and the period of each rate is determined from the menu choice strings. Thus periodic scan rates can be changed by copying menuScan.dbd into the IOC’s build directory and modifying the set of choices defined therein. The choice names such as menuScan10_second are not used in this case, but must still be unique. Each periodic choice string must begin with a number and be followed by any of the following unit strings:

second or seconds
minute or minutes
hour or hours
Hz or Hertz

17.3.2 dbScan.h

All software components that interact with the scanning system must include this file.

The most important definitions in this file are:

#define SCAN_PASSIVE       menuScanPassive 
#define SCAN_EVENT         menuScanEvent 
#define SCAN_IO_EVENT      menuScanI_O_Intr 
#define SCAN_1ST_PERIODIC  (menuScanI_O_Intr + 1) 
 
typedef struct ioscan_head IOSCANPVT; 
typedef struct event_list EVENTPVT; 
typedef void (⋆io_scan_complete)(void usr, IOSCANPVT, int prio); 
typedef void (⋆once_complete)(void usr, struct dbCommon⋆); 
 
long scanInit(void); 
void scanRun(void); 
void scanPause(void); 
void scanShutdown(void); 
 
EVENTPVT eventNameToHandle(const char event); 
void postEvent(EVENTPVT epvt) EPICS_DEPRECATED; 
void post_event(int event); 
void scanAdd(struct dbCommon ⋆); 
void scanDelete(struct dbCommon ⋆); 
double scanPeriod(int scan); 
void scanOnce(struct dbCommon precord); 
int scanOnceCallback(struct dbCommon ⋆, once_complete cb, void usr); 
int scanOnceSetQueueSize(int size); 
 
/⋆print periodic lists⋆/ 
epicsShareFunc int scanppl(double rate); 
 
/⋆print event lists⋆/ 
epicsShareFunc int scanpel(const char event_name); 
 
/⋆print io_event list⋆/ 
epicsShareFunc int scanpiol(void); 
 
void scanIoInit(IOSCANPVT ppios); 
unsigned int scanIoImmediate(IOSCANPVT pios, int prio); 
unsigned int scanIoRequest(IOSCANPVT pios); 
void scanIoSetComplete(IOSCANPVT, io_scan_complete, void usr);

The first set of definitions defines the various scan types. The typedefs are used when interfacing with the routines below. The remaining definitions declare the public scan access routines. These are described in the following subsections.

17.3.3 Initializing And Controlling Database Scaning

long scanInit(void);

The routine scanInit is called by iocInit. It initializes the scanning system.

void scanRun(void); 
void scanPause(void); 
void scanShutdown(void);

These routines start, pause and stop all the scan tasks respectively. They are used by the iocInit, iocRun, iocPause and iocShutdown commands.

17.3.4 Adding And Deleting Records From Scan List

The following routines are called each time a record is added to or deleted from a scan list.

scanAdd(struct dbCommon ⋆); 
scanDelete(struct dbCommon ⋆);

These routines are called by scanInit at IOC initialization time in order to enter all records into the correct scan list. The routine dbPut calls scanDelete and scanAdd each time a scan-related field is changed (scan-related fields are declared to be SPC_SCAN in dbCommon.dbd). scanDelete is called before the field is modified and scanAdd after the field is modified.

17.3.5 Obtaining the scan period from the SCAN field

double scanPeriod(int scan);

The argument is the index into the set of enum choices from menuScan.dbd. Most users will pick the value from the SCAN field of a database record. The routine returns the scan period in seconds. The result will be 0.0 if scan doesn’t refer to a periodic scan rate.

17.3.6 Declaring and Triggering Database Events

Any software component may declare and subsequently trigger a database event. Database events used to be numbered with 8-bit integers and did not have to be declared in advance. Since Base 3.15 though events can now be named, in which case they must be declared to convert the name into an event object.

EVENTPVT eventNameToHandle(const char event);

This routine must be called from task context (i.e. not from an interrupt service routine) to convert an event’s name into an associated EVENTPVT handle. The first time each name is seen a handle will be created for it; subsequent calls to eventNameToHandle with the same name will return the same handle.

A database event is triggered by calling one of:

void postEvent(EVENTPVT eventObj); 
void post_event(int eventNum) EPICS_DEPRECATED;

The original integer post_event routine is now deprecated in favor of the new routine postEvent that takes an event handle instead of the event number. These event-posting routines may be called by virtually any IOC software component, including from an interrupt service routine on VxWorks or RTEMS. For example sequence programs can call them. The record support module for the eventRecord calls postEvent.

17.3.7 Interfacing to I/O Event Scanning

Interfacing to the I/O event scanner is done via some combination of device and driver support.

  1. Include dbScan.h
  2. For each separate I/O event source the following must be done:

    1. Declare an IOSCANPVT variable, e.g.
      static IOSCANPVT ioscanpvt;
    2. Call scanIoInit during initialization, e.g.
      scanIoInit(&ioscanpvt);
  3. Provide the device support get_ioint_info routine. This routine has the prototype:
    long get_ioint_info(int cmd, struct dbCommon precord, 
        IOSCANPVT ppvt);

    This routine will be called each time the record pointed to by precord is added to or deleted from an I/O Event scan list. The cmd argument will be zero if the record is being added to an I/O event list, 1 if it is being deleted from the list. This routine must set ⋆ppvt to the IOSCANPVT variable associated with this record.

  4. Whenever an I/O event is detected, the device software must call scanIoRequest or scanIoImmediate, e.g.
    scanIoRequest(ioscanpvt); 
    scanIoImmediate(ioscanpvt, priorityLow);

    The routine scanIoRequest() may be safely called from interrupt level. A request is queued and will be handled by one of the standard callback threads. There are three sets of callback threads fed from three queues, one for each priority level (see section 16.2); the PRIO field of a record determines which queue will be used for processing this record after scanIoRequest() has been called.

    The routine scanIoImmediate() may not be called from interrupt level. Instead of queuing a request, this routine directly processes records on the current thread. Unlike scanIoRequest, this routine only scans records with the given priority level. It must therefore be called three times, once for each priority level.

    scanIoRequest() returns a bit pattern indicating which priority queues the request was added to. A return value of zero means that no records are currently configured to use this interrupt source for I/O Interrupt scanning.

  5. Device or driver support that needs to implement flow control can set up a completion callback by calling scanIoSetComplete, e.g.
    static void myCallback(void arg, IOSCANPVT pvt, int prio) { 
        ... 
    } 
     
    scanIoSetComplete(ioscanpvt, myCallback, (void ⋆)arg);

    The completion callback will be run from one of the callback threads, once per priority actually used (bits set in the return value of scanIoRequest), after the list of records with that priority level has been processed. Note that for records with asynchronous device support, record processing might not have completed when the callback is run.

The following code fragment shows an event record device support module that supports I/O event scanning:

#include  <vxWorks.h> 
#include  <types.h> 
#include  <stdioLib.h> 
#include  <intLib.h> 
#include  <dbDefs.h> 
#include  <dbAccess.h> 
#include  <dbScan.h> 
#include  <recSup.h> 
#include  <devSup.h> 
#include  <eventRecord.h> 
/⋆ Create the dset for devEventXXX ⋆/ 
long init(); 
long get_ioint_info(); 
struct { 
    long  number; 
    DEVSUPFUN  report; 
    DEVSUPFUN  init; 
    DEVSUPFUN  init_record; 
    DEVSUPFUN  get_ioint_info; 
    DEVSUPFUN  read_event; 
}devEventTestIoEvent={ 
    5, 
    NULL, 
    init, 
    NULL, 
    get_ioint_info, 
    NULL}; 
static IOSCANPVT ioscanpvt; 
static void int_service(IOSCANPVT ioscanpvt) 
{ 
    scanIoRequest(ioscanpvt); 
} 
 
static long init() 
{ 
    scanIoInit(&ioscanpvt); 
    intConnect(<vector>,(FUNCPTR)int_service,ioscanpvt); 
    return(0); 
} 
static long get_ioint_info( 
int   cmd, 
struct eventRecord   pr, 
IOSCANPVT   ppvt) 
{ 
    ppvt = ioscanpvt; 
    return(0); 
}

17.4 Implementation Overview

The code for the entire scanning system resides in dbScan.c. This section gives an overview of how this code is organized.

17.4.1 Definitions And Routines Common To All Scan Types

Everything is built around two basic structures:

typedef struct scan_list { 
    epicsMutexId lock; 
    ELLLIST      list; 
    short        modified; 
}; 
 
typedef struct scan_element{ 
    ELLNODE         node; 
    scan_list       pscan_list; 
    struct dbCommon precord; 
}

Each scan_list.list is the head of a list of scan_element nodes pointing to records that all belong to the same scan set. For example, all records that are periodically scanned at the 1 second rate are in the same scan set. The libCom ellLib routines are used to access the list. The scan_element.node field contains the next and previous links. Each record that appears in a scan_list has an associated scan_element. The SPVT field which appears in dbCommon points to the associated scan_element.

The lock, modified, and pscan_list fields allow scan_elements, i.e. records, to be dynamically removed and added to scan lists. If scanList, the routine which actually processes a scan list, is studied it can be seen that these fields allow the list to be scanned very efficiently when no modifications are made to the list while it is being scanned. This is, of course, the normal case.

The dbScan.c module contains several private routines. The following access a single scan set:

17.4.2 Database Event Scanning

Event scanning is built around the following definitions:

typedef struct event_list { 
    CALLBACK           callback[NUM_CALLBACK_PRIORITIES]; 
    scan_list          scan_list[NUM_CALLBACK_PRIORITIES]; 
    struct event_list next; 
    char               event_name[MAX_STRING_SIZE]; 
} event_list; 
 
static event_list  volatile pevent_list[256]; 
static epicsMutexId event_lock;

Event scanning uses the general purpose callback tasks to perform record processing, i.e. no extra threads are spawned for this. When a named event is declared by a call to eventNameToHandle() an event_list will be created for that named event. Every event_list contains a scan_list for each of the 3 priorities. The next member is used to keep a singly-linked list of all the event_list objects, with the first item on that list pointed to by pevent_list[0]. pevent_list is an array of pointers to numbered event_list objects, and is used when an event name is an integer in the range 1..255. It provides fast access to 255 numbered events, i.e. one for each possible numeric database event.

17.4.2.1 postEvent
void postEvent(event_list pel);

This routine is called to request an event scan for a named event handle. It may be called from interrupt level. It looks at each scan_list in the event_list (one for each callback priority) and if any nodes are present in the list it makes a callbackRequest to process that set of records. The appropriate callback task calls routine eventCallback, which just calls scanList.

17.4.2.2 post_event
void post_event(int eventNum) EPICS_DEPRECATED;

This routine is called to request an event scan for a numbered event. It may be called from interrupt level. It looks up the event_list indicated by the given event number and calls postEvent with that handle.

17.4.3 I/O Event Scanning

I/O event scanning is built around the following definitions:

typedef struct io_scan_list { 
    CALLBACK callback; 
    scan_list scan_list; 
} io_scan_list; 
 
typedef struct ioscan_head { 
    struct ioscan_head next; 
    struct io_scan_list iosl[NUM_CALLBACK_PRIORITIES]; 
    io_scan_complete cb; 
    void arg; 
} ioscan_head; 
 
static ioscan_head pioscan_list = NULL; 
static epicsMutexId ioscan_lock;

I/O event scanning uses the general purpose callback tasks to perform record processing, i.e. no extra threads are spawned for this. The callback field of io_scan_list is used to communicate with the callback tasks.

The following routines implement I/O event scanning:

17.4.3.1 scanIoInit
void scanIoInit(IOSCANPVT ppios)

This routine is called by device or driver support. It must be called once for each interrupt source. scanIoInit allocates and initializes an ioscan_head object which contains an io_scan_list for each callback priority. It puts the address of the allocated object in ppios.

When scanAdd or scanDelete are called, they call the device support routine get_ioint_info which returns ppios. The scan_element is then added to or deleted from the correct scan list.

17.4.3.2 scanIoRequest
unsigned int scanIoRequest(IOSCANPVT pios)

This routine is called by device or driver support to request a specific I/O event scan. It may be called from interrupt level. It looks at each io_scan_list referenced by pios (one for each callback priority) and if any elements are present in the scan_list a callbackRequest is issued. The appropriate callback task calls routine ioscanCallback, which calls scanList followed by any completion callback that was registered with pios.

The scan_element is then added to or deleted from the correct scan list.

17.4.3.3 scanIoImmediate
unsigned int scanIoImmediate(IOSCANPVT pios, int prio);

Scans any records in the given IOSCANPVT with the given priority. Record processing is done using the current thread. This is intended to allow device or driver support to implement private scanning threads. However links in these records may result in other records also being processed using the same thread.

Such device or driver support should call scanIoImmediate for all priority levels. For maximum throughput these calls can be made concurrently.

17.4.4 Periodic Scanning

Periodic scanning is built around the following definitions:

typedef struct periodic_scan_list { 
    scan_list           scan_list; 
    double              period; 
    const char          name; 
    unsigned long       overruns; 
    volatile enum ctl   scanCtl; 
    epicsEventId        loopEvent; 
} periodic_scan_list; 
 
static int nPeriodic; 
static periodic_scan_list ⋆⋆papPeriodic; 
static epicsThreadId periodicTaskId;

The nPeriodic variable holds the number of periodic scan rates configured. papPeriodic points to an array of pointers to periodic_scan_lists. There is an array element for each scan rate. A periodic scan task is created for each scan rate.

The following routines implement periodic scanning:

17.4.4.1 initPeriodic
void initPeriodic(void);

This routine first determines how many periodic scan rates are to be created from the definition of the menuScan menu. The array of pointers referenced by papPeriodic is allocated. For menu choice a periodic_scan_list is allocated and initialized. It parses the choice string for that choice to obtain the scan period for the scan.

17.4.4.2 periodicTask
periodicTask (struct scan_list psl);

In outline this task runs an infinite loop, calling scanList and then waiting until the start of the next scan interval, allowing for the time it took to scan the list. If a periodic scan list takes longer to process than its defined scan period, its next scan will be delayed by half a scan period, with a maximum of 1 second delay. This does not limit what scan rates can actually be implemented, as long as all the records in the list can be processed within the requested period. Persistent over-runs (more than 10 times in a row) will result in a warning message being logged. The total number of over-runs is counted by each scan thread and can be displayed using the scanppl command.

17.4.5 Scan Once

17.4.5.1 scanOnce
typedef void (⋆once_complete)(void usr, struct dbCommon⋆); 
int scanOnce (dbCommon precord); 
int scanOnceCallback(struct dbCommon ⋆, once_complete cb, void usr)

A task onceTask waits for requests to issue a dbProcess request. The routine scanOnce puts the address of the record to be processed in a ring buffer and wakes up onceTask.

This routine may be called from interrupt level.

The scanOnceCallback variant also takes a callback function and user pointer; the completion function is invoked from the onceTask after the record has been processed.

These functions return zero when a request is successfull queued, and a non-zero error code if a request can’t be queued.

17.4.5.2 SetQueueSize

scanOnce places its requests into a ring buffer. This is set by default to be 1000 entries long. The size can be changed by executing the following command in the startup script before iocInit:

int scanOnceSetQueueSize(int size);