EPICS应用程序开发17 -- 数据库扫描

1 概要

数据库扫描是决定何时运行一个记录的机制。五种扫描类型是可能的:

1) Periodic:一个记录能够被周期地运行。支持很多时间间隔。

2) Event:事件扫描是基于另一个软件组件通过调用例程post_event提交事件。

3) I/O Event:这种扫描类型地原来含义是由硬件中断造成一个对记录运行的请求。这个机制支持硬件中断以及软件产生的事件。

4) Passive:只能通过对dbScanPassive的请求,被动记录被运行。在记录运行过程中,已经被声明为"Process Passive"的数据库链接(转发(forward),输入(input)或输出(output))被访问时,这个请求发生。它也能够由dbPutField被调用产生(这通常源于一个通道访问写请求)。

5) Scan Once:为了提供给缓存写,扫描系统提供了一个例程scanOnce,它安排一个记录被运行一次。

本文以越来越详细地解释数据库扫描。它解释了设计扫描地数据库字段。它接着讨论扫描系统的接口。末尾部分简要介绍了如何实现扫描器。

2、扫描相关字段

以下字段通常通过DCT被定义。但应该注意动态地更改任何扫描相关字段是非常可能的。例如,一个显示管理器窗口绑定一个菜单控件到一个记录的SCAN字段并且允许操作者动态地更改扫描机制。

2.1 SCAN

这个字段,指定了扫描机制,有一个以下格式地相关联菜单:

  • Passive:被动扫描。
  • Event:事件扫描。字段EVNT指定事件号。
  • I/O Intr:I/O Event扫描
  • 10 Second:每10秒种周期地扫描
  • ...
  • .1 Second:每0.1秒地周期扫描。

2.2 PHAS

这个字段确定位于相同扫描集中地记录的运行顺序。例如,以2秒速率被周期扫描的所有记录是位于相同的扫描集。有相同EVNT的所有事件扫描记录是位于相同扫描集等。对于在于相同扫描集的记录,PHAS=0的所有记录在PHAS=1的记录前被运行,PHAS=1的所有记录在PHAS=2的所有记录前运行,等。

一般,依赖PHAS执行顺序顺序不是一个好想法。使用数据库链接更好。

2.3 EVNT--事件号

只有SCAN被设置为Event时,这个字段才有意义,在这种情况下,它指定了事件号。为了一个记录被事件扫描,EVNT必须在0~255范围内。也应该注意某些EPICS软件组件将不为事件0请求事件扫描。一个示例是eventRecord记录支持模块。因而,应用程序开发者想要在范围1~255范围定义事件。

2.4 PRIO--调度优先级

这个字段可以被任何需要指定调度优先级的软件组件使用,例如,事件和I/O事件扫描工具使用这个字段。

3 扫描相关的软件组件

这个文件包含一个于字段SCAN相关联的菜单的定义。定义格式为:

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")
}

前三个选项必须按显示的顺和位置出现。剩下的定义是对应周期扫描速率,它们必须按从最慢到最快的顺序出现(顺序直接控制分配给特定扫描速率的线程优先级,并且更快的扫描速率应该被分配更高的线程优先级)。在IOC初始化时,在扫描初始化时读取菜单选项字符串。周期扫描速率的数目和每个速率的周期是被从菜单选项字符串确定的。因而,通过更高menuScan.dbd并且通过dbLoadDatabase装置这个版本能够更改扫描速率。唯一要求是每个周期选项字符串必须以一个用秒为单位指定的数值开始。

3.2 dbScan.h

于扫描系统交互的所有软件组件必须包含这个文件。在这个文件中最重要的定义是:

#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);

3.3 初始化并且控制数据库扫描

scanInit(void);

例程scanInit被iocInit调用。它初始化扫描系统。

scanRun(void);
scanPause(void);

这些例程各自启动和停止所有扫描任务。它们被iocInit, iocRun和iocPause命令使用。

3.4 添加记录到扫描列表和从扫描列表删除记录

每次添加一个记录到一个扫描列表或者从一个扫描列表删除一个记录,调用以下例程。

