EPICS应用程序开发16--EPICS通用任务

1、概要

本文描述EPICS提供的两个通用任务的集合:1) Callback; 2) 任务看门狗

当为一个IOC编写代码时,经常在其下没有明显的任务要执行。一个代表性示例是用于异步设备支持模块的完成代码。EPICS为这样的代码提供了回调任务。

如果一个IOC任务"崩溃",一般没有东西监视这个vxWorks shell来发现这个问题。EPICS提供了一个看门狗任务,它周期性检查其它任务的状态。如果它发现了一个受监视的任务已经结束或者暂停,它发出一条错误消息并且也可以调用能够采取其它操作的其它例程。例如,如果一个受监视的任务崩溃了,一个subroutine记录安排被置入警报。

由于IOCs通常自动运行,即:没有东西监视vxWorks Shell,发出printf调用的代码产生不会被看到的错误消息。此外,fprintf的vxWorks实现需要比printf调用多得多的栈空间。vxWorks的另一个问题是logMsg功能。logMsg以比除shell外所有其它任务更高优先级产生消息。EPICS通过一个错误消息处理功能解决了所有这些问题。代码可以调用errMessage, errPrintf或errlogPrintf中任何例程。这些中任意例程导致错误消息由一个单独的低优先级任务产生。进行调用的任务必须等待到消息被处理,但其它任务不被延时。此外,消息可以被发送到一个系统范围的错误消息文件。

2、通用回调任务

2.1 概要

EPICS提供了三种通用IOC回调任务。这些任务之间的唯一差别是它们的调度优先级:low,medium或high。低优先级任务以仅高于通道访问的优先级运行,中优先级大约等价于周期扫描任务中位数的优先级,而高优先级以比事件扫描任务更高的优先级。回调任务可用于需要在其下立即或者一段延时后运行某个作业的任务的任何软件组件。三种回调任务向任务看门狗注册它们自己。用大量栈空间创建它们并且因而可以用于调用记录运行。例如,I/O事件扫描程序使用通用回调任务。

为了使用通用回调任务,必须采取以下步骤:

1) 包含回调定义

#include <callback.h>

2) 为一个结构体提供存储,此结构体是用于此回调任务的私有结构体:

CALLBACK mycallback;

使这个成为一个更大结构体的组成部分是可能的,例如:

struct {
    ...
    CALLBACK mycallback;
    ...
} ...

3) 进行调用(在大部分情况中,这些实际上是宏)去初始化CALLBACK中的字段:

callbackSetCallback(CALLBACKFUNC func, CALLBACK *pcb);

这定义了要被执行的回调例程。第一个参数是一个函数地址,其将被传给CALLBACK的地址,并且返回void。第二个参数是CALLBACK结构体的地址。

callbackSetPriority(int, CALLBACK *pcb);

第一个参数是优先级,其将是以下值之一:priorityLow, priorityMedium或priorityHigh。这些值在callback.h中被定义。第二个参数再次是CALLBACK结构体。

callbackSetUser(void *, CALLBACK *pcb);

这个调用被用于保存一个指针值,能够用以下宏获取这个值:

callbackGetUser(void *,CALLBACK *pcb);

如果你的回调函数存在去在dbScanLock/dbScanUnlock调用之间运行单个记录,你可以使用向你提供回调例程并且同时设置两个参数的快捷方式(这里的用户参数是一个指向这个记录实例的指针):

callbackSetProcess(CALLBACK *pcb, int prio, void *prec);

4) 当需要一个回调请求时,只要调用以下之一:

callbackRequest(CALLBACK *pcb);
callbackRequestProcessCallback(CALLBACK *pcb, int prio, void *prec);

二者都能被从中断级别代码调用。回调程序被传递单个参数,其是与被传递给callbackRequest相同参数,即:CALLBACK结构体地址。第二个例程是调用callbackSetProcess和callbackRequest的一个快捷方式。以下的延时版本,在为相关联任务排队待执行的回调例程前等待指定时间。

callbackRequestDelayed(CALLBACK *pCallback, double seconds);
callbackRequestProcessCallbackDelayed(CALLBACK *pCallback, int Priority, void *pRec, double seconds);

这些程序不能被从中断级别代码调用。

2.2 语法

提供了以下调用:

void callbackInit(void);

void callbackSetCallback(void *pcallbackFunction, CALLBACK *pcallback);
void callbackSetPriority(int priority, CALLBACK *pcallback);
void callbackSetUser(void *user, CALLBACK *pcallback);
void callbackGetUser(void *user, CALLBACK *pcallback);
void callbackSetProcess(CALLBACK *pcallback, int Priority, void *prec);

