提纲
1) 为什么在EPICS上编程
2)构建系统特性:假设基本理解Unix Make
3)在libCom中可用的工具
1) 为什么在EPICS上编程
1、社区标准:EPICS合作者知道和明白EPICS结构
2、在很多操作系统之间代码移值性
- 使得C/C++代码可移植不总是容易的。
- EPICS APIs在所有目标机上相同。
- 支持Linux,Mac,Window(MS, Cygwin, MinGW),Solaris, vxWroks, RTEMS等。
3、在操作系统之间构建的可移值性。
- 编译代码可能不是小事。
- EPICS Makefiles在所有主机上工作相同。
- 支持Linux, Mac,Windows(MS, Cygwin, MinGW),Solaris。
2) EPICS构建系统
1、用于在源代码树中Makefiles的构建规则的高级集合
- 需要GNU Make版本3.81及以上。
- 从没有见过一个相同构建规则的集合。
- 自动规则,imake,premake和Qmake都产生Makefiles
- Cmake产生Makefiles或者Visual Stdio工程文件
2、为多种目标机架构构建而设计:其它构建系统没有集成此功能
3、构建规则需要一个特定的应用程序布局
a)<top>/configure目录
- RELEASE file(s)
- 若干CONFIG*和RULES*文件
b) Makefiles必须有特定的内容
- 设置TOP变量
- 包含特定的CONFIG和RULES文件
Makefiles
1) 四种不同类型,与此角色相关联
顶层:
用于向下进入子目录(支持额外的构建目标)
结构的:
用于向下进入子目录。
条件的
用于构建软件
启动
用于iocBoot/ioc目录
2) 不同
a) 它们包含的那些configure/RULES*
b) 那些规则检查来控制它们想做什么的那些变量
顶层Makefile
1) <top>/Makefile
TOP = .
include $(TOP)/configure/CONFIG
DIRS = list of subdirectories
aslo set *_DEPEND_DIRS here
include $(TOP)/configure/RULES_TOP
2) 在第二个include行后添加任何其它的规则
3)DIRS变量列出了要在其内递归构建的所有子目录。
4)*_DEPEND_DIR变量控制子目录构建的顺序。
- 示例:test_DENPEND_DIRS = configure src
- test子目录将在configure和src目录后被构建。
- 设置*_DEPEND_DIR变量对于并行构建重要:运行'make -j'将在所有子目录中同时构建
结构化的Makefile
1) 只用于向下进入子目录的Makefiles
TOP = .. 根据适合调整路径
include $(TOP)/configure/CONFIG
DIR = list of subdirectories
aslo set *_DEPEND_DIR here
include $(TOP)/configure/RULES_DIRS
2) 非常类似顶层Makefile
- 像之前设置DIRS和*_DEPEND_DIRS变量
- 包含RULES_DIRS替代RULES_TOP
3) 示例
- <top>/exampleApp
- <top>/iocBoot
启动的Makefiles
1) 用于iocBoot/ioc目录的Makefiles
TOP = ../..
include $(TOP)/configure/CONFIG
ARCH = ioc目标架构
TARGETS = 待构建的其它文件
include $(TOP)/configure/RULES.ioc
2) ARCH设置控制产生的TARGETS的内容
3)已知TARGETS是
- cdCommands:只用于vxWorks
- envPahts:用于其它操作系统
- dllPath.bat:用于windows架构
- relPath.sh:用于cygwin
4) 可以在第二个include行后添加用于其它目标的规则。
构建的Makefiles
1) 用于编译软件的Makefiles
TOP = ../.. 视情况而定调整路径
include $(TOP)/configure/CONFIG
在这里设置变量
include $(TOP)/configure/RULES
此处添加额外的规则和依赖
2)构建的Makefiles必须被命名为'Makefile',不要’GNUmakefile‘或'makefile'。
3)很多变量可用于控制构建什么
完整列表见应用开发者手册的第四章。
构建和/或安装什么
1)受到一个命名最终产品的变量集合控制
- INC:C/C++头文件(.h)
- LIBRARY:静态的或者共享对象库(lib.a lib.so .dll)
- LOADABLE_LIBRARY:共享对象库(lib.so .dll lib.dylib)
- PROD:可执行的程序(.exe)
- TESTPROD:可执行程序,但不安装。
- OBJS:对象文件(.o)
- SCRIPTS:被解析的脚本。
- DBD:数据库定义文件(.dbd)
- DBDINC:记录类型或者菜单数据库定义文件
- DB:数据库实例文件(.db .vbd)
- TARGETS:其它构建目标,可能需要构建规则,在很多情况中,你使用的名称不应该包括前缀/后缀
2)指定名称的对象按照它们被构建被复制到合适的安装目录。例如:<top>/include, <top>/lib/<arch>, <top>/bin/<arch>, <top>/dbd和<top>/db
限制构建
1)通过使用这些变量限制构建目标为(所有)主机架构:
PROD_HOST, TESTPROD_HOST, LIBRARY_HOST, LOADABLE_LIBRARY_HOST, OBJS_HOST, SCRIPTS_HOST
2) 通过使用这些变量限制构建为(所有)IOC架构:
PROD_IOC, TESTPROC_IOC, LIBRARY_IOC, OBJS_IOC, SCRIPT_IOC
3) 通过使用这些变量限制构建为OS-specific架构:
- PROD_<osclass>, TESTPROD_<osclass>, LIBARY_<osclass>, LOADABLE_LIBRARY_<osclass>
- <osclass>可以是Linux, vxWorks, Win32, Darwin, RTEMS或solaris
- 示例,仅为嵌入的目标构建库
LIBRARY_vxWorks = myDev
LIBRARY_RETMES = myDev
指定源文件名称
1) 如果一个Makefile仅创建一个目标(库,可执行文件等),你可以添加所有源文件的名称到SRCS变量:
SRCS = myDev.c myDrv.c
2) 如果一个Makefile仅创建一个库,你可以添加所有库源文件的名称到LIB_SRCS变量:
LIBRARY = myDev
LIB_SRCS += myDev.c myDrv.c
3) 如果一个Makefile仅创建一个可执行文件(PROD),你可以添加所有它的源文件的名称到PROD_SRCS变量:
PROD = myIOC
PROD_SRCS += myMain.c mySeq.st
4) 但使用<name>_SRCS变量通常最好:
LIBRARY = myLib
myLib_SRCS = parser.c scanner.cpp process.cpp
PROD = myTool
myTool_SRCS += tool.c
操作系统特定的源文件
1) 你可以在源文件变量名称末尾添加_<osclass>来限制在什么OS上构建此代码:
- SRCS_<osclass>
- LIB_SRCS_<osclass>
- PROD_SRCS_<osclass>
- <name>_SRCS_<osclass>
2)当设置_<osclass>变量时,重要的_DEFAULT变量用于所有不带_<osclass>版本的OS的构建
3)示例:
LIBRARY = myDev
LIB_SRCS = myDev.c
LIB_SRCS_vxWorks = devVx.c
LIB_SRCS_RTEMS = devRtems.c
LIB_SRCS_DEFAULT = devPosix.c # Linux, Darwin, Solaris
LIB_SRCS_WIN32 = -nil-
源文件位置
1) 通常源文件出现在与Makefile相同目录中
2)可以告诉make从附近目录搜索源文件
SRC_DIRS += <dir>
此处<dir>是从O.<arch>构建目录到包含此源文件的目录的相对路径。
3)可以使用代码的多个OS-Specific实现。
4)在一个或多个这些子目录中放置源文件。
- os/<osclass>:OS-specific版本
- os/posix:基于Posix的OS的版本(Linux,Unix,Darwin,RTEMS)
- os/default:最后机会的通用版本。
5)相同源文件文件名应该用于所有版本。
C/C++编译器标记
1)为编译器命令行添加标志的很多方法,例如:
- USR_CFLAGS:所有C编译
- USR_CXXFLAGS:所有C++编译
- USR_CPPFLAGS:C预编译器标志
- USR_CFLAGS_<osclass>:用于<osclass>的所有C编译。
- USR_CXXFLAGS_<osclass>:用于<osclass>的所有C++编译。
- USR_CFLAGS_<arch>:用于<arch>的所有C编译。
- USR_CXXFLAGS_<arch>:用于<arch>的所有C++编译。
- <name>_CFLAGS:编译<name>.c
- <name>_CFLAGS_<osclass>:为<osclass>编译<name.c>
- <name>_CFLAGS_<arch>:为<arch>编译<name.c>
2) 包含文件搜索路径有它们子集的变量
- USR_INCLUDES, USR_INCLUDES_<osclass>, <name>_INCLUDES, <name>_INCLUDES_<osclass>, <name>_INCLUDES_<arch>
- 在INCLUDES中指定名称的每个目录前需要一个’-I‘标记。
与库文件链接
1)由在configure/RELEASE文件中被列出的其它EPICS模块提供的库文件通常将被自动找到
构建系统自动搜索那些lib/<arch>目录以及<top>/lib/<arch>目录。
2)如果一个库文件被放置在其它地方,Makefile必须指定在何处
设置变量<name>_DIR为这个库的绝对路径
例如:
LIBS += usb
usb_DIR = /opt/local/lib
3) 如果它来自一个非EPICS包,使用<top>/configure/CONFIG_SITE来设置此路径为那个包:
不要让用户必须编辑Makefiles来能够构建此代码
在Makefiles中的条件
1) 使用GNU Makefile条件来调整构建
- configure/CONFIG文件包含<top>/configure/RELEASE和<top>/configure/CONFIG_SITE文件
- 如果构建依赖可选模块是否可用,使用configure/RELEASE变量
- 在CONFIG_SITE中使用变量来让用户使能/禁用特性
ifdef SNCSEQ
在这里用于与sequencer一起构建的行
else
在这里用于没有与sequencer一起构建的行
endif
ifeq ($(BUILD_IOCS), YES)
在这里用于构建IOCs的行
else
在这里用于不构建IOCs的行
endif
libCom -- 通用工具库
1) 这个库有两个主要功能
- 在所有支持的OS之间提供一个共同的操作系统API
- 为由IOC,通道访问和其它程序使用实现了更多通用工具。
2)base/src/libCom包含了159个C/C++头文件(3.14.12.5)
3)没有时间在这里讨论或甚至提到
4)主要功能在IOC应用程序开发者指南章节中讨论了:
- IOC错误日志记录
- 任务看门狗
- IOC shell
- libCom
- libCom OSI库文件
- Registry
对应C代码的libCom亮点
1) 多线程和线程间通信
- epicsThread, epicsMutex, epicsEvent
- epicsRingBytes, epicsRingPointer, epicsMessageQueue
2)进程通信和字符串转换
- epicsStdio, epicsStdlib, epicsString
- osiSock
- errlog & logClient
- macLib
3) 数据类型和结构体
epicsTypes, ellLib, gpHash
4) 数学
calc引擎, epicsMath, epicsEndian
5) 共享库
shareLib.h和epicsExport.h
多线程
1) epicsThread.h提供了一个通用的线程API
线程创建(名称,优先级,栈大小,函数,参数)
- 如果支持,OS线程优先级映射到范围low=0 .. high=99
- 栈大小是OS和架构相关的:Small, medium, large
支持的线程操作:
- sleep(延时), 挂起,继续,获取名称,获取id,休眠量,显示
- 没有远程杀死一个线程的API,例程必须为要退出的线程返回
C++包装类
2)线程私有变量
变量操作:创建,销毁,get,set
3)线程onceAPI
确保初始化函数的执行仅一次
被其它线程执行初始化函数的并行尝试将在这个函数在第一个线程中返回前推迟它们。
互斥和事件信号
1) epicsMutex.h
- 互斥信号量
- 支持递归锁
- 来自OS可用的优先级继承和删除安全性。
- 互斥操作:创建,销毁,锁定,解锁,尝试锁定,显示
- C++包装类
2)3.15 epicsSpin.h:spin-lock信号量,基于epicsMutex C API。
3)epicsEvent.h
- 二进制信号量
- 事件操作:创建,销毁,信号,等待,尝试等待,无超时等待,显示
- C++包装类
环形消息缓存
1) epicsRingBytes.h
- 固定大小环形缓存
- 支持变长度消息
- 如果需要,调用者必须实现锁:如果单写入线程,在写入时不需要锁定;如果单读取线程,读取时不需要锁定;Base 3.15提供一个可选的内部spin-lock
- 缓存操作:创建,删除,put,get,flush
- 状态请求:size,is full, is empty, 使用字节,空闲字节。
用于指针的环形缓存
epicsRingPointer.h
1) 固定大小的环形缓存
2)仅支持单指针消息
3)调用者必须根据需要实现锁定
- 如果单写入线程,在写入时不需要锁定
- 如果单读取线程,在获取时不需要锁定
- Base 3.15提供了一个可选的内部spin-lock
4) C++包装类
5)缓存操作:
- 创建,删除,push,pop,flush
- 3.15:create-locked
6) 状态查询:
- 大小,是满,是空,已用字节,空闲字节
消息队列
epicsMessageQueue.h
1) 固定大小的队列
2)支持变长消息
3)为与多个读取和写入线程而设计
4)C++包装类
5)队列操作:创建,销毁,发送,尝试发送,无超时发送,接收,尝试接收,不超时接收
6)状态查询:pending, show
对<stdio.h>的包装
epicsStdio.h(包含了stdio.h)
1) epicsSnprintf() & epicsVsnprintf()
- 实现或者对C99的snprintf()&vsnprintf()函数的包装
- 确保所有操作系统表现基本相同
2)用于重定向stdin, stdout, stderr流的基础设施
- 用于每个流的每线程设置(主要用于iocsh):epicsGetThreadStdinI(), epicsSetThreadStdin()等
- 包含epicsStdioRedirect.h来重新定义标识符stdin, stdout&stderr和函数printf(), puts()和putchar():在3.15中,这个头被合并到了epicsStdio.h
3)各种文件和文件名函数:推荐不使用这些老的APIs
标准库
epicsStdlib.h(包含stdlib.h)
1) epicsStrtod()
- 同义或者对strtod()的包装
- 确保所有操作系统表现相同,支持NaN和Inf字符串
2)epicsScanDouble() epicsScanFloat()
确保sscanf("%f")和sscanf("%lf")支持NaN和Inf字符串。
3)3.15:epicsParse函数系列
- 用于转换字符串为所有数值类型
- 可选的单位字符串捕获
- 错误检查包括值向上溢出和向下溢出
- 所有函数返回一个状态值(错误代码)
字符串处理
epicsString.h
1) 编译有用字符串函数:
- 从raw转换字符串到转义的C类型
- 从转义的C类型转换字符串为raw
- 用转义的不可打印字符打印字符串
- shell团聚匹配(此字符串匹配通配符模式吗?)
- 计算字符串的哈希值和内存缓存
2)这些替代了不是在所有操作系统上可用的标准例程
- 大小写无关的字符串比较(strcasecmp, strncasecmp)
- 可再入字符串tokenization(strtok_r)
- 字符串复制(strdup)
宏替换
macLib.h
1) 通用宏替换库
2)支持多种变量范围,递归宏,...
3)也处理环境变量
4)操作:创建上下文,启用/禁用警告,删除上下文,获取宏值,设置宏值,push scope,pop scope,解析宏定义,安装被解析的定义,展开字符串,用环境变量展开字符串,报告上下文。
5)高效,可高,经过充分测试
6)更详细见base/src/libCom/macLib中macLibREADME文件。
网络套接字API
osiSock.h
1) 提供用于创建和使用网络套接字的统一API:对于Windows,Solaris等不需要特殊的应用程序代码。
2)被Base(CA客户端和服务器,日志客户端和服务器等),Asyn,pvAccess使用。
3)使用特别广泛,可靠。
4)为常见任务提供若干例程
- 查询可用的网络接口
- 创建套接字,绑定到地址,监听连接,ioctl, 销毁
- 配置用于广播UDP的套接字
- 转换套接字或IP地址为ASCII(DNS和数值)或者从ASCII(DNS和数值)转成套接字或IP地址
- 如何解冻被阻塞从一个套接字读取的线程
- 查找套接字错误消息字符串。
报告和记录错误
1)errMdef.h errlog.h
提供用于与错误处理和日志相关目的的APIs
- 关联和查找有错误状态值的字符串
- 用于错误数值前缀的标准
- 记录和flush错误消息
- 转发记录的错误到一台远程服务器的监听程序
- 在调试口启用/禁用记录消息的显示
2)iocLogServer
- 用于IOCs和应用程序记录错误消息到其的服务器应用程序。
- 存储消息在环形文件中(可配置固定最大尺寸)
- 也支持记录文件目录轮转
3) IOC应用程序开发手册中第10章详细描述。
标准类型
epicsTypes.h
1)各种大小的EPICS标准类型定义
2)以前的C99,以及我们支持的操作系统的一些
3)定义这些标准类型:epicsInt8, epicsUInt8, epicsInt16,epicsUInt16, epicsInt32, epicsUInt32, epicsFloat32, epicsFloat64 epicsEnum16; 3.15:epicsInt64, epicsUInt64
4)epicsInt8总是'char',因而可能在某些架构上是无符号的。
5) 也定义了:
- MAX_STRING_SIZE(40)
- epicsFalse(0)
- epicsTrue(1)
- stringOf(token)
链表
ellLib.h
1) 双链表管理程序
2)插入式 - 链接对象必须包含一个ELLNODE, 不需要额外内存
3)操作:初始化列表, 对象计数, 第一个对象,末尾对象,下一个对象,前一个对象,添加对象,连接链表,删除对象,提取对象,获取第一个对象,插入对象,获取第n个对象,步进n个对象,在链表中查找对象,释放所有对象,验证链表有效性。
4)链表和节点会被静态地初始化
5)高效,可靠,经过充分测试
6)模仿vxWorks lstLib的API
哈希表
gpHash.h
1) 用于通过名称快速对象查找的通用哈希表
2)在初始化时固定的buckets数目:2的指数,从256到65536
3)在一个哈希表中可以存储多个对象类型:传入一个类型ID(指针)来区分,在哈希计算中被包含
4)非-obtrusive, 表分配节点对象
5)线程安全的访问,表包含一个互斥锁
6)操作:创建表,添加指定名称的对象,通过名称查找,通过名称删除,释放表,转存内容
计算引擎
1)表达式编译器和计算引擎
2)编译数学表达式为一个私有的后缀字节代码格式:支持大多数标准C操作,在语法中小差别
3)用指定的输入值集合(double)快速执行字节代码
4)被Base中calc,calcout,transform记录,areaDetector插件使用。
5)检查输入值的例程被编译的表达式使用和修改。
6) 操作:编译表达式,执行计算,参数使用,转存字节码
7)高效,可靠,经过充分测试
8)在IOC应用程序开发者手册中详细描述
其它头文件
1) epicsMath.h
- 包含math.h
- 定义epicsINF和epicsNAN
- 确保finite(), isnan()和isinf()都被定义
2)epicsEndian.h
a)定义4个数值宏
- EPICS_ENDIAN_LITTLE
- EPICS_ENDIAN_BIG
- EPICS_BYTE_ORDER
- EPICS_FLOAT_WORD_ORDER
b) 两个_ORDER宏是架构专用,并且应该与前两个做比较来确定CPU的大小端
库导出和导入
1) shareLib.h
a) 定义用于标记库符号的若干宏,对windows DLLs必不可少
- epicsShareFunc:要被导出/导入的函数
- epicsShareClass:要被导出/导入的类
- epicsShareExtern:'extern'变量声明
- epicsShareDef:变量定义
- epicsShareAPI:在windows上函数使用__stdcall调用规则
b) 取决于是否定义了宏epicsExportSharedSymbols以及编译器是否正在构建DLL或一个静态(archive)库,这些定义不同。
2) epicsExport.h
- 定义epicsExportSharedSymbols,接着包含shareLib.h
- 为IOC注册定义了一些其它宏:epicsExportAddress, epicsExportRegistrar(), epicsRegisterFunction()
合适地使用shareLib.h
1) 库头文件应该:在这个头文件中包含这些声明所需的shareLib.h以及任何其它头文件,接着使用合适的epicsShare关键字来装饰这个头的声明。
2)库实现应该:
- 包含用于此代码将成为其组成部分的库外找到代码的所有所需头。被这个模块头文件包含的外部头也必须被包含在这里。
- # define epicsExportSharedSymbols
- 包含用于此代码将成为其组成部分的库内找到代码的所有所需头。
2)实现包含epicsExport.h而不是定义宏epicsExportSharedSymbols。
单元测试
1) epicsUnitTest.h
- 单元测试报告库
- 产生测试任何东西协议(TAP)标准输出
- 与在工作站上构建系统'runtests'和'tapfiles'目标一起运行
- 在运行在嵌入操作系统时,内建测试使用功能
- 操作:plan,ok,pass,fail,skip,todo,diagnostic, abort, done
2) testMain.h
定义一个宏MAIN(),允许测试在工作站上以程序被构建以及在嵌入操作系统上以函数被构建
3)构建系统变量TESTPROD, TESTSCRIPT(3.15:TESTLIBRARY构建程序而不安装它们;测试程序通常在它们的O.<arch>目录中运行。
4)通过运行'make runtests'等添加要被运行的测试程序名到TESTS。