scanAdd(struct dbCommon *);
scanDelete(struct dbCommon *);

为了输入通过DCT创建的所有记录到正确的扫描列表,这些例程在IOC初始化时被scanInit调用。每次一个扫描相关的字段被更改时,例程dbPut调用scanDelete和scanAdd(每个扫描相关字段在dbCommon.dbd中被声明为SPC_SCAN)。在这个字段被更改前,调用scanDelete,在这个字段被更改后,调用scanAdd。

3.5 从SCAN字段获取扫描周期

double scanPeriod(int scan);

这个参数是对应menuScan.h中枚举选项集合中一个偏移。大部分用户仅使用一个数据库记录的SCAN字段。它返回以秒为单位的扫描周期。如果扫描不指向一个周期速率,结果将是0.0。

3.6 声明数据库事件

当任何软件组件想要声明一个数据库事件时,它只要调用:

post_event(event)

这实际上能被任何IOC软件组件调用。例如,sequence程序可以调用它。eventRecord的记录支持模块可以调用它。

3.7 I/O事件扫描的接口

通过设备和驱动支持的某种组合完成到I/O事件扫描器的连接。

1) Include <dbScan.h>

2)  对于单独的事件源,必须做以下事情:

  • 声明一个IOSCANPVT变量,例如:static IOSCANPVT ioscanpvt
  • 调用scanIoINit,例如:scanIOInit(&ioscanpvt);

3)  提供设备支持get_ioint_info例程。这个程序有以下格式:

long get_ioint_info(
    int cmd,
    struct dbCommon *precord,
    IOSCANPVT *ppvt);

由precord指向的记录每次被添加到一个I/O事件扫描列表或者被从这个事件扫描列表删除时,条用这个例程。cmd有(0,1)分别对应这个记录正在被添加到一个I/O事件列表或者被从这个列表删除。这个例程必须传一个值给*ppvt。

4) 当探测到一个I/O事件时,调用scanIoRequest,例如:

scanIoRequest(ioscanpvt)

能够从中断层级调用这个例程。这个请求实际上被定向到一个其中一个标准回调任务。实际的回调任务是由dbCommon的PRIO字段决定的。

以下代码片段展示了一个支持I/O事件扫描的event记录设备支持模块:

#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);
}

4、实现概要

整个扫描系统的代码位于dbScan.c中,即:周期、事件以及I/O event。这部分概要地描述在dbScan.c中代码被如何组织。要完整理解扫描系统如何工作必须研究dbScan.c的代码。

4.1 所有扫描类型共有的定义和例程

所有东西都是围绕两个基本结构体被构建的:

typedef struct scan_list {
    epicsMutexId lock;
    ELLLIST      list;
    short        modified;
};

typedef struct scan_element{
    ELLNODE         node;
    scan_list       *pscan_list;
    struct dbCommon *precord;
}

 之后我们将看到scan_list是如何被确定的。此刻,只要知道scan_list.list是一个属于相同扫描集的记录列表的头(例如,以1秒速率被周期扫描的所有记录是位于相同扫描集)。在scan_element中的node字段包含这个列表链接。正常的libCom ellLib例程用于访问这个列表。出现在某个扫描列表中每个记录有一个相关联的scan_element。其出现在dbCommon中的SPVT字段保存相关联的scan_element的地址。

lock,modified和pscan_list字段允许scan_elements,即记录,被动态移除和添加到扫描列表。如果scanList,实际运行一个扫描列表的例程,被研究,它可以被看成这些字段当列表被扫描时如果对列表没有做修改,允许这个列表被高效的扫描。这当然是正常情况。

dbScan.c模块包含若干私有例程。以下访问单个扫描集:

  • printList:打印一个扫描集中所有记录名称。
  • scanList:这个例程是扫描系统的核心。对于在扫描集合中每个记录,它做以下事情:
dbScanLock(precord);
dbProcess(precord);
dbScanUnlock(precord);

它也有当扫描集被运行时识别一个记录列表何时被修改的代码。 

  • addToList:这个例程添加一个新元素到一个扫描列表。
  • deleteFromList:这个例程从一个扫描列表删除一个元素。

