OpenPLC源码分析(二)编译过程分析

1、整体分析OpenPLC的runtime编译st文件的过程

整体过程如上图所示,具体步骤如下。

(1)下载安装OpenPLC的runtime时需要运行以下指令

git clone https://github.com/thiagoralves/OpenPLC_v3.git
cd OpenPLC_v3
./install.sh [platform]

其中platform可以选择win/linux/docker/rpi/custom

(2)install.sh中,存储着执行background_installer.sh的命令

(3)OpenPLC文件夹中的background_installer.sh文件会根据传入的参数来选择不同的OpenPLC runtime的安装方式。

 ① 如果安装平台是win,则会执行如下代码:

 echo "Installing OpenPLC on Windows"
    cp ./utils/apt-cyg/apt-cyg ./
    install apt-cyg /bin # 安装命令行工具
    apt-cyg update
    apt-cyg install lynx
   
    apt-cyg install gcc-core gcc-g++ git pkg-config automake autoconf libtool make sqlite3 python3 # 安装OpenPLC所需的编译器、版本控制工具、构建工具和其他依赖库
    lynx -source https://bootstrap.pypa.io/pip/get-pip.py > get-pip3.py # 安装Python包管理器pip

    #Setting up venv
    python3 -m venv "$VENV_DIR"
    "$VENV_DIR/bin/python3" get-pip3.py
    "$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3

    echo ""
    echo "[MATIEC COMPILER]"
    cp ./utils/matiec_src/bin_win32/*.* ./webserver/ # 将编译器复制到webserver文件夹,包括iec2c.exe
    if [ $? -ne 0 ]; then
        echo "Error compiling MatIEC"
        echo "OpenPLC was NOT installed!"
        exit 1
    fi
 # 以上代码是在Windows上编译MatIEC编译器,并将编译后的文件复制到webserver目录
 
 # 以下代码都是在执行定义的函数
    install_st_optimizer
    install_glue_generator
    disable_opendnp3
    install_libmodbus
    finalize_install win
 
 ② 如果安装平台是linux,则会执行如下代码:
 echo "Installing OpenPLC on Linux"
    linux_install_deps sudo
    install_py_deps
    install_all_libs sudo # 函数中安装了matiec, optimizer, glue_generator...
    [ "$2" == "ethercat" ] && install_ethercat
    install_systemd_service sudo
    finalize_install linux
(4)在搭建的Webserver中,接收到有关的st文件就会调用有关的函数进行编译
 OpenPLC-Webserver文件夹中的webserver.py文件

【webserver.py】

import sqlite3 # 一种嵌入式关系型数据库管理系统
import openplc # 与OpenPLC软件配合使用的Python库,提供了与PLC通信和控制相关的功能
import flask # web应用框架,搭建网站
 
app = flask.Flask(__name__) 
openplc_runtime = openplc.runtime()
 
@app.route('/compile-program', methods=['GET', 'POST']) # URL
def compile_program():
  global openplc_runtime # 使用全局变量提前说明
  
  st_file = flask.request.args.get('file') # 获得待编译文件
  
  openplc_runtime.compile_program(st_file)
 
【openplc.py】
class runtime:
    def compile_program(self, st_file):
        a = subprocess.Popen(['./scripts/compile_program.sh', str(st_file)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  # subprocess创建子进程,执行shell脚本,在主进程中获得子进程的输出
 
(5)具体是利用OpenPLC-Webserver-scripts文件夹中的compile_program.sh文件进行编译
【compile_program.sh】
#store the active program filename
echo "$1" > ../active_program  # 即str(st_file)待编译文件
 
#compiling the ST file into C
cd .. # 切换上一级目录
echo "Optimizing ST program..."
./st_optimizer ./st_files/"$1" ./st_files/"$1" # 执行可执行文件st_optimizer,优化代码
echo "Generating C files..."
./iec2c -f -l -p -r -R -a ./st_files/"$1" # 编译成C文件
if [ $? -ne 0 ]; then
echo "Error generating C files"
echo "Compilation finished with errors!"
exit 1
fi
 
#compiling for each platform
cd core
if [……]
……
elif [ "$OPENPLC_PLATFORM" = "linux" ]; then
    echo "Compiling for Linux"
    echo "Generating object files..."
    if [ "$OPENPLC_DRIVER" = "sl_rp4" ]; then
        g++ -std=gnu++11 -I ./lib -c Config0.c -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w -DSL_RP4 # 使用g++编译器编译,链接一些库文件
    else
        g++ -std=gnu++11 -I ./lib -c Config0.c -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w
    fi
 echo "Generating glueVars..."
    ./glue_generator
    echo "Compiling main program..."
    if [ "$OPENPLC_DRIVER" = "sl_rp4" ]; then
        g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w $ETHERCAT_INC -DSL_RP4 # 将之前生成的目标文件(objective files)链接起来,并生成可执行文件"openplc"
    else
        g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w $ETHERCAT_INC
    fi
 
2. 源码仔细分析
  安装OpenPLC runtime过程中的三个函数值得关注:install_st_optimizer,install_glue_generator和install_all_libs,这三个函数会编译产生可执行文件——./st_optimizer,./glue_generator,./iec2c 
  我们仔细分析st_optimizer和glue_generator的有关代码。
(1)在OpenPLC文件夹中的background_installer.sh文件中定义的安装函数

【background_installer.sh】

function install_all_libs { # 安装平台是linux时调用的函数
    install_matiec "$1"
    install_st_optimizer "$1"
    install_glue_generator "$1"
    install_opendnp3 "$1"
    disable_ethercat "$1"
    install_libmodbus "$1"
}
 
function install_matiec {
    echo "[MATIEC COMPILER]"
    cd "$OPENPLC_DIR/utils/matiec_src"
    autoreconf -i # 自动更新configure脚本及相关文件
    ./configure # 运行configure脚本,配置编译环境
    make # 读取Makefile 的文件,执行编译、链接等操作,生成可执行文件或者其他目标文件
    cp ./iec2c "$OPENPLC_DIR/webserver/" || fail "Error compiling MatIEC"
    cd "$OPENPLC_DIR"
}
 
function install_st_optimizer { 
    echo "[ST OPTIMIZER]"
    cd "$OPENPLC_DIR/utils/st_optimizer_src"
    g++ st_optimizer.cpp -o "$OPENPLC_DIR/webserver/st_optimizer" || fail "Error compiling ST Optimizer" # 使用g++编译器编译st_optimizer.cpp文件,并将编译后的可执行文件命名为"$OPENPLC_DIR/webserver/st_optimizer"
    cd "$OPENPLC_DIR"
}

function install_glue_generator {
    echo "[GLUE GENERATOR]"
    cd "$OPENPLC_DIR/utils/glue_generator_src"
    g++ -std=c++11 glue_generator.cpp -o "$OPENPLC_DIR/webserver/core/glue_generator" || fail "Error compiling Glue Generator" # 使用g++编译器编译glue_generator.cpp文件,并将编译后的可执行文件命名为"$OPENPLC_DIR/webserver/core/glue_generator"
    cd "$OPENPLC_DIR"
}

 (2)st_optimizer.cpp的源码

 位置:OpenPLC-utils-st_optimizer_src文件夹中的st_optimizer.cpp文件

 进行代码优化,主要是针对if语句进行优化
(3)glue_generator.cpp的源码
 位置:OpenPLC-utils-glue_generator_src文件夹中的glue_generator.cpp文件
【glue_generator.cpp】
int mainImpl(int argc, char *argv[]) # 主函数
{
  ……
  
ifstream locatedVars(input_file_name, ios::in);
    if (!locatedVars.is_open()) {
        cout << "Error opening located variables file at " << input_file_name << endl;
        return 1;
    } # 通过ifstream对象locatedVars打开名为input_file_name的文件
    ofstream glueVars(output_file_name, ios::trunc);
    if (!glueVars.is_open()) {
        cout << "Error opening glue variables file at " << output_file_name << endl;
        return 2;
    } # 通过ofstream对象glueVars打开名为output_file_name的文件,打开文件之前清空文件内容
 
 generateHeader(glueVars); 将IEC程序中的变量与OpenPLC内存指针进行连接
    generateBody(locatedVars, glueVars); # 根据输入流中的数据生成文件的主体部分,其中包括了IEC变量的名称和类型信息
    generateBottom(glueVars); #将生成的底部代码添加到 C++ 源文件中,可以方便地引入时间更新功能,如模拟器、游戏引擎或实时数据处理系统
 
}
 
void generateHeader(ostream& glueVars)
{
  #接受一个ostream类型的参数,即glueVars,然后将一段固定的头文件内容写入到这个输出流中
  #定义了一些变量和缓冲区,用于将IEC程序中的变量与OpenPLC内存指针进行连接
 
}
 
void generateBody(istream& locatedVars, ostream& glueVars) {
 
 
    while (parseIecVars(locatedVars, iecVar_name, iecVar_type))
    {
        glueVar(glueVars, iecVar_name, iecVar_type); 
    } # 通过parseIecVars函数从输入流locatedVars中解析IEC变量的名称和类型,然后将这些信息传递给glueVar函数,由glueVar函数将这些信息写入到输出流glueVars中
}
(4)install_matiec函数中的Makefile.am文件
Makefile 将会根据规则编译源文件,并链接相应的库文件,最终生成iec2c 和iec2iec两个可执行文件
【Makefile.am】

include common.mk # 定义C++编译器的编译选项

bin_PROGRAMS = iec2c iec2iec # 定义了两个可执行文件iec2c和iec2iec

SUBDIRS = absyntax absyntax_utils stage1_2 stage3 stage4

ACLOCAL_AMFLAGS=-I config

HGVERSION= $(shell hg -R $(top_srcdir) parents --template '{node|short}' 2> /dev/null || grep node $(top_srcdir)/.hg_archival.txt 2> /dev/null || true )
AM_CXXFLAGS += -DHGVERSION="\"${HGVERSION}\""

iec2c_LDADD = stage1_2/libstage1_2.a \
stage3/libstage3.a \
stage4/generate_c/libstage4_c.a \
absyntax/libabsyntax.a \
absyntax_utils/libabsyntax_utils.a # 定义链接时需要的库文件

iec2iec_LDADD = stage1_2/libstage1_2.a \ # 定义链接时需要的库文件
stage3/libstage3.a \
stage4/generate_iec/libstage4_iec.a \
absyntax/libabsyntax.a \
absyntax_utils/libabsyntax_utils.a

iec2c_SOURCES = main.cc # 编译main.cc文件

iec2iec_SOURCES = main.cc

(5)main.cc文件

解析命令行选项、设置运行时选项,并执行MATIEC编译器的4个阶段:

阶段1 - 词法分析器(使用flex实现)

阶段2 - 语法解析器(使用bison实现)

阶段pre3 - 填充符号表(为抽象符号树中的符号提供搜索的符号表)

阶段3 - 语义分析器(目前仅执行类型检查,还未实现)

阶段4 - 代码生成器(使用gcc、javac等工具,生成ANSI C代码)

posted @   碳酸钾K2CO3  阅读(1501)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示