Database scanning is the mechanism for deciding when to process a record. Five types of scanning are possible:
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 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.
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
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.
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.
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.
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
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); 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 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 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.
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.
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.
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.
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
.
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
during initialization, e.g.
scanIoInit(&ioscanpvt);
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.
scanIoRequest
, e.g.
scanIoRequest(ioscanpvt);
This routine can be called from interrupt level.
The 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.
scanIoRequest()
will return a bit pattern indicating which of the three queues the request was sent to.
A return value of zero means no records are currently configured to use this interrupt source for I/O Interrupt scanning.
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); }
The code for the entire scanning system resides in dbScan.c
.
This section gives an overview of how this code is organized.
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:
printList
- Prints the names of all records in a scan set.
scanList
- This routine is the heart of the scanning system.
For each record in a scan set it does the following:
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.
addToList
- This routine adds a new element to a scan list.
deleteFromList
- This routine deletes an element from a scan list.
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.
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
.
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.
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:
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.
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
.
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:
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.
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.
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 may be called from interrupt level.
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);