GCC编译器在DCS组态软件中的应用
2008-03-26
作者:刘金龙
摘 要:本文介绍了ConMaker编译功能的设计与实现,对ConMaker编译器的各个子模块的实现分别进行了说明,阐述了如何应用GCC实现ConMaker的编译功能。
关键词:DCS,GCC,编译,编译器
北京和利时系统工程股份有限公司是以工业过程控制为基础的企业,公司2001年从德国3S公司引进了控制编程软件CoDeSys。实践证明,CoDeSys是一款符合IEC61131-3标准的、功能强大、运行效率较高的算法组态软件" title="组态软件">组态软件。但是CoDeSys也存在着不足之处,如对组态人员计算机素质要求较高,缺少参数回读等DCS系统必备的功能。另外,由于知识产权保护等各方面原因,包括CoDeSys在内几乎所有商业化工业组态软件的源码都不公开,这在很大程度上制约了和利时公司在CoDeSys基础上进一步改进和完善的空间,从而限制了公司产品发展的可持续性。
为了提高组态软件的持续改进能力,实现控制组态软件的完全自主化,增强企业的核心竞争力,和利时公司自主研发了适合DCS工程需求的自主化组态软件ConMaker。
1 ConMaker系统介绍
ConMaker从可用性与易用性两方面考虑,在结合和利时公司以往使用与开发DCS系统的经验,借鉴国外先进工控软件优点的基础上开发的新一代组态工具。ConMaker系统分为4个主要部分:
(1) 算法编辑界面
算法编译界面包括图3-2-1中的算法编辑(ST, CFC, LD)、变量定义、硬件配置和库管理器。用户可以通过ConMaker友好的编辑界面,使用IEC61131-3标准中定义的语言进行工艺算法逻辑的编写、工程任务的配置、DP等硬件的配置及工程辅助信息的填写。
(2) 编译与执行
编译与执行部分包括预编译和编译。编辑好的组态算法通过ConMaker编译模块进行编译。编译过程先检查组态算法的逻辑错误,在进行组态的预编译和编译、链接、装载和代码整理,最后根据配置文件生成特定格式的二进制代码。编译模块配合通讯模块可实现组态工程的全下装、增量下装、强制、参数回读等功能。
(3) 通讯与在线监视
通讯与在线监视部分包括参数回读、下装、在线、网络变量和通讯。ConMaker的通讯模块可将编译好的二进制代码下装到控制器上,并可周期读取监控点的实时值。ConMaker通讯模块还可以对监控的组态工程进行在线操作,如写变量、强制、任务启停等。
(4) 系统框架程序
ConMaker通过系统框架程序对各ConMaker模块进行统筹管理,处理模块间的消息传递。此外,ConMaker系统还提供了日志记录、用户管理、权限控制和支持主要功能点的快捷键操作等辅助功能,方便用户对组态工程进行操控和管理。
2 ConMaker编译功能的需求与分析
2.1 编译模块功能需求
ConMaker的编译功能需要实现,对预编译模块从用户组态的IEC算法转换得到的逻辑等价的C语言描述文件进行编译,生成二进制文件。具体实现的主要功能点有:
(1) ConMaker编辑界面定义的变量要分配在特定的内存相对地址上。
(2) ConMaker支持任务调用、过程调用、函数调用和功能块调用。
(3) 某站编译成功后ConMaker会输出该站的本次编译相关信息。编译相关信息包括:工程大小(PRG文件大小)、共有多少个POU参与编译、分配在各内存区域的变量所占空间、网络变量数目、有参数回读属性变量所占空间等。
(4) 编译并下装后的进行在线监视时可以进行读写变量、强制和强制释放等操作。
(5) 编译、下装、强制、写入等操作要记录日志。
2.2 编译模块需求分析
下面对ConMaker编译功能的需求逐条分析,达到对ConMaker编译模块的详细理解和剖析。
(1) 由于ConMaker需要和RTS搭配使用,RTS中对下装工程的变量所在的内存区域进行了限制,在RTS中变量分为Memory、Input、Output、Retain、Global五类,各类所属内存彼此独立,互相不重叠。在编译过程中需要按照预编译阶段已经指定好的各个变量的相对偏移,将各变量分配到指定的相对地址上。
(2) 由于RTS采用函数指针列表的方式来实现函数的调用和访问,所以在控制器上最终运行的机器码只能只用函数相对地址调用的方式来实现。这就要求对C文件初步编译生成汇编语言描述的.s文件进行二次处理,提取出其中函数访问的部分,然后按照函数指针列表中的相对地址更新.s文件中函数调用部分(如x86下call***语句),并对函数返回值部分的指令做相应处理。
(3) 为实现ConMaker的强制功能,需要借助强制信息文件来记录ConMaker中变量强制相关的信息。强制信息文件中包含PRG文件中可能会被真正强制的每一个变量的赋值语句的机器码信息。
2.3 选择开源" title="开源">开源编译器
ConMaker开发的关键技术之一就是编译功能的实现。由于编译器开发的代价太高,出于成本和风险控制的考虑,在现有情况下可以利用开源的编译器来实现ConMaker编译功能,借助于开源编译器实现ConMaker编译功能积累的经验,对以后自主开发安全级编译器也可起到很好的借鉴和帮助作用。
在选择开源编译器时ConMaker重点考虑编译器生成目标代码的正确性、目标代码的高效性和编译过程所用的时间。现在较常用的开源编译器有20多种,大多是由国外的高校和开源组织开发的。其中GCC是目前公认的最稳定、高效的开源编译器,运行速度快,可实现多种高级语言、多种目标代码的交叉编译,最重要的一点是使用GCC的项目和程序员非常多,经过反复的测试、应用和维护,GCC的正确性有了很好的保证。鉴于以上的情况,ConMaker选用开源编译器GCC来实现其编译功能。
3 ConMaker编译功能的设计与实现
3.1 编译模块体系结构设计与子模块划分
从ConMaker的系统结构图(图4-1)中可看到,编译模块位于ConMaker的底层,主要与预编译模块和通讯模块交互。编译模块中编译预处理相关的操作与预编译有紧密联系,编译后生成的PRG文件通过通讯模块实现控制器下装。
在ConMaker编译模块的内部,大体上可划分为5个子模块:
[1] 初始化处理模块;
[2] 编译预处理模块;
[3] 编译处理模块;
[4] 相关配置数据生成模块;
[5] 相关文件生成模块。
ConMaker编译器的子模块划分如图4-1所示。
图4-1:ConMaker编译器子模块逻辑图
3.1.1 初始化子模块
初始化子模块主要完成两个功能,一是用来实现通过ConMaker的配置文件(Target文件)对编译模块进行相关数据和编译选项的设置;二是生成根据编译配置生成一些获取工程标识号等ConMaker中必须有且功能固定的函数的二进制机器码。
3.1.2 编译预处理子模块
编译预处理主要设置函数表信息和生成工程变量赋初值的二进制机器码。
函数表信息包括工程中涉及到的每一个可能被调用的函数名称、函数索引值、函数类型、函数代码长度等信息。
由于ConMaker预编译模块生成的C文件中不包括各变量的初始化部分代码,因此要实现变量赋初值需要额外生成赋初值的机器码和重定位" title="重定位">重定位数据,这部分代码是无法借助GCC实现的。编译预处理子模块根据ConMaker预编译模块提供的变量表结合CPU类型生成变量赋初值的机器码。
3.1.3 编译处理子模块
编译处理子模块是ConMaker编译的核心。经过前期的准备和预处理,对ConMaker组态算法逻辑等价的C文件进行编译,这里需要ConMaker预编译模块保证C语言描述文件的词法、语法和语义的正确性。
由于在编译的过程中,需要调用GCC进行编译,当GCC编译结束后ConMaker才能进行后续处理,因此需单独启用一个进程完成GCC的调用,当GCC执行结束后ConMaker进行后续的处理,在GCC执行过程中ConMaker其它进程都处于等待状态。
整个编译过程分多步进行,包括汇编-〉修改汇编代码-〉编译-〉链接等。具体的处理过程如下:
3.1.3.1 生成汇编文件
调用GCC编译C文件生成汇编文件。从C文件到汇编文件的生成对编译器而言是非常重要的一步,GCC在编译C文件到汇编文件的过程中进行了词法检查、语法检查、语义分析检查、中间代码生成、中间代码优化和目标代码生成几步关键处理,首先将C文件经过前端处理生成抽象语法树AST(Abstract Syntax Tree),然后转换成中间代码的表示形式RTS(Register Transfer Language),最后经代码生成器生成最终的汇编语言描述文件。
3.1.3.2 修改汇编文件
修改汇编代码。如前文系统分析中提到的,要使GCC编译生成的汇编文件能符合ConMaker 的编译要求,需要修改汇编文件中函数调用语句的汇编代码。
由于C语言的编译和链接乃至运行过程中发生的与编译相关的错误大多与函数调用约定(Call Convertion)有关,所以这里我们对使用GCC的编译方式和C文件的语法的限定作相关说明,保证ConMaker编译、链接生成的目标代码的正确性,因此ConMaker需要修改GCC编译生成的汇编文件中所有“call _函数名”格式的指令。
3.1.3.3 生成未重定位目标代码
调用GCC编译修改后的汇编文件生成未重定位的目标代码。从汇编语言到机器语言的转换主要工作是由汇编器完成的。GCC后台调用的汇编器是as,as可编译GCC生成的AT&T格式汇编语言得到目标代码,另外as可在生成的目标代码中包含被编译程序的符号表,这可是ConMaker编译模块以后实现组态工程的调试功能打下基础。
3.1.3.4 连接
连接是编译过程中很重要的一个环节,连接负责的工作是为程序分配相关的地址和完成重定位以使编译的程序可以在目标机器上运行。在连接的过程中涉及到一些与其相关的概念。
重定位:编译器和汇编其一般在建立目标代码文件的时候都令程序的地址从零开始,但很少有计算机允许你将你的程序加载" title="加载">加载到零地址。如果一个程序由多个子程序组成,所有的子程序必须被加载到不交叉的地址中。重定位就是为程序的各个部分分配加载地址,并调整程序的代码和数据以反映已分配的地址的过程。在很多系统中,重定位发生不止一次。一个连接器从多个子程序建立一个程序并且从零开始连接输出程序非常常见,多个子程序会重定位到大程序中的指定位置。之后在程序加载时,系统会决定实际的地址,连接后的程序会作为一个整体重定位到加载地址。
符号确定:当一个程序由多个子程序构成时,一个子程序对其他子程序的引用由符号(symbol)完成;一个主程序可能要用到一个称为sqrt的平方根例程,而数学库中定义了sqrt。连接器通过计算sqrt在库中分配的位置并根据调用者的目标代码来修正这个位置,最后给call指令提供正确的地址。
加载:指将程序从其它存储器复制到内存中,另外还进行系统保护设置和将虚拟内存地址映射到磁盘页上等工作。
可完成连接功能的软件称为连接器,GCC使用的连接器是ld。像通常的连接器一样,ld通过两遍连接来完成连接工作的,以未重定位的目标文件和连接脚本、调用命名参数为输入,生成最终的目标文件和其它辅助信息。
ld的第一遍连接运行时,先扫描输入文件,以确定输出目标代码中各段(Segment)的大小,收集所有符号的定义和引用,之后,ld建立一个输入文件的框架图并记录输入文件中包含的所有段信息和输入文件的符号表。经过第一遍的连接,ld可根据连接脚本文件为符号表中的需要地址分配的变量分配数值地址,确定输出文件中包含的段及各段的大小和位置,最后标记出输出文件所包含的内容。
ld第二遍连接使用了第一遍所收集的信息,用以确定实际的连接过程。它读取并重定位目标代码,用符号引用来替换数值地址,并调整代码和数据中的内存地址以反映重定位段地址,最后将从定位代码写入到输出文件中。接下来它写出这个输出文件,通常还要加上头信息、重定位段和符号表信息。如果程序使用了动态连接,符号表还要包含能够提供信息以供运行时连接器确定动态符号所需。在很多情况下,ld本身会在输出文件中产生少量的代码或数据,诸如用于在复用或动态连接库中调用例程的“粘贴代码(glue code)”,或者指向用于在程序开始时执行的初始化例程的指针数组。
不论程序是否使用动态连接,输出文件中都会包含一个符号表用以重新连接或调试,这个符号表并不会有程序本身使用,但是其他处理输出文件的程序可能会用到。
ld工作的核心是重定位和代码修正。为重定位目标代码中的代码所使用的数据和定义的代码通常是从0开始的,要使这些代码被目标机器运行需要进行重定位和修正目标代码以反映实际情况的地址分配。在连接过程中,一些指针等数据和指令也可能会进行重定位,所以代码修正不仅影响到具体的某些指令,目标文件的数据部分中的所有指针可能都同时需要调整。
在ConMaker编译模块中,变量分为多种类型,不同类型变量分配的地址空间也不一样,可通过连接脚本来进行具体控制。
3.1.4 配置数据生成子模块
ConMaker编译组态工程最后生成的二进制文件中包含了一些工程配置信息,如组态工程中的硬件配置信息和任务配置信息、I/O点信息的数据都由配置数据生成子模块实现。这些数据主要以信息说明为主,不包含组态逻辑部分。
任务配置信息的数据按照组态工程中设置的任务信息填充,包括任务数、任务名、任务周期、调用方式、看门狗设置等按固定的格式组织,以二进制数据形式存放在CI文件中。
ConMaker的硬件配置在硬件配置模块处理,生成二进制格式数据,在编译过程中被编译模块的配置数据生成子模块获取填入到CI文件中。
I/O点信息数据是组态中参与运算的I/O点的描述信息,只有添加描述信息的I/O点才可能在实际的数据采集和输出中起作用。
3.1.5 相关文件生成子模块
在编译的过程中需要生成一些相关文件,这些文件的生成过程贯穿整个编译模块,包括最终需要的PRG文件、FV文件等与通讯相关的磁盘文件和一些编译过程中产生的临时文件。下文中将重点介绍ConMaker生成的磁盘文件的过程及各文件的作用。
ConMaker在编辑和预编译阶段将组态工程的逻辑转换成C语言的描述形式,将其保存在C文件中,文件的内容大多采用ANSI C89语法规定的规则描述,唯一的区别是C文件中采用了GCC的C语言扩充的_attribute_属性,这使得ConMaker预编译生成的C语言文件不能用其它编译器编译。
ConMaker在编译阶段以C文件为输入通过调用GCC及整理数据得到编译的临时文件.asd,如果用户此时选择对ConMaker编译结果进行保存则编译模块会将.asd文件转换成编译信息文件.ci,之后整理ci文件的内容生成PRG文件。对于工程组态中定义的所有变量类型和每个变量的具体信息,编译模块将其统一记录在符号表文件.SDB 中。
在ConMaker编译成功后,这时用户可通过ConMaker的通讯模块来操作PRG文件进行下装和在线的操作,如果进行了组态工程的下装,通讯模块会根据本次下装的PRG文件经过处理后生成下装信息文件.di,di文件将记录下本次下装所有的信息,包括控制器的IP地址、下装的工程标识及工程的组态信息等。另外,通讯模块在下装后会将强制临时文件转换成强制信息文件.fv,在下装后才进行强制信息文件的生成可以减少编译执行的时间。由于ConMaker支持增量下装的功能,所以在每次编译生成PRG文件后,编译模块会比较ci文件和di文件的数据并以此判断在下次可能进行的增量下装时需要传递的通讯内容,将这些通讯内容保存在.OL文件中。
4 总结
本文介绍了和利时自主研发的组态软件ConMaker,对其编译功能的需求进行了说明和分析,并对如何借助GCC实现ConMaker的编译功能做了详细介绍。通过一年半时间的预研和开发,经过初步测试,ConMaker编译的工程运行快速、稳定,满足实际应用的需要。
参考文献
[1] Alfred V.Aho, Ravi Sethi,Jeffrey D.Ullman著:Compilers : Principles Techniques and Tools,机械工业出版社,1987
[2] Andrew W.Appel,Maia Ginsburg著:Modern Compiler Implementation in C,人民邮电出版社,2000
[3] Brian W.Kernighan,Dennis M.Ritchi著:The C Programming Language (2nd Edition),机械工业出版社,1991
[4] John E.Hopcroft,Rajeev Motwani,Jeffrey D.Ullman著:Introduction to Automata Theory Languages and Computation (2nd Edition),机械工业出版社,1990
[5] 沈美明,温冬婵著:80x86汇编语言程序设计,清华大学出版社,2001
[6] http://gcc.gnu.org/onlinedocs/gccint/
[7] GB/T 15969.3--2005/IEC 61131-3:2002