这里描述如何使用数组字段内存管理特定。这使得数组数据能够被移入和移出waveform,aai和aao类型的值字段(BPTR)。
使用这种特定包括用另一个(用户分配的)字段替代存储在BPTR字段的指针。基本规则是:
1、BPTR以及它当前指向的内存,只能在这个记录被锁定时才能被访问。
2、NELM不可以被更改。
3、BPTR必须总是指向一段足够大的内存,来装下最大的元素数目(由NELM字段确定)。
规则1意味着从一个设备支持函数或者手动调用dbScanLock()后读,写或者解引用BPTR字段才安全。规则3意味着BPTR不能被设置NULL,并且在替代BPTR时,替换必须为最差情况分配足够大空间。一个外部客户端能够在几乎任何时间写入一个最大NELM个元素的数组。
以下示例演示了为waveform缓存使用了自定义分配。
以下是一个waveform记录设备支持的源代码devwf.c:
#include "errlog.h"
#include "initHooks.h"
#include "ellLib.h"
#include "devSup.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "cantProceed.h"
#include "epicsTypes.h"
#include "epicsMutex.h"
#include "epicsEvent.h"
#include "epicsThread.h"
#include "menuFtype.h"
#include "dbScan.h"
#include "alarm.h"
#include "recGbl.h"
#include "waveformRecord.h"
#include "epicsExport.h"
static ELLLIST allPvt = ELLLIST_INIT;
struct devicePvt {
ELLNODE node;
/* 同步访问这个结构体 */
epicsMutexId lock;
/* 当需要另一个更新时,唤醒这个worker */
epicsEventId wakeup;
/* 当另一个更新可用时,通知scanner线程。 */
IOSCANPVT scan;
/* 下次更新 */
void *nextBuffer;
epicsUInt32 maxbytes, numbytes;
};
static long init(int phase);
static long init_record(dbCommon *pcommon);
static long get_iointr_info(int dir, dbCommon *prec, IOSCANPVT *scan);
static long read_wf(waveformRecord *prec);
static void worker(void*);
static void startWorkers(initHookState);
wfdset devWfZeroCopy = {
{5, NULL, init, init_record, get_iointr_info},
read_wf
};
epicsExportAddress(dset, devWfZeroCopy);
static long init(int phase)
{
if(phase!=0){
return 0;
}
initHookRegister(&startWorkers);
return 0;
}
static long init_record(dbCommon *pcommon)
{
struct devicePvt *priv;
waveformRecord * prec = (waveformRecord *)pcommon;
if(prec->ftvl!=menuFtypeSHORT) {
errlogPrintf("%s.FTVL must be set to SHORT for this example\n", prec->name);
return 0;
}
/* 清理由记录支持分配的数组。
* 由于我们使用lcalloc()/free(),不必要,
* 但用其它方法分配时,不需要。
*/
free(prec->bptr);
prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl), "first buf");
// struct devicePvt
priv = callocMustSucceed(1, sizeof(*priv), "init_record devWfZeroCopy");
priv->lock = epicsMutexMustCreate();
priv->wakeup = epicsEventMustCreate(epicsEventFull);
scanIoInit(&priv->scan);
priv->maxbytes = prec->nelm*dbValueSize(prec->ftvl);
ellAdd(&allPvt, &priv->node);
prec->dpvt = priv;
return 0;
}
static void startWorkers(initHookState state)
{
ELLNODE *cur;
/* 直到调用scanIoRequest安全,才启动worker线程 */
if(state!=initHookAfterInterruptAccept){
return;
}
for(cur=ellFirst(&allPvt); cur; cur=ellNext(cur))
{
struct devicePvt *priv = CONTAINER(cur, struct devicePvt, node);
epicsThreadMustCreate("wfworker",
epicsThreadPriorityHigh,
epicsThreadGetStackSize(epicsThreadStackSmall),
&worker, priv);
}
}
static void worker(void* raw)
{
struct devicePvt *priv=raw;
void *buf = NULL;
epicsUInt32 nbytes = priv->maxbytes;
while(1) {
epicsThreadSleep(1.0);
if(!buf) {
/* 为之后(本地)使用分配并且初始化一个新缓存 */
size_t i;
epicsInt16 *ibuf;
buf = callocMustSucceed(1, nbytes, "buffer");
ibuf = (epicsInt16*)buf;
for(i=0; i<nbytes/2; i++)// 字节数除以2,就是元素数目
{
ibuf[i] = rand();
}
}
/* 当扫描是'I/O Intr'时,等待事件信号,
* 而当记录是周期扫描时,等待超时
*/
// 等待事件priv-wakeup 1.0秒,如果返回事件出错,挂起正在运行的线程
if(epicsEventWaitWithTimeout(priv->wakeup, 1.0)==epicsEventError) {
cantProceed("worker encountered an error waiting for wakeup\n");
}
// 获取互斥锁priv->lock
epicsMutexMustLock(priv->lock);
if(!priv->nextBuffer) {
/* 使得本地缓存对read_wf函数可用 */
priv->nextBuffer = buf;
buf = NULL;
priv->numbytes = priv->maxbytes;
scanIoRequest(priv->scan);
}
epicsMutexUnlock(priv->lock);
}
}
static long get_iointr_info(int dir, dbCommon *prec, IOSCANPVT *scan)
{
struct devicePvt *priv=prec->dpvt;
if(!priv)
{
return 0;
}
*scan = priv->scan;
/* 当这个线程被放入I/O扫描列表时,唤醒这个worker*/
if(dir==0){// 发出事件信号
epicsEventSignal(priv->wakeup);
}
return 0;
}
static long read_wf(waveformRecord *prec)
{
struct devicePvt *priv=prec->dpvt;
if(!priv){
return 0;
}
epicsMutexMustLock(priv->lock);
if(priv->nextBuffer) {
/* 更新可用,所以占用它 */
if(prec->bptr){// 指向数据区的指针有效,释放此数据区
free(prec->bptr);
prec->bptr = priv->nextBuffer; /* 切换指针指向,没有复制内存 */
priv->nextBuffer = NULL;
prec->nord = priv->numbytes / dbValueSize(prec->ftvl);// 计算实际元素数目
epicsEventSignal(priv->wakeup);// 工作线程又可以运行
}
epicsMutexUnlock(priv->lock);
assert(prec->bptr);
}
return 0;
}
/*
typedef long (*DEVSUPFUN)(void *);
typedef struct dset { //设备支持入口表
long number; //支持例程数目
DEVSUPFUN report; // 打印报告
DEVSUPFUN init; // init支持层
DEVSUPFUN init_record; // 特定记录的init设备
DEVSUPFUN get_ioint_info; // 获取io中断信息
//以下时取决于记录的其它函数
} dset;
*/
编写以上源文件的devwfsup.dbd文件:
device(waveform,CONSTANT,devWfZeroCopy,"Zero Copy Demo")
在Makefile文件中添加以上两个文件:
wfDevSup_DBD += devwfsup.dbd
wfDevSup_SRCS += devwf.c
添加一个只含一个waveform记录的db文件:
record(waveform, "$(TEST):wf") {
field(DTYP, "Zero Copy Demo")
field(FTVL, "SHORT")
field(NELM, "6")
field(SCAN, "I/O Intr")
}
更改启动文件st.cmd:
dbLoadRecords("db/wf.db","TEST=TEST")
启动这个IOC程序,并且查看加载的记录实例:
epics> dbl
TEST:wf
用cainfo和camonitor通道访问进行测试:
root@orangepi4-lts:/usr/local/EPICS/base/modules/database/src/std/dev# cainfo TEST:wf
TEST:wf
State: connected
Host: 192.168.50.184:5064
Access: read, write
Native data type: DBF_SHORT
Request type: DBR_SHORT
Element count: 6
root@orangepi4-lts:/usr/local/EPICS/base/modules/database/src/std/dev# camonitor TEST:wf
TEST:wf 2023-08-11 04:38:32.964899 6 11358 -32429 28512 -25174 -11566 -30030
TEST:wf 2023-08-11 04:38:33.965274 6 27856 32389 -2566 -12204 2520 14133
TEST:wf 2023-08-11 04:38:34.965634 6 -19224 -17196 29030 19074 -11420 -20584
TEST:wf 2023-08-11 04:38:35.966003 6 -17959 -21592 -25977 -20875 -3483 10352
TEST:wf 2023-08-11 04:38:36.966368 6 29274 10378 -18625 -19358 -18816 4905
TEST:wf 2023-08-11 04:38:37.966640 6 28740 -7458 -27524 -8283 -32631 26446
TEST:wf记录中数组值每秒钟随机变化一次。