1 介绍
EPICS IOC Shell是一个简单的命令解释器,它提供了vxWorks Shell功能的一个子集。它被用于解释启动脚本(st.cmd)以及执行在console终端输入的命令。在大多数情况中,vxWorks启动脚本能够不用修改地被IOC Shell解释。本文以下部分从用户和程序员视角描述IOC Shell的操作。
2 IOC Shell的操作
IOC Shell读取输入行,展开环境变量参数,把行分解成命令和参数,介质调用对应编写命令的函数。由一个或多个'空白'字符分隔命令和参数。解析成空白的字符包括实际空白字符和tab字符以及逗号和开和闭的小括号。因而,命令行:
dbLoadRecords("db/dbExample1.db","user=mrk")
将被IOC shell解析成dbLoadRecord命令和参数db/dbExample1.db和user=mrk。
不识别的命令产生一条诊断信息,但在其它方面被忽略。缺失参数被赋予一个默认值(对于数值参数为0,对于string参数为NULL)。忽略其它参数。
与vxWorks不同,string参数不是必须被包围在引号中,除非这些参数包含一个或多个空白字符,在这种情况下,必须使用在下面描述的其中一种引用机制。
2.1 环境变量展开
不是以一个注释字符(#)开始的输入行被搜索格式$(name)或${name}的宏引用。对应在macLib功能的文档描述了对应宏引用的一些可能语法变化。在任何其它运行发生前,这样的引用被它们指定名称的环境变量的值替代。宏展开是递归的,因而,例如:
epics> epicsEnvSet v1 \${v2}
epics> epicsEnvSet v2 \${v3}
epics> epicsEnvSet v3 somePV
epics> dbpr ${v1}
将打印有关somePV过程变量的信息--传给dbpr命令的${v1}参数展开成了${v2},${v2}展开为${v3},${v3}展开成了somePV。在定义中需要反斜杠来延迟以下变量的替代,否则在运行epicsEnvSet命令前,执行这个替换。
出现在单引号中的宏引用不被展开。
2.2 引用
引用用于移除通常赋给某个字符的特殊含义,并且可以用于在参数中包含空白或引号字符。引用发生在以上宏展开被执行后,并且不能用于在多个输入行上扩展命令。
有三种引用机制:反斜杠字符,单引号和双引号。一个反斜杠(\)保留了随后字符的字面值。包含在单或双引号中的字符在引号中每个字符的字面值(包括反斜杠)。单引号可以出现在双引号之间以及双引号可能出现在单引号之间。注意:从shell调用的命令可以在它们的命令行字符串上执行其它的取消转义和宏扩展。
2.3 命令行编辑和历史
IOC Shell可以使用readline或tecla库从console终端获取输入。这通过这些库提供的命令行历史功能,提供了完全的命令行编辑以及对先前命令的简单访问。需要完整细节,参考readline或tecla库文档。不支持命令和参数补全。
如果未使用readline或tecla库,仅有的命令行编辑和历史功能将是底层操作系统提供的那些。例如,在Windows中console键盘提供了其自身的命令行编辑和历史命令。在wxWorks上,使用ledLib命令行输入库例程。
2.4 重定向
IOC Shell识别Unix Shell I/O重定向操作符的一个子集。重定向操作符可以在一条命令前,或者一条命令内任何位置,或者跟着一条命令。重定向按它们出现的顺序从左到右被处理。不能打开或者创建一个文件引起这个重定向失败并且此命令被忽略。
输入重定向使得其名称来自filename被展开的文件被打开用于在文件描述符上读取,或者如果未指定,标准输入(文件描述符0)。用于重定向输入的一般格式是:
[n]<filename
作为一种特殊格式,IOC shell自身识别以从filename读取命令直到遇到一个exit命令或EOF的一个请求出现作为一个标准输入重定向。IOC shell接着从但概念源读取命令。从filename读取的命令不被添加到readline命令历史。嵌套层级仅受限于能够同时被打开的文件最大数目。
输出重定向引起其名称来自filename展开的文件被打开用于在文件描述符n上写,或者如果没有指定n,标准输出(文件描述符1)。如果文件不存在,创建它;如果它存在,截短其为大小0。对应重定向输出的一般格式是:
[n]>filename
用于在输出末尾添加的一般格式:
[n]>>filename
用这种方式的重定向引起filename被打开用于在文件描述符n后追加,如果没有指定n,标准输出。如果文件不存在,创建它。
IOC Shell识别以下命令以及在数据库定义章节和IOC测试工具章节中描述的命令。如果包含了sequencer,则在sequencer文档中描述的命令也将被识别。
命令 | 描述 |
help [command ...] | 显示指定命令的概要。使用通配符匹配,所以'help db*'显示所有以字面'db'开头的所有命令的概要。不带参数,这显示一个所有命令的列表。 |
# | 一个'#'作为一行上第一个非空白字符标记一个注释的开始,这持续到本行结尾(但某些Base版本可能在'#'字符后一个空白来适当地识别它为一个注释) |
exit | 停止读取命令。当顶层命令解释器遇到一个exit命令或者end-of-file(EOF),它返回到其调用者。 |
cd directory | 更改工作目录为这个目录。 |
pwd | 输出工作目录的路径。 |
var [name [value]] | 如果两个参数都出现,分配这个值到指定名称的变量。如果仅出现name参数,打印那个变量的当前值。如果没有参数出现,打印向shell注册的所有变量的值。在应用程序数据库定义中使用variable关键字注册变量。 |
show [-level] [task ...] | 显示有关指定任务的信息。如果没有任务出现,显示所有任务的信息。level参数控制打印的信息量。默认level是0。任务参数可以是任务名称或者任务i.d.号。 |
system command_string | 发送command_string给系统命令解释器执行。只在某个应用程序数据库定义文件包含registrar(iocshSystemCommand)并且系统提供了合适的命令解释器时,这条命令才出现。 |
epicsEnvSet name value | 设置环境变量为指定的值。 |
epicsEnvShow [name] | 如果没有指定name,将显示所有环境变量的名称和值。如果指定了name,将显示那个环境变量的值。 |
epicsParamShow | 显示所有EPICS配置参数的名称和值。 |
iocLogInit | 初始化IOC日志。 |
epicsThreadSleep sec | 暂停IOC shell执行sec秒。 |
var命令是为了用于简单的应用程序,诸如设置调试标记的值。需要更复杂表达处理的应用程序应该使用cexp包。
显示周期活动报告的spy命令在RTEMS上可用,作为RTEMS_UTILS支持模块的组成。要添加这条命令到一个应用程序,必须做以下更改。
2.6 环境变量
IOC Shell使用以下环境变量来控制其运行。
变量 | 描述 |
IOCSH_PS1 | 提示字符串。默认是``epics> ''. |
IOCSH_HISTSIZE | 要记住先前命令行数目。如果IOCSH_HISTSIZE环境变量没有出现,HISTSIZE环境变量的值被使用。如果两个环境变量都没有,将记住10条命令。 |
TERM, INPUTRC | 这些和其它环境变量被readline和termcap库使用,并且在对应那些库文件的文档中被描述。 |
IOC shell不提供用于条件执行命令的操作符,但能够使用宏展开模拟这个作用。最简单的技术是在一条命令前添加一个宏,它展开成'#'或('')。以下启动脚本行展示了如何这么做:
...
$(LOAD_DEBUG=#) $(DEBUG) dbLoadRecords("db/debugRec.db", "P=$(P),R=debug")
...
用正常方式启动这个IOC将造成以上行被注释掉并且debugRec.db文件被忽略:
./st.cmd
在启动这个IOC前,设置LOAD_DEBUG环境变量为一个空字符串将导致debugRec.db文件被装置:
LOAD_DEBUG="" ./st.cmd
一个类似技术可以用于条件地执行外部脚本。启动命令文件包含如下代码:
epicsEnvSet PILATUS_ENABLED "$(PILATUS_ENABLED=NO)"
...
< pilatus-$(PILATUS_ENABLED).cmd
在一个名为pilatus-YES.cmd的文件中有一个条件代码集合,而在一个名为pilatus-NO.cmd的文件中有另一个条件代码集合。这种技术可以通过为以上示例提供其它pilatus-XXX.cmd脚本被展开成一种类似C ’switch‘语句的形式。
3 IOC Shell编程
在这部分种描述的声明被包含在iocsh.h头文件种。
3.1 调用IOC Shell
调用IOC shell命令解释器的原型是:
int iocsh(const char *pathname);
int iocshCmd(const char *cmd);
传给iocsh函数的路径名参数是从其读取命令的文件的名称。如果pathname参数是NULL,从标准输入读取命令并且向终端发出提示。读取命令直到遇到一个exit命令或者达到了文件结尾,到此,iocsh返回一个0值。如果不能打开指定的文件,iocsh返回-1。
可以从vxWorks调用IOC shell,要么从在一个vxWorks启动脚本内要么从vxWorks命令行解释器,使用iocsh "script"去从IOC shell脚本读取。也可以从vxWorks命令行解释器不带参数地调用它,在这种情况种,IOC shell接管了命令行交互的责任。
iocsh函数接收单条IOC shell命令并且执行它。可以从任何线程调用这个函数,但这些命令种很多不是线程安全的,因此这只应该被谨慎使用。这个函数从一个vxWorks启动脚本或者命令行执行单条IOC shell命令是最有用的,像这样:
iocshCmd "iocsh command string"
作为执行过程的一部分,对字符串执行以上描述的stdio流重定向和环境变量展开过程。
3.2 注册命令
在命令被IOC shell识别前,必须注册命令。通过调用这个注册函数实现注册:
void iocshRegister(const iocshFuncDef *piocshFuncDef, iocshCallFunc func);
第一个参数是一个指向一种数据结构的指针,此结构描述这条命令和它接收的任何参数。第二个参数是一个指向一个函数的指针,当遇到相应命令时,其被iocsh调用。
由iocshFuncDef结构体描述的命令:
struct iocshFuncDef {
const char *name;
int nargs;
const iocshArg * const *arg;
};
name元素是这条命令的名称。arg元素是一个指向一个指针数组的指针,指针数组中每个指针指向结构体,每个结构体定义单个参数。nargs元素声明了在指向参数描述的指针数组中条目数目。如果nargs是0,arg可以为NULL。定义每个参数的结构体是:
struct iocshArg {
const char *name;
iocshArgType type;
}iocshArg;
name元素由help命令使用来打印这条命令的概要。type元素描述了这个参数的类型并且接受以下值:
类型指定符 | 描述 |
iocshArgInt | 这个参数将被转换成一个整数值。 |
iocshArgDouble | 参数将被转换成一个双精度浮点值。 |
iocshArgString | 参数将被保留为一个字符串。用于保存这个字符串的内存是iocsh'所有',并且在处理函数返回后立即被重新使用。 |
iocshArgPersistentString | 将产生这个参数的一个副本,并且指向这个副本的指针将被传递给这个处理程序。通过使用这个指针作为参数传递给free(),被调用函数可以释放这个副本。 |
iocshArgPdbbase | 参数必须是pdbbase。 |
iocshArgArgv | 预计任意参数数目。后面的iocshArg结构体将被忽略。 |
当其相应命令被识别时被调用的'handler'函数应该是这样的形式:
void showCallFunc(const iocshArgBuf *args);
传给这个handler函数的参数是一个指向一个联合体数组的指针。在这个数组中元素数目等于在描述这个命令的结构体中指定的参数数目。包含参数值的联合体的类型和名称取决于相应参数描述符的'type'元素:
类型指定符 | 类型 | 联合体元素 |
iocshArgInt | int | args[i].ival |
iocshArgDouble | double | args[i].dval |
iocshArgString | char * | args[i].sval |
iocshArgPersistentString | char * | args[i].sval |
iocshArgPdbbase | void * | args[i].vval |
iocshArgArgv | int | args[i].aval.ac |
char ** | args[i].aval.av |
如果一个iocshArgArgv参数类型出现,它经常是为这条命令指定的第一个以及唯一参数。在这种情况下,args[0].aval.av[0]将是 这条命令的名称,args[0].aval.av[1]将是第一个参数,一次类推。
3.3 注册器命令注册
命令通常是在注册器函数中向IOC Shell注册。应用程序的数据库描述文件使用registrar关键字指定一个函数,在应用程序启动过程中其将被从EPICS初始化代码被调用。这个函数接着调用iocshRegister来向这个iocsh注册其命令。
以下代码片段展示了如何为一个示例驱动程序执行这个。
#include <iocsh.h>
#include <epicsExport.h>
/* drvXxx code, FuncDef and CallFunc definitions ... */
static void drvXxxRegistrar(void)
{
iocshRegister(&drvXxxConfigureFuncDef, drvXxxConfigureCallFunc);
}
epicsExportRegistrar(drvXxxRegistrar);
要在一个应用程序中包含这个驱动程序,开发者接着添加:
registrar(drvXxxRegistrar)
到一个应用程序数据库描述文件。
3.4 自动化的命令注册
一个C++静态构造函数可以用于在EPICS应用程序开始前注册IOC shell命令。以下示例展示如何描述和注册一个myThreadSleep命令(展示如何进行命令注册,由于原文中已经描述的如何注册的epicsThreadSleep命令已经存在,所以我在此注册了另一个相似命令,并且展示了如何定义函数以及如何注册函数的详细过程,本人已经测试成功)。
1) 在用户家目录中exer路径下新建一个exer4目录作为这个IOC程序的顶层目录,然后进入这个目录:
[blctrl@main-machine exer]$ mkdir exer4
[blctrl@main-machine exer]$ cd exer4
2) 使用base中makeBaseApp.pl程序在当前路径下建立一个IOC类型程序的目录结构:
[blctrl@main-machine exer4]$ /usr/local/EPICS/base/bin/linux-x86_64/makeBaseApp.pl -t ioc regFunc
[blctrl@main-machine exer4]$ /usr/local/EPICS/base/bin/linux-x86_64/makeBaseApp.pl -i -t ioc regFunc
Using target architecture linux-x86_64 (only one available)
The following applications are available:
regFunc
What application should the IOC(s) boot?
The default uses the IOC's name, even if not listed above.
Application name?
[blctrl@main-machine exer4]$ ls
configure iocBoot Makefile regFuncApp
3) 进入放置程序源代码的路径:
[blctrl@main-machine exer4]$ cd regFuncApp/src/
4) 在当前路径下,编写两个文件 regfunc.c和regfunc.dbd,内容如下:
regfunc.c:
# include <stdio.h>
#include <unistd.h>
# include <epicsExport.h>
# include <iocsh.h>
void myepicsThreadSleep(double time)
{
printf("I will sleep %.2f seconds\n", time);
sleep(time);
printf("I finished sleeping now!\n");
}
static const iocshArg timeArg0 = {"time", iocshArgDouble};
static const iocshArg * timeArgs[] = {&timeArg0};
static iocshFuncDef timeFuncDef = {"myepicsThreadSleep", 1, timeArgs};
static void myepicsThreadSleepCallFunc(const iocshArgBuf * args)
{
myepicsThreadSleep(args[0].dval);
}
static void myepicsThreadSleepRegister(void)
{
iocshRegister(&timeFuncDef, myepicsThreadSleepCallFunc);
}
epicsExportRegistrar(myepicsThreadSleepRegister);
regfunc.dbd:
registrar(myepicsThreadSleepRegister)
5) 编辑与添加以上两个文件相同路径下的Makefile文件,在#regFunc_DBD += xxx.dbd行下添加以下两行:
...
# Include dbd files from all support applications:
#regFunc_DBD += xxx.dbd
regFunc_DBD += regfunc.dbd # 添加行1
regFunc_SRCS += regfunc.c # 添加行2
...
6) 回到这个IOC类型程序的顶层目录,即:exer4,执行make,进行编译。
7) 编译结束后,进入iocBoot/iocregFunc/目录中,执行以下命令,启动这个IOC类型程序。
[blctrl@main-machine exer4]$ cd iocBoot/iocregFunc/
[blctrl@main-machine iocregFunc]$ ls
envPaths Makefile st.cmd
[blctrl@main-machine iocregFunc]$ ../../bin/linux-x86_64/regFunc st.cmd
#!../../bin/linux-x86_64/regFunc
< envPaths
epicsEnvSet("IOC","iocregFunc")
epicsEnvSet("TOP","/home/blctrl/exer/exer4")
epicsEnvSet("EPICS_BASE","/usr/local/EPICS/base")
cd "/home/blctrl/exer/exer4"
## Register all support components
dbLoadDatabase "dbd/regFunc.dbd"
regFunc_registerRecordDeviceDriver pdbbase
## Load record instances
#dbLoadRecords("db/xxx.db","user=blctrl")
cd "/home/blctrl/exer/exer4/iocBoot/iocregFunc"
iocInit
Starting iocInit
############################################################################
## EPICS R7.0.3.1
## EPICS Base built Sep 8 2022
############################################################################
iocRun: All initialization complete
## Start any sequence programs
#seq sncxxx,"user=blctrl"
epics>
8) 用help命令打印我们自己注册命令的使用概要,并且测试这个命令:
epics> help myepicsThreadSleep
myepicsThreadSleep time
epics> myepicsThreadSleep 2
I will sleep 2.00 seconds
I finished sleeping now!
epics>
结论: 我们成功地用iocsh注册器向IOC注册了一条自己编写的命令,并且在iocsh shell检查了这条命令的使用概要以及测试运行了这条命令。