void callbackRequest(CALLBACK *);
void callbackRequestProcessCallback(CALLBACK *pCallback, int Priority, void *prec);
void callbackRequestDelayed(CALLBACK *pCallback, double seconds);
void callbackRequestProcessCallbackDelayed(CALLBACK *pCallback, int Priority, void *prec, double seconds);
void callbackCancelDelayed(CALLBACK *pcallback);
int callbackSetQueueSize(int size);

1) callbackInit在IOC初始化时被自动执行,因而用户代码从不调用这个函数。

2) callbackSetCallback, callbackSetPriority, callbackSetUser和callbackGetUser实际上是宏。

3) callbackReqeust和callbackRequestProcessCallback都可以被从中断上下文被调用。

4) callbackRequest例程的延时版本在排队这个回调前等待指定时间。

5) callbackCancelDelayed可以用于取消一个中断回调。

callbackSetCallback(ProcessCallback, pCallback);
callbackSetPriority(Priority, pCallback);
callbackSetUser(pRec, pCallback);
callbackRequest(pCallback);

例程ProcessCallback被设计成异步设备完成并且被定义为:

static void ProcessCallback(CALLBACK *pCallback)
{
    dbCommon    *pRec;
    struct rset *prset;
 
    callbackGetUser(pRec, pCallback);
    prset = (struct rset *)pRec->rset;
    dbScanLock(pRec);
    (*prset->process)(pRec);
    dbScanUnlock(pRec);
}

2.3 一个使用回调的示例

以下是一个回调任务的示例用法的代码:

#include <callback.h>

static structure {
    char      begid[80];
    CALLBACK callback;
    char     endid[80];
}myStruct;

void myCallback(CALLBACK *pcallback)
{
    struct myStruct *pmyStruct;
    callbackGetUser(pmyStruct,pcallback)
    printf("begid=%s endid=%s\n",&pmyStruct->begid[0],

    &pmStruct->endid[0]);
}
example(char *pbegid, char*pendid)
{
    strcpy(&myStruct.begid[0],pbegid);
    strcpy(&myStruct.endid[0],pendid);
    callbackSetCallback(myCallback,&myStruct.callback);
    callbackSetPriority(priorityLow,&myStruct.callback);
    callbackSetUser(&myStruct,&myStruct.callback);
    callbackRequest(&myStruct.callback);
}

我在Linux系统上实现以上示例的过程以及详细代码在下面展示:

第一步:建立应用程序目录,并且用makeBaseApp.pl建立一个应用程序区域:

[blctrl@telecom exer]$ mkdir exer6
[blctrl@telecom exer]$ cd exer6
[blctrl@telecom exer6]$ /usr/local/EPICS/bin/linux-x86_64/makeBaseApp.pl -t ioc callbackTest
[blctrl@telecom exer6]$ /usr/local/EPICS/bin/linux-x86_64/makeBaseApp.pl -i -t ioc callbackTest

第二步:进入callbackTestApp/src/目录添加以下两个文件mycallbackTest.c和mycallbackTest.dbd:

mycallbackTest.c

#include <stdio.h>
#include <unistd.h>
#include <callback.h>
#include <string.h>

#include <epicsExport.h>
#include <iocsh.h>


struct myStruct {
        char begid[80];
        CALLBACK callback;
        char endid[80];
};

static struct myStruct MyStruct;

void mycallback(CALLBACK * pcallback)
{
        struct myStruct * pmyStruct;
        callbackGetUser(pmyStruct, pcallback);
        printf("begid=%s  endid=%s\n", (char *)&pmyStruct->begid[0], (char *) &pmyStruct->endid[0]);
}

void callbackTest(char * pbegid, char * pendid)
{
        strcpy(&MyStruct.begid[0], pbegid);
        strcpy(&MyStruct.endid[0], pendid);
        callbackSetCallback(mycallback, &MyStruct.callback);
        callbackSetPriority(priorityLow, &MyStruct.callback);
        callbackSetUser(&MyStruct, &MyStruct.callback);
        printf("start callback:\n");
        callbackRequest(&MyStruct.callback);
        sleep(2);
        printf("callbackTest finished!\n");
}


static const iocshArg     callbackArg0 = {"pbegid", iocshArgString};
static const iocshArg     callbackArg1 = {"pendid", iocshArgString};
static const iocshArg    *callbackArgs[] = {&callbackArg0, &callbackArg1};
static const iocshFuncDef callbackFuncDef = {"callbackTest", 2, callbackArgs};