4.2 事件扫描

围绕以下定义构建事件扫描:

#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是一个指向scan_lists的指针的二维数组。注意:这个数组允许256个事件,即:每个事件对应每种可能的事件号。换句话,每种事件号和优先级有它自己的扫描列表。在为那个事件号添加一个元素的第一个请求钱,不实际创建scan_list。

 4.2.1 post_event

post_event(int event)

调用这个例程请求事件扫描。可以从中断层级调用它。它查看由pevent_list[*][event](每一个对应每个回调优先级)引用的每个event_scan_list,并且如果任何元素出现在scan_list中,发出一个callbackRequest。合适的回调任何调用例程eventCallback,其只调用scanList。

4.3 I/O事件扫描

围绕以下定义构建I/O事件扫描:

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};

 数组iosl_head和字段next仅被保存,使得scanpiol可以被实现,并且将不进一步讨论。I/O事件扫描使用通用回调任务执行记录运行,即:不为I/O事件产生任务。io_scan_list的回调字段用于于回调任务通信。

以下例程实现了I/O事件扫描:

4.3.1 scanIoInit

scanIoInit (IOSCANPVT  *ppioscanpvt)

这个例程被设备或驱动支持调用。为每个中断源调用一次它。scanIoInit分配和初始化一个io_scan_list结构体数组;每个结构体对应每个回调优先级并且在pioscanpvt中放置地址。支持三种回调优先级:low,medium和high。因而对应每个中断源,这种结构体按如下说明:

 

当调用sanAdd或scanDelete时,它们调用返回pioscanpvt的设备支持例程get_ioint_info。scan_element被添加到正确的扫描列表或被从正确的扫描列表删除。

4.3.2 scanIoRequest

scanIoRequest (IOSCANPVT pioscanpvt)

 调用这个例程去请求I/O事件扫描。可以从中断层级调用它。它查看由pioscanpvt引用的每个io_scan_list(每一个对应每种回调优先级)并且如果任何元素出现在scan_list中,发出一个callbackRequest。合适的回调任务调用例程ioeventCallback,它仅调用scanList。

4.4 周期扫描

围绕以下定义构建周期扫描:

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:在iocInit时被确定,是周期速率的数目。

papPeriodic:是指向一个指向scan_lists的指针数组的一个指针。一个数组元素对应一种扫描速率。因而在以下图形中说明的结构体在iocInit后存在。

 为每种扫描速率创建一个周期扫描任务。以下例程实现了周期扫描:

4.4.1 initPeriodic

initPeriodic()

 这个例程首先确定扫描速率。通过访问它找到的第一个记录的SCAN字段,它做这件事。它用一个DBR_ENUM请求发出对dbGetField的调用。这返回对应SCAN的菜单选项。从这,确定了周期扫描速率。分配由papPeriodic引用的指针数组。对于每种扫描速率,分配一个scan_list并且产生一个periodicTask。

4.4.2 periodicTask

periodicTask (struct scan_list *psl)

这个任务只是执行一个无限循环,调用scanList并且接着等待到下一个扫描间隔开始,允许它使用此时间扫描这个列表。如果一个周期扫描列表花费比它的已定义扫描周期更长时间来运行,下次扫描将被延时半个扫描周期(最大1秒延时)。这不限制实际实现什么扫描速率。持续超负荷运行(连续10次以上)将导致一条消息被记录。超负荷运行的总数是由每个扫描线程计数并且可以使用scanppl命令显示它。

4.5 scanOnce

void scanOnce (dbCommon *precord)

一个任务OnceTask等待发送dbProcess请求的请求。例程scanOnce把要被运行的记录地址

放入一个环形缓存并且唤醒onceTask。

能够从中断层级调用这个例程。

4.5.2 SetQueueSize

scanOnce在一个环形缓存中放置其请求。这默认被设置成了1000个条目。通过在启动脚本中iocInit前执行以下命令,能够更改它:

int scanOnceSetQueueSize(int size);

猜你喜欢

转载自blog.csdn.net/yuyuyuliang00/article/details/127011481