一、概要
本文目的是足够详细地描述记录支持,使得C程序员可以编写新地记录支持模块。在尝试编写新的记录支持前,你应该仔细地学习一些已有的记录支持模块。如果一个已有的记录支持模块类似所需的模块,大部分工作将已经完成了。
从先前的章节,应该清楚很多事情源于记录运行而发生。具体发生了什么是依赖于记录类型。为了使新的记录类型和新的设备类型不影响核心的IOC系统,使用了记录支持和设备支持的概念。对于每种记录类型,存在一个记录支持模块。它负责所有记录的具体细节。为了使一个记录支持模块独立于设备具体细节,创建了设备支持的概念。
一个记录支持模块由一个标准的例程集合组成,它们被数据访问例程调用。这些例程实现了记录具体代码。每种记录类型能够定义一个专用于那种记录类型的设备支持例程的标准集合。
到目前为止,最重要的记录支持例程是process,在dbProcess想要运行一个记录时它调用这个process。这个例程负责记录运行的具体情况。在很多情况中,它调用一个设备支持I/O例程。接下来的部分概述了为了运行一个记录必须做什么。接着时一个记录和设备支持模块必须提供的入口表的描述。剩下部分给出了一个示例记录和设备支持模块,并且描述了对记录支持模块有用的一些全局例程。
记录以其设备支持模块是应该包含记录特定头文件的仅有源文件。因而它们将是不通过数据库访问访问记录特定字段的仅有例程。
二、记录运行的概要
最重要的记录支持例程是process。这个例程决定了记录运行意味着什么。在调用记录特定的"process"例程前,已经做了以下事情:
- 决定去运行一个记录。
- 检查记录是否活跃,即:pact必须是FALSE。
- 检查记录是否被禁用。
process例程和其相关联的设备支持一起负责以下任务:
- 当它正在被处理时,设置记录活跃
- (在设备支持帮助下)执行I/O
- 检查记录特定的警报条件
- 产生数据库monitors
- 请求forward链接运行
记录运行的并发性是某些设备本质上异步。从不允许等待一个慢设备完成。异步记录执行以下步骤:
- 初始化I/O操作并且设置pact=TRUE
- 确定一个方法用于当操作结束时再次调用process。
- 不结束记录运行地立即返回
- 在I/O操作结束记录运行后调用process
- 设置pact=False并且返回
以下给出地示例展示了如何能够做这件事。
三、记录支持和设备支持入口表
每个记录类型有一个相关联地记录支持例程集合。通过在recSup.h中声明的以及由特定记录类型定义的struct typed_rset数据结构定位这些例程。这样使用一个记录支持矢量表从每种记录类型的具体实现隔离了iocCore软件。因而在不修改IOC核心软件下能够定义新的记录类型。
每种记录类型也有0或多个设备支持例程集合。没有相关联硬件的记录类型,例如计算记录(calc),通常没有任何相关联的设备支持。有相关联硬件的记录类型通常对应每种设备类型有一个设备支持模块。设备支持的概念从设备特定细节隔离了IOC核心软件以及设置记录支持。
对应每种记录类型是一个记录支持例程的集合。这个例程集合对应每种记录类型是相同的。通过记录支持入口表(RSET)定位这些例程,RSET有以下结构:
/⋆ 记录支持入口表 ⋆/
struct typed_rset {
long number; /⋆ 支持例程的数目 ⋆/
long (⋆report)(void ⋆precord);
long (⋆init)();
long (⋆init_record)(struct dbCommon ⋆precord, int pass);
long (⋆process)(struct dbCommon ⋆precord);
long (⋆special)(struct dbAddr ⋆paddr, int after);
long (⋆get_value)(void); /⋆ 过时了,设置为NULL ⋆/
long (⋆cvt_dbaddr)(struct dbAddr ⋆paddr);
long (⋆get_array_info)(struct dbAddr ⋆paddr, long ⋆no_elements, long ⋆offset);
long (⋆put_array_info)(struct dbAddr ⋆paddr, long nNew);
long (⋆get_units)(struct dbAddr ⋆paddr, char ⋆units);
long (⋆get_precision)(const struct dbAddr ⋆paddr, long ⋆precision);
long (⋆get_enum_str)(const struct dbAddr ⋆paddr, char ⋆pbuffer);
long (⋆get_enum_strs)(const struct dbAddr ⋆paddr, struct dbr_enumStrs ⋆p);
long (⋆put_enum_str)(const struct dbAddr ⋆paddr, const char ⋆pbuffer);
long (⋆get_graphic_double)(struct dbAddr ⋆paddr, struct dbr_grDouble ⋆p);
long (⋆get_control_double)(struct dbAddr ⋆paddr, struct dbr_ctrlDouble ⋆p);
long (⋆get_alarm_double)(struct dbAddr ⋆paddr, struct dbr_alDouble ⋆p);
};
每个记录支持模块必须定义它的RSET。外部名称必须是这样的格式:
<record_type>RSET
特定记录类型不需要的任何例程应该被初始化为值NULL。详细见以下示例。
通过设备支持入口表(DSET)定位设备支持例程,DSET有以下结构:
struct dset { /⋆ 设备支持入口表 ⋆/
long number; /⋆ 支持例程的数目 ⋆/
DEVSUPFUN report; /⋆ print report ⋆/
DEVSUPFUN init; /⋆ init支持 ⋆/
DEVSUPFUN init_record;/⋆ init记录实例⋆/
DEVSUPFUN get_ioint_info; /⋆ 获取io中断信息⋆/
/⋆ 其它函数是依赖记录的 ⋆/
};
每种设备支持模块必须定义它的相关联DSET。外部名称必须与在devSup.ascii中出现的名称相同。
有相关联设备支持的任何记录支持模块也必须包含用于访问其相关联设备支持模块的定义。字段dset,其在dbCommon中被声明,包含了DSET的地址。由iocInit给其赋一个值。
四、示例记录支持模块
这部分包含一个记录支持包的框架。这个记录类型是xxx并且这个记录在dbCommon字段外还有以下字段:VAL, PREC, EGU, HOPR, LOPR, HIHI, LOLO, HIGH, LOW, HHSV, LLSV, HSV, LSV, HYST,ADEL, MDEL, LALM, ALST, MLST。这些字段将有与它们用于ai记录的字段有相同含义。
4.1 声明
/⋆ Create RSET - Record Support Entry Table ⋆/
#define report NULL
#define initialize NULL
static long init_record(struct dbCommon ⋆, int);
static long process(struct dbCommon ⋆);
#define special NULL
#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
static long get_units(DBADDR ⋆, char ⋆);
static long get_precision(const DBADDR ⋆, long ⋆);
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static long get_graphic_double(DBADDR ⋆, struct dbr_grDouble ⋆);
static long get_control_double(DBADDR ⋆, struct dbr_ctrlDouble ⋆);
static long get_alarm_double(DBADDR ⋆, struct dbr_alDouble ⋆);
rset xxxRSET={
RSETNUMBER,
report,
initialize,
init_record,
process,
special,
get_value,
cvt_dbaddr,
get_array_info,
put_array_info,
get_units,
get_precision,
get_enum_str,
get_enum_strs,
put_enum_str,
get_graphic_double,
get_control_double,
get_alarm_double
};
epicsExportAddress(rset,xxxRSET);
typedef struct xxxset { /⋆ xxx input dset ⋆/
long number;
DEVSUPFUN dev_report;
DEVSUPFUN init;
DEVSUPFUN init_record; /⋆returns: (-1,0)=>(failure,success)⋆/
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_xxx;
} xxxdset;
static void checkAlarms(xxxRecord ⋆prec);
static void monitor(xxxRecord ⋆prec);
以上声明定义了记录支持入口表(RSET),一个用于相关联设备支持入口表(DSET)的模板,以及进一步声明了私有例程。
必须用一个外部名称xxxRecord声明这个RSET。它定义了为这个记录类型提供的记录支持例程。注意:进一步声明对应支持的所有例程,而一个NULL声明对应不支持的任何例程。
为这个模块使用,声明了DSET的模板。
4.2 init_record
static long init_record(struct dbCommon ⋆pcommon, int pass)
{
xxxRecord ⋆prec = (xxxRecord ⋆) pcommon;
xxxdset ⋆pdset = (xxxdset ⋆) prec->dset;
if (pass == 0)
return 0;
if (!pdset) {
recGblRecordError(S_dev_noDSET, (void ⋆)prec, "xxx:␣init_record");
return S_dev_noDSET;
}
/⋆ must have read_xxx function defined ⋆/
if ((pdset->number < 5) || (pdset->read_xxx == NULL)) {
recGblRecordError(S_dev_missingSup, (void ⋆)prec, "xxx:␣init_record");
return S_dev_missingSup;
}
if (pdset->init_record) {
long status = pdset->init_record(prec);
if (status)
return(status);
}
return 0;
}
这个例程,iocInit为类型xxx的每个记录调用这个例程两次,检查它是否有一个合适的设备支持例程的集合并且如果存在,调用DSET的init_record条目。
对init_record(pass=0)的第一次调用只能执行与这个记录相关联的初始化。在第二次调用(pass=1)过程中,可能执行指向其它记录的初始化。注意:在第二次pass过程中,其它记录可能指向这个记录中的字段。一个显示这些规则重要的代表性示例是waveform记录。waveform记录的VAL字段实际上指向一个数组。waveform记录支持模块必须为这个数组分配存储区。如果另一个记录有一个指向waveform VAL字段的数据库链接,则在解析这个链接前,必须分配这个存储区。通过使得waveform记录支持在第一次pass([ass=0)过程中分配这个数组并且在第二次pass(pass=1)过程中解析这个链接引用来完成这件事。
4.3 process
static long process(struct dbCommon ⋆pcommon)
{
xxxRecord ⋆prec = (xxxRecord ⋆) pcommon;
xxxdset ⋆pdset = (xxxdset ⋆) prec->dset;
long status;
unsigned char pact = prec->pact; //刚进入process例程时,记录的状态
if ((pdset==NULL) || (pdset->read_xxx==NULL)) {
prec->pact = TRUE;
recGblRecordError(S_dev_missingSup, (void ⋆)prec, "read_xxx");
return S_dev_missingSup;
}
/⋆ 在调用设备支持前,一定不能设置pact ⋆/
status = pdset->read_xxx(prec);
/⋆ 检查设备支持是否设置了pact ⋆/
if (!pact && prec->pact) return 0;
prec->pact = TRUE;
recGblGetTimeStamp(prec);
/⋆ check for alarms ⋆/
checkAlarms(prec);
/⋆ check event list ⋆/
monitor(prec);
/⋆ process the forward scan link record ⋆/
recGblFwdLink(prec);
prec->pact = FALSE;
return status;
}
记录process例程是IOC软件的核心。当dbProcess决定一个记录应该被运行时,它调用记录特定的process例程。process决定记录运行真的意味着什么。以上时应该做什么的代表示例。除被dbProcess调用外,process例程可能也被异步记录结束例程调用。
以上模型支持同步和异步设备支持例程。例如,如果read_xxx是一个异步例程,将发生以下顺序的事件:
- 用pact为FALSE调用process
- read_xxx被调用。由于pact是FALSE,它启动I/O,安排回调,并且设置pact为TRUE。
- read_xxx返回
- 因为pact从FALSE变成了TRUE,process仅返回。
- 对dbProcess的任何新调用被忽略,因为它发现pact为TRUE。
- 一段时间后,回调发生并且再次调用process。
- read_xxx被调用。由于pact是TRUE,它直到它是一个结束请求。
- read_xxx返回。
- process结束记录运行。
- pact设为False
- process返回
到此,这个记录已经完全被运行了。下次调用process,一切从头开始。
4.4 其它工具例程
/* 获取工程单位 */
static long get_units(DBADDR ⋆paddr, char ⋆units)
{
xxxRecord ⋆prec = (xxxRecord ⋆) paddr->precord;
strncpy(units,prec->egu,DB_UNITS_SIZE);
return 0;
}
/* 获取精度 */
static long get_precision(const DBADDR ⋆paddr, long ⋆precision)
{
xxxRecord ⋆prec = (xxxRecord ⋆) paddr->precord;
⋆precision = prec->prec;
if(paddr->pfield == (void ⋆)&prec->val) return(0);
recGblGetPrec(paddr,precision);
return 0;
}
/* 获取图形上下限制 */
static long get_graphic_double(DBADDR ⋆paddr,struct dbr_grDouble ⋆pgd)
{
xxxRecord ⋆prec = (xxxRecord ⋆) paddr->precord;
int fieldIndex = dbGetFieldIndex(paddr);
if(fieldIndex == xxxRecordVAL
|| fieldIndex == xxxRecordHIHI
|| fieldIndex == xxxRecordHIGH
|| fieldIndex == xxxRecordLOW
|| fieldIndex == xxxRecordLOLO
|| fieldIndex == xxxRecordHOPR
|| fieldIndex == xxxRecordLOPR) {
pgd->upper_disp_limit = prec->hopr;
pgd->lower_disp_limit = prec->lopr;
}
else
recGblGetGraphicDouble(paddr, pgd);
return 0;
}
/⋆ similar routines would be provided for ⋆/
/⋆ get_control_double and get_alarm_double⋆/
这些是一些记录支持包提供的各种例程的示例。在下面部分描述余下例程必须执行的函数。
4.5 警报处理
static void checkAlarms(xxxRecord ⋆prec)
{
double val;
float hyst, lalm, hihi, high, low, lolo;
unsigned short hhsv, llsv, hsv, lsv;
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, prec->udfs);
return;
}
hihi = prec->hihi; lolo = prec->lolo; high = prec->high; low = prec->low;
hhsv = prec->hhsv; llsv = prec->llsv; hsv = prec->hsv; lsv = prec->lsv;
val = prec->val; hyst = prec->hyst; lalm = prec->lalm;
/⋆ alarm condition hihi ⋆/
if (hhsv && (val >= hihi || ((lalm==hihi) && (val >= hihi-hyst)))){
if (recGblSetSevr(prec, HIHI_ALARM, prec->hhsv))
prec->lalm = hihi;
return;
}
/⋆ alarm condition lolo ⋆/
if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))){
if (recGblSetSevr(prec, LOLO_ALARM, prec->llsv))
prec->lalm = lolo;
return;
}
/⋆ alarm condition high ⋆/
if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))){
if (recGblSetSevr(prec, HIGH_ALARM, prec->hsv))
prec->lalm = high;
return;
}
/⋆ alarm condition low ⋆/
if (lsv && (val <= low || ((lalm==low) && (val <= low+hyst)))){
if (recGblSetSevr(prec, LOW_ALARM, prec->lsv))
prec->lalm = low;
return;
}
/⋆ we get here only if val is out of alarm by at least hyst ⋆/
prec->lalm = val;
return;
}
这是用于检查模拟类型记录警报状况的代表性代码集。实际的代码集可以非常记录专用。注意:系统地其它部分可以产生警报。算法总是最大化警报严重性,即:将报告最高待处理警报。
以上算法也遵守用于警报的回滞因子。这是防止遇到当前值非常接近一个警报限制并且噪声使其持续在这个限制上下波动时发生警报风暴。只在值进入一个较低警报严重性时,它才遵守这个回滞。
注意:这个测试:
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, prec->udfs);
return;
}
数据库公共定义了字段UDF,当字段VAL未定义时,它一应该被设置。字段UDFS控制处于未定义状态记录的严重性。STAT和SEVR字段被初始化就像调用了recGblSetSevr(prec, UDF_ALARM, prec->udfs)。因而如果记录从未被运行,这个记录将处于UNDEFINED警报状态以及由记录的UDFS字段设置的严重性。字段UDF被初始化未值1(true)。因而以上代码在UDF被重置为0(False)前保持这个记录处于这个警报状态。
UDF字段为非0意味着这个记录未被定义,即:其VAL字段的内容不代表一个实际值。在记录被装载到一个IOC时,这通常时记录的初始状态。当代码设置VAL字段,它也应该设置UDF,通常为False。对于其VAL字段为DBF_FLOAT或DBF_DOUBLE的记录,当VAL被设置成一个NaN(非数值)值时,UDF可能被设置成true。
对于输入记录,设备支持负责获取一个输入值。如果不能获取输入值,记录支持或设备支持都不能设置UDF为false。如果设备支持读取一个raw值,它返回一个告诉设备支持执行转换的值。在记录支持设置VAL为被转换的值,它设置UDF为false。如果设备支持获取一个被转换的值,它写到VAL,并且设置UDF为false。
对于输出记录,记录/设备支持之外的东西写到VAL字段或者因为记录支持通过OMSL字段获取一个值,VAL被传一个值。在以上任何一种情况中,写到VAL字段的代码设置UDF为false。
当数据库访问写到VAL字段时,它设置UDF为false。
调用例程recGblSetSevr产生警报。它可以被iocCore, 记录支持或者设备支持调用。探测警报的代码负责产生这个警报。
4.6 产生monitors
static void monitor(xxxRecord ⋆prec)
{
unsigned short monitor_mask = recGblResetAlarms(prec);
double delta = prec->mlst - prec->val;
/⋆ check for value change ⋆/
if (delta < 0.0) delta = -delta;
if (delta > prec->mdel) {
/⋆ post events for value change ⋆/
monitor_mask |= DBE_VALUE;
/⋆ update last value monitored ⋆/
prec->mlst = prec->val;
}
/⋆ check for archive change ⋆/
delta = prec->alst - prec->val;
if (delta < 0.0) delta = -delta;
if (delta > prec->adel) {
/⋆ post events on value field for archive change ⋆/
monitor_mask |= DBE_LOG;
/⋆ update last archive value monitored ⋆/
prec->alst = prec->val;
}
/⋆ send out monitors connected to the value field ⋆/
if (monitor_mask) {
db_post_events(prec, &prec->val, monitor_mask);
}
}
所有记录类型应该如展示地调用recGblResetAlarms。注意:在这个例程结束后,nsta和nsev将有0值。这是在运行结束后确保警报检查重头开始所需地。当一个记录从一个警报状态变为无警报状态时,代码也管理产生警报monitors。记录支持例程遵守以上模型是必须地,否则警报运行将不遵守这个规则。
模拟类型记录也应该提供由这个示例展示的monitor和archive回滞字段。
db_post_events导致通道访问为连接了这个记录和字段的客户端发出monitors。这个调用为:
int db_post_events(void ⋆precord, void ⋆pfield, unsigned int monitor_mask)
此处:
1) precord:记录的地址
2) pfield:字段的地址
3) monitor_mask:一个位掩码,可以是以下的任意组合:
- DBE_ALARM:一个警报状态的变化已经发生。这是由recGblResetAlarms设置。
- DBE_LOG:存档值更新。
- DBE_VAL:值更新。
- DBE_PROPERTY:值属性更新。
重要:记录支持模块负责为由于记录运行而变化的任何字段调用db_post_event。它不应该为没有变化的字段调用db_post_event。
五、记录支持例程
这部分描述在RSET中定义的例程。未应用于一个特定记录类型的任何例程必须被声明为NULL。
5.1 产生记录中每个字段的报告
long report(void ⋆precord);
这个例程未被大部分记录类型使用。任何操作都是记录类型特定的。
5.2 初始化记录运行
long init(void);
这个例程在IOC初始化时被调用一次。任何操作都是记录类型特定的。大部分记录类型不需要这个例程。
5.3 初始化特定记录
long (⋆init_record)(struct dbCommon ⋆precord, int pass);
iocInit为由此例程处理的这种类型的每个数据库记录调用这个例程2次(pass=0和pass=1)。它必须执行以下功能:
- 检查相关联设备支持例程和/或发出它们的初始化调用
- 执行任何记录类型专用的初始化
- 在第一次pass期间,它只执行影响precord引用记录的初始化。
- 在第二次pass期间,它执行影响其它记录的初始化。
5.4 运行记录
long (⋆process)(struct dbCommon ⋆precord);
这个例程必须遵守先前指定的规则。
5.5 特殊处理
long special(struct dbAddr ⋆paddr, int after);
这个例程为由dbAddr指向的字段实现记录类型特定的特殊处理。当从这个记录之外写到一个字段时,它被调用两次,一次用after=0在此字段被做任何更改前,第二次用after=1在完成更改后。这个例程通过从第一次调用(after=0)返回一个错误状态阻止进行任何修改。文件special.h定义了特殊类型。只为用户特殊字段调用这个例程,即:SPC_xxx>100的字段。一个字段在ASCII记录定义文件中被声明为特殊。新值不应该被添加到special.h,而是使用SPC_MOD。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被修改。
5.6 获取值
get_value这个例程不再被使用。它应该在记录支持入口表中保留位一个NULL过程。
5.7 转换dbAddr定义
如果这个字段有等于SPC_DBADDR的特殊设置,这个例程被dbNameToAddr调用。一个代表性使用是当一个字段指向一个数组时。这个例程可以更改dbAddr字段的任意组合:no_elements, field_type, field_size, special, pfield以及dbr_type。例如,如果一个waveform记录的VAL字段被传递给dbNameToAddr, cvt_dbaddr会更改dbAddr使得它指向实际的数组而非VAL。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
注意:
- 通道访问调用db_name_to_addr,它时老的数据库访问的组成部分。db_name_to_addr调用dbNamtToAddr。当一个客户端连接了这个记录,做了这件事。
- no_elements必须被设置成将在数组中存储的最大元素数目。
5.8 获取数组信息
long get_array_info(struct dbAddr ⋆paddr, long ⋆no_elements, long ⋆offset);
这个例程返回当前元素数目以及指定的数组的第一个值的偏移。如果数组实际上是一个环形缓存,偏移有意义。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。get_array_info更改pfield是允许的。这个特性可以用于实现双缓存。
当数组字段正在被写,在字段值被更改前,调用get_array_info。
5.9 写数组信息
long put_array_info(struct dbAddr ⋆paddr, long nNew);
在新值已经被放入到指定的数组后,调用这个例程。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.10 获取单位
long get_units(struct dbAddr ⋆paddr, char ⋆units);
此例程设置单位位对应这个字段的工程单位。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.11 获取精度
long get_precision(const struct dbAddr ⋆paddr, long ⋆precision);
这个例程获取精度,即:十进制位数目,其用于转换这个字段值位一个ASCII串。应该为不与值字段直接相关的字段调用recGblGetPrec。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.12 获取枚举字符串
long get_enum_str(const struct dbAddr ⋆paddr, char ⋆pbuffer);
这个例程设置*pbuffer为对应这个字段值的ASCII串。这个字段必须为类型DBF_ENUM。
查看bi或mbbi记录的代码为例。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.13 获取对应枚举字段的字符串
long get_enum_strs(const struct dbAddr ⋆paddr, struct dbr_enumStrs ⋆p);
这个例程传值给结构体dbr_enumStrs的所有字段。
查看bi或mbbi记录的代码为例。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.14 写入枚举字符串
long put_enum_str(const struct dbAddr ⋆paddr, char ⋆pbuffer);
给出一个ASCII串,这个例程更新数据库字段。它比较这个字符串和与每个枚举值相关联的字符串值,并且如果它找到一个匹配,设置数据库字段等于匹配的字符串的索引。
查看bi或mbbi记录的代码为例。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.15 获取图形Double信息
long get_graphic_double(struct dbAddr ⋆paddr, struct dbr_grDouble ⋆p);
这个例程传递值给结构体dbr_grDouble的图形相关联字段。应该为不直接与值字段相关联的字段调用recGblGetGraphicDouble。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.16 获取图形Double信息
long get_control_double(struct dbAddr ⋆paddr, struct dbr_ctrlDouble ⋆p);
这个例程传递值给结构体dbr_ctrlDouble的所有字段。应该为不直接与值字段相关联的字段调用recGblGetControlDouble。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
5.17 获取警报Double信息
long get_alarm_double(struct dbAddr ⋆paddr, struct dbr_alDouble ⋆p);
这个例程传值给结构体dbr_alDouble的所有字段。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被更改。
6 全局记录支持例程
很多全局记录支持例程可用。这些例程是为了记录特定处理例程使用,但可以被想要使用它们服务的任何例程调用。
这些例程中每一个例程的名称都已"recGbl"开头。使用这些例程的代码应该
#include <recGbl.h>
6.1 警报状态和严重性
在记录运行过程中很多不同位置可能产生警报。算法是最大化警报严重性,即:产生最高严重性待处理警报。如果发现了多个相同严重性的警报,则报告第一个。这意味着当一代码段想要产生一个警报,只在它将声明的警报严重性高于已有的警报严重性,它才做这件事。四个字段(在数据库公共中)用于实现警报:sevr, stat, nsev和nsta。前两个是在这个记录完全被运行后的状态和严重性。后两个字段(nsta和nsev)是在记录运行过程中要设置的状态和严重性。两个例程用于处理警报。当一个例程想要产生一个警报,它调用recGblSetSevr。如果这个例程导致警报严重性增加,它将只更改nsta和nsev。在运行末尾,记录支持模块必须调用recGblRestAlarms。这个例程设置stat=nsta, sevr=nsev,nsta=0和nsev=0。如果自上次调用以来stat或sevr已经更改了值,它为stat和sevr调用db_post_event并且返回一个DBE_ALARM的值。如果没有发生变化,它返回0。因而在调用recGblResetAlarm后,为记录下次运行产生警报做好了所有准备。以上示例记录支持模块展示了如何使用这些宏。
int recGblSetSevr(void ⋆precord, short nsta, short nsev);
如果它更改了nsta和/或nsev,返回TRUE,如果它不更改它们,返回FALSE。
unsigned short recGblResetAlarms(void ⋆precord);
返回:对应monitor_mask的初始值。
6.2 警报确认
数据库公共包含另外两个警报相关字段:
- acks:最高严重性未确认警报
- ackt:需要被确认的瞬态警报
这些字段被iocGblRestAlarms处理,并且不应该被记录支持使用。它为警报处理程序提供警报确认功能。
6.3 产生错误:过程变量名,调用者,消息
建议:对新代码使用errlogPrintf替代这个。
void recGblDbaddrError(long status, struct dbAddr ⋆paddr, char ⋆pcaller_name);
这个例程和泉系统错误处理系统连接,显示以下信息:状态信息,过程变量名,进行调用的例程。
6.4 产生错误:状态字符串,记录名称,调用者
建议:对于新代码使用errlogPrintf替代这个。
void recGblRecordError(long status, void ⋆precord, char ⋆pcaller_name);
这个例程和泉系统错误处理系统连接,显示以下信息:状态信息,过程变量名,进行调用的例程。
6.5 产生错误:记录名,调用者,记录支持消息
建议:对于新代码使用errlogPrintf替代这个。
void recGblRecsupError(
long status,
struct dbAddr ⋆paddr,
char ⋆pcaller_name, /⋆ 调用例程的名称 ⋆/
char ⋆psupport_name); /⋆ 支持例程的名称 ⋆/
这个例程连接全系统错误处理系统,显示以下信息:状态信息,记录名称,调用例程,记录支持条目名称。
6.6 获取图形Double
void recGblGetGraphicDouble(struct dbAddr ⋆paddr, struct dbr_grDouble ⋆pgd);
这个例程被get_graphic_double记录支持例程使用,去获取它不知道如何设置的字段的图形值。
6.7 获取控制Double
void recGblGetControlDouble(struct dbAddr ⋆paddr, struct dbr_ctrlDouble ⋆pcd);
这个例程可以被get_control_double记录支持例程使用,去获取它不知道如何设置的字段的控制值。
6.8 获取警报Double
void recGblGetAlarmDouble(struct dbAddr ⋆paddr, struct dbr_alDouble ⋆pcd);
这个例程可以被get_alarm_double记录支持例程使用,去获取它不知道如何设置的字段的警报值。
6.9 获取精度
void recGblGetPrec(struct dbAddr ⋆paddr, long ⋆pprecision);
这个例程可以被get_precision记录支持例程使用,去获取它不知道设置何止精度的字段的精度。
6.10 获取时间戳
void recGblGetTimeStamp(void ⋆precord)
这个例程获取当前的时间戳并且放置它再这个记录中。它做了以下事情:
- 如果TSEL不是一个常量链接,并且TSEL指向一个记录的TIME字段,从由TSEL引用的记录获取时间,并且这被放入字段TIME。这个例程返回。
- 如果TSEL不是一个常量链接,dbGetLink被调用,并且这个值被放入字段TSE。
- 如果TSE等于epicsTimeEventDevice(-2),则什么也不做,即:这个例程仅返回。
- epicsTimeGetEvent被调用。
6.11 forward链接
void recGblFwdLink(void ⋆precord);
这个例程由process使用去请求forward链接的运行。
6.12 初始化常量链接
int recGblInitConstantLink(struct link ⋆plink, short dbfType, void ⋆pdest);
初始化一个常量链接。这个例程通常被init_record(或者被相关联的设备支持)调用去初始化与一个常量链接相关联的字段。根据它(没有,有)修改了目标,它返回(FALSE, TRUE)。
6.13 模拟值死区检查
void recGblCheckDeadband(epicsFloat64 ⋆poldval, const epicsFloat64 newval,
const epicsFloat64 deadband, unsigned ⋆monitor_mask, const unsigned add_mask);
检查模拟(double)值是否超出了指定的死区,并且再monitor掩码中设置位。这个例程通常被一个模拟记录的monitor调用(作为运行的组成部分)去检查一个值是否超出了一个预定义的死区。它也根据检查结果再一个monitor掩码中设置位。如果newval在指定的deadband外部,newval被复制到*poldval并且add_mask被或到了monitor_mask中。