/* Wrapper called by iocsh, selects the argument types that hello needs */
static void callbackCallFunc(const iocshArgBuf *args) {
   callbackTest(args[0].sval,args[1].sval);
}

/* Registration routine, runs at startup */
static void callbackRegister(void) {
    iocshRegister(&callbackFuncDef, callbackCallFunc);
}
epicsExportRegistrar(callbackRegister);

mycallbackTest.dbd

registrar(callbackRegister)

第三步:编辑相同目录下的Makefile文件,添加以下两行:

# Include dbd files from all support applications:
callbackTest_DBD += mycallbackTest.dbd    # 添加行1
callbackTest_SRCS += mycallbackTest.c     # 添加行2

第四步:回到这个应用程序的顶层目录,即exer6,执行make。

第五步:进入iocBoot/ioccallbackTest/,执行启动命令:

[blctrl@telecom exer6]$ cd iocBoot/ioccallbackTest/
[blctrl@telecom ioccallbackTest]$ ../../bin/linux-x86_64/callbackTest st.cmd

第六步:用help命令检查命令是否成功向ioc shell注册,并且使用这个命令:

epics> help callbackTest
callbackTest pbegid pendid
epics> callbackTest mystart myend
start callback:
begid=mystart  endid=myend
callbackTest finished!

这个简单示例展示了如何用你自己的在任意位置包含CALLBACK结构体的结构体使用回调任务。

2.4 回调队列

回调请求放置每种回调优先级的请求到ige单独的环形缓存。这些缓存默认能够最多保存2000个请求。通过在启动文件中在iocInit前调用callbackSetQueueSize更高这个限制。语法是:

int callbackSetQueueSize(int size)

3 任务看门狗

EPICS提供一个作为用于其它任务的看门狗的任务。任何任务可以请求被监视,并且大部分IOC任务做这件事。在IOC中一个状态监控子系统可以注册被通知有关发生的任何变化。看门狗任务周期地运行并且检查在其任务列表中的每个任务。如果任何任务被暂停,一条错误消息被显示并且进行任何通知。任务看门狗提供以下特性:

1) 包含模块:

#include <taskwd.h>

2) 由一个任务请求被监视:

taskwdInsert (epicsThreadId tid, TASKWDFUNC callback, VOID *usr);

这添加指定tid的任务到被监视的任务列表,并且发出一个新任务已经被注册的任何请求的通知。如果tid为0,使用进行调用的线程的epicsThreadId。如果callback非NULL,并且之后此任务被暂停了,将用单个参数usr调用回调例程。

3) 从列表移除任务:

taskwdRemove(epicsThreadId tid);

这个例程必须在受监视的任务退出前被调用。它发出任何请求的通知并且从被监视的任务列表移除任务。如果tid是0,使用进行调用的线程的epicsThreadId替代。

4) 请求收到变更通知:

typedef struct {
    void (*insert)(void *usr, epicsThreadId tid);
    void (*notify)(void *usr, epicsThreadId tid, int suspended);
    void (*remove)(void *usr, epicsThreadId tid);
} taskwdMonitor;

taskwdMonitorAdd(const taskwdMonitor *funcs, void *usr);

此调用为任务看门狗提供一个回调集合,当一个任务被注册或移除或者当任何任务被暂停时,任务看门狗去调用这个回调集合。在注册时给出的usr指针和此通知有关的线程的tid一起被传递给了这个回调。在很多情况,insert和remove回调将从此线程自身的上下文被调用,虽然这不受到确保(例如,注册可以由一个父线程进行的)。notify回调也指明任务进入或者退出暂停;它在两种情况中被调用,不同于向taswdkInsert和taskwdAnyInsert。

5) 撤销通知请求:

taskwdMonitorDel(const taskwdMonitor *funcs, void *usr);

此调用移除一个先前注册的通知。funcs和usr必须匹配原先注册时传递给taskwdMonitorAdd的值。

6) 打印一个报告:

taskwdShow(int level);

如果level是0,显示任务数目和注册的monitors。对于更大的值,在表格中也显示注册任务名称和它们的当前状态。

7) 为了向后兼容性目的提供了以下例程,但现在过时了:

taskwdAnyInsert(void *key, TASKWDANYFUNC callback, VOID *usr);

当正在被任务看门狗监视的任意任务被暂停时,将调用这个回调例程。key必须有一个唯一值,因为在调用taskwdAnyRomve时,任务看门狗系统使用这个值要移除哪个条目。

taskwdAnyRemove(void *key);

key是被传递给taskwdAnyInsert的相同值。

猜你喜欢

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