Database scanning is the mechanism for deciding when to process a record. Five types of scanning are possible:
post_event
.
dbScanPassive
.
This happens when database links (Forward, Input, or Output), which have been declared ``Process Passive" are accessed during record processing.
It can also happen as a result of dbPutField
being called (which normally results from a Channel Access put request).
scanOnce
which arranges for a record to be processed one time.
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.
The following fields are normally defined via DCT.
It should be noted, however, that it is quite permissible to change any of the 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.
This field, which specifies the scan mechanism, has an associated menu of the following form:
EVNT
specifies event number.
This field determines processing order for records that are in the same scan set.
For example all records periodically scanned at a 2 second rate are in the same scan set.
All Event scanned records with the same EVNT
are in 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.
This field only has meaning when SCAN
is set to Event
scanning, in which case it specifies the event number.
In order for a record to be event scanned, EVNT
must be in the range 0,...255.
It should also be noted that some EPICS software components will not request event scanning for event 0.
One example is the eventRecord
record support module.
Thus the application developer will normally want to define events in the range 1,...,255.
This field can be used by any software component that needs to specify scheduling priority, e.g. the event and I/O event scan facility uses this field.
This file contains definitions for a menu related to field SCAN
.
The definitions are of the form:
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 must 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 at scan initialization.
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 changing menuScan.dbd
and loading this version via dbLoadDatabase
.
The only requirement is that each periodic choice string must begin with a numeric value specified in units of seconds.
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) /*definitions for I/O Interrupt Scanning */ typedef struct io_scan_list *IOSCANPVT; long scanInit(void); void scanRun(void); void scanPause(void); void post_event(int event); void scanAdd(struct dbCommon *); void scanDelete(struct dbCommon *); double scanPeriod(int scan); void scanOnce(struct dbCommon *precord); int scanOnceSetQueueSize(int size); int scanppl(void); /* print periodic lists*/ int scanpel(void); /* print event lists*/ int scanpiol(void); /* print io_event list*/ void scanIoInit(IOSCANPVT *); void scanIoRequest(IOSCANPVT);
The first set of definitions defines the various scan types.
The next definition IOSCANPVT
is used when interfacing with the I/O interrupt scanner.
The remaining definitions define the public scan access routines.
These are described in the following subsections.
scanInit(void);
The routine scanInit
is called by iocInit
.
It initializes the scanning system.
scanRun(void); scanPause(void);
These routines start and stop all the scan tasks respectively.
They are used by the iocInit
, iocRun
and iocPause
commands.
The following routines are called each time a record is added 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 created via DCT into the correct scan list.
The routine dbPut
calls scanDelete
and scanAdd
each time a scan related field is changed (each scan related field is declared to be SPC_SCAN
in dbCommon.dbd
).
scanDelete
is called before the field is modified and scanAdd
after the field is modified.
double scanPeriod(int scan);
The argument is an offset into the set of enum choices for menuScan.h. Most users will just use the SCAN field of a database record. It returns the scan period in seconds. The result will be 0.0 if scan doesn't refer to a periodic rate.
Whenever any software component wants to declare a database event, it just calls:
post_event(event)
This can be called by virtually any IOC software component.
For example sequence programs can call it.
The record support module for eventRecord
calls it.
Interfacing to the I/O event scanner is done via some combination of device and driver support.
<dbScan.h>
IOSCANPVT
variable, e.g.
static IOSCANPVT ioscanpvt;
scanIoInit
, e.g.
scanIoInit(&ioscanpvt);
get_ioint_info
routine.
This routine has the format:
long get_ioint_info( int cmd, struct dbCommon *precord, IOSCANPVT *ppvt);
This routine is called each time the record pointed to by precord
is added or deleted from an I/O event scan list.
cmd
has the value (0,1) if the record is being (added to, deleted from) an I/O event list.
This routine must give a value to *ppvt
.
scanIoRequest
, e.g.
scanIoRequest(ioscanpvt)
This routine can be called from interrupt level.
The request is actually directed to one of the standard callback tasks.
The actual one is determined by the PRIO
field of dbCommon
.
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); }
The code for the entire scanning system resides in dbScan.c
, i.e. periodic, event, and I/O event.
This section gives an overview of how the code in dbScan.c
is organized.
The listing of dbScan.c
must be studied for a complete understanding of how the scanning system works.
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; }
Later we will see how scan_list
s are determined.
For now just realize that scan_list.list
is the head of a list of records that belong to the same scan set (for example, all records that are periodically scanned at a 1 second rate are in the same scan set).
The node field in scan_element
contain the list links.
The normal libCom ellLib
routines are used to access the list.
Each record that appears in some scan list has an associated scan_element
.
The SPVT
field which appears in dbCommon
holds the address of 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 if 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:
dbScanLock(precord); dbProcess(precord); dbScanUnlock(precord);
It also has code to recognize when a scan list is modified while the scan set is being processed.
Event scanning is built around the following definitions:
#define MAX_EVENTS 256 typedef struct event_scan_list { CALLBACK callback; scan_list scan_list; } event_scan_list; static event_scan_list *pevent_list[NUM_CALLBACK_PRIORITIES][MAX_EVENTS];
pevent_list
is a 2d array of pointers to scan_lists
.
Note that the array allows for 256 events, i.e. one for each possible event number.
In other words, each event number and priority has its own scan list.
No scan_list
is actually created until the first request to add an element for that event number.
The event scan lists have the memory layout illustrated below:
post_event(int event)
This routine is called to request event scanning.
It can be called from interrupt level.
It looks at each event_scan_list
referenced by pevent_list[*][event]
(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 eventCallback
, which just calls scanList
.
I/O event scanning is built around the following definitions:
typedef struct io_scan_list { CALLBACK callback; scan_list scan_list; struct io_scan_list *next; } static io_scan_list *iosl_head[NUM_CALLBACK_PRIORITIES] = { NULL,NULL,NULL};
The array iosl_head
and the field next
are only kept so that scanpiol
can be implemented and will not be discussed further.
I/O event scanning uses the general purpose callback tasks to perform record processing, i.e. no task is spawned for I/O event.
The callback field of io_scan_list
is used to communicate with the callback tasks.
The following routines implement I/O event scanning:
scanIoInit (IOSCANPVT *ppioscanpvt)
This routine is called by device or driver support.
It is called once for each interrupt source.
scanIoInit
allocates and initializes an array of io_scan_list
structures; one for each callback priority and puts the address in pioscanpvt
.
Three callback priorities are supported; low, medium, and high.
Thus for each interrupt source the structures are as illustrated below:
When scanAdd
or scanDelete
are called, they call the device support routine get_ioint_info
which returns pioscanpvt
.
The scan_element
is added or deleted from the correct scan list.
scanIoRequest (IOSCANPVT pioscanpvt)
This routine is called to request I/O event scanning.
It can be called from interrupt level.
It looks at each io_scan_list
referenced by pioscanpvt
(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 ioeventCallback
, which just calls scanList
.
Periodic scanning is built around the following definitions:
typedef struct periodic_scan_list { scan_list scan_list; double period; volatile enum ctl scanCtl; epicsEventId loopEvent; } periodic_scan_list; static int nPeriodic; static periodic_scan_list **papPeriodic; static epicsThreadId *periodicTaskId;
nPeriodic
, which is determined at iocInit
time, is the number of periodic rates.
papPeriodic
is a pointer to an array of pointers to scan_lists
.
There is an array element for each scan rate.
Thus the structure illustrated in the figure below exists after iocInit
.
A periodic scan task is created for each scan rate. The following routines implement periodic scanning:
initPeriodic()
This routine first determines the scan rates.
It does this by accessing the SCAN
field of the first record it finds.
It issues a call to dbGetField
with a DBR_ENUM
request.
This returns the menu choices for SCAN
.
From this the periodic rates are determined.
The array of pointers referenced by papPeriodic
is allocated.
For each scan rate a scan_list
is allocated and a periodicTask
is spawned.
periodicTask (struct scan_list *psl)
This task just performs 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, the 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.
void scanOnce (dbCommon *precord)
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 can be called from interrupt level.
scanOnce
places its request on a ring buffer.
This is set by default to 1000 entries.
It can be changed by executing the following command in the startup script before iocInit
:
int scanOnceSetQueueSize(int size);