手写一个PrattParser基本运算解析器1: 编译原理概述
点击查看 基于Swift的PrattParser项目
编译原理概述
编译原理是我们每一个程序猿必须要了解的技能, 编译原理实际上并没有啥高深的技术, 我们如果在做业务开发, 也很少会用到编译开发的知识, 但是编译原理又是我们必备的基础知识之一. 所以我们需要对编译原理的内容有一个大概的了解.
其实我自己写这一个系列的起因, 一个是我恶补编译原理的相关内容, 另外一个就是看到了B站熊爷的技术去魅篇- 手写一个普拉特解析器, 感觉这个对我辅助理解遍历原理的一个很好的方案. 所以才会有这一个系列的博客.
首先就是编译过程中的基本组成结构单元. 如下图所示.
基本组成结构主要有 词法分析, 语法分析, 语义分析, 中间代码生成, 代码优化, 目标代码生成, 符号表管理, 错误管理.
下面, 我们就以下运算代码(以 Clang 作为编译器为例)为示例, 写一些伪过程来了解整个编译过程.
1 + 3 * 2
-
词法分析(Lexical Analysis)
词法分析主要是将源代码分解为词法单元(tokens),如关键字、标识符、运算符等。词法分析器负责读取字符流,根据语法规则识别和分类词法单元。
使用下面的命令(需要安装Mac xcode, 以提供
Clang
编译前端环境)clang -Xclang -dump-tokens helloworld.c
上面的运算代码会被划分成如下的词法单元. (会报错, 但不影响)
numeric_constant '1' [StartOfLine] Loc=<helloworld.c:1:1>
plus '+' [LeadingSpace] Loc=<helloworld.c:1:3>
numeric_constant '3' [LeadingSpace] Loc=<helloworld.c:1:5>
star '*' [LeadingSpace] Loc=<helloworld.c:1:7>
numeric_constant '2' [LeadingSpace] Loc=<helloworld.c:1:9>
eof '' Loc=<helloworld.c:1:11>
Loc
代表着词法单元在源文件中的位置, 所以实际上1 + 3 * 2
被划分成以下的词法单元.
-
语法分析(Syntax Analysis)
将词法单元流转换为抽象语法树(Abstract Syntax Tree,AST)。语法分析器负责检查和验证语法结构,根据语法规则构建语法树,以便后续的语义分析和代码生成。
上一个步骤我们已经把源文件划分成了一个个的词法单元, 但是执行顺序呢? 因为在数学运算过程中 乘法 要比 加法 优先级高, 所以我们在
1 + 3 * 2
的运算过程中. 需要先计算3 * 2
而不是1 + 3
, 我们由于有数学基础, 一眼就知道应该先计算谁, 但程序是如何知道应该先计算谁呢? 所以我们需要构建AST语法树, 为什么要构建AST语法树, 我们实际操作一下你就明白了.注: 由于
1 + 3 * 2
构建语法树不是很直观, 我们把helloworld.c
中的内容改为int a = 1 + 3 * 2;
使用下面的命令来构建AST语法树.
clang -Xclang -ast-dump -fsyntax-only helloworld.c
抛去一些别的构建树节点, 我们会发现如下的结构.
`-VarDecl 0x7fb69188e200 <helloworld.c:1:1, col:17> col:5 a 'int' cinit`-BinaryOperator 0x7fb69188e330 <col:9, col:17> 'int' '+'|-IntegerLiteral 0x7fb69188e2b0 <col:9> 'int' 1`-BinaryOperator 0x7fb69188e310 <col:13, col:17> 'int' '*'|-IntegerLiteral 0x7fb69188e2d0 <col:13> 'int' 3`-IntegerLiteral 0x7fb69188e2f0 <col:17> 'int' 2
上面看着很杂乱, 我们稍微改造一下, 再看一下, 整体如下图所示.
通过上图, 我们就知道AST语法树实际上是类似于二叉树的结构. 我们可以通过生成的AST语法树知道, 如果想要执行
图中的加法运算
, 就必须先执行图中的乘法运算
, 这也就是为什么计算机能通过AST语法树知道操作的优先级.
-
语义分析(Semantic Analysis)
对语法树进行语义检查,包括类型检查、作用域检查等。语义分析器负责分析语句和表达式的含义和关联,确保程序在运行时不会出现语义错误。
在iOS开发过程中, 语义分析也叫做静态分析. 计算机做的操作也是包括
类型检查
、实现检查(某个类是否存在某个方法)
、变量使用
,还会有一些复杂的检查
.例如在 Objective-C 中,给某一个对象发送消息(调用某个方法),检查这个对象的类是否声明这个方法(但并不会去检查这个方法是否实现,这个错误是在运行时进行检查的),如果有什么错误就会进行提示。
-
中间代码生成(Intermediate Code Generation)
将语法树转换为中间表示形式,如三地址码、虚拟指令等。中间代码生成器负责将高级语言的语法结构转换成较低级的表示形式,以便后续的优化和目标代码生成。
在Clang编译前端编译代码过程中, 中间代码的形式为 LLVM IR的形式输出的.
我们使用如下终端指令, 来生成
LLVM IR
形式的中间代码.clang -S -emit-llvm helloworld.c -o helloworld.ll
生成的中间代码如下所示.
; ModuleID = 'helloworld.c' source_filename = "helloworld.c" target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx13.0.0"@a = global i32 7, align 4!llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5}!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 3]} !1 = !{i32 1, !"wchar_size", i32 4} !2 = !{i32 7, !"PIC Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"Apple clang version 14.0.3 (clang-1403.0.22.14.1)"}
我们发现全局变量
int a
的结果已经被计算出来了, 如下所示.@a = global i32 7, align 4
-
代码优化(Code Optimization)
对中间代码进行优化,以提高程序的性能和效率。代码优化器负责分析和优化中间代码,如常量折叠、循环展开、函数内联等优化技术,以减少执行时间和内存占用。
我们可以通过 -O 参数来优化生成的中间代码.
clang -O3 -S -emit-llvm helloworld.c -o helloworld.ll
这时候, 我们会得到如下的中间代码.
; ModuleID = 'helloworld.c' source_filename = "helloworld.c" target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-macosx13.0.0"@a = local_unnamed_addr global i32 7, align 4!llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5}!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 3]} !1 = !{i32 1, !"wchar_size", i32 4} !2 = !{i32 7, !"PIC Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"Apple clang version 14.0.3 (clang-1403.0.22.14.1)"}
那么
-O3
是什么含义呢? 下面, 我整理了 Clang 遍历过程中所有的优化参数的具体定义.-
优化级别 O0 :这是最低级别的优化,默认情况下会开启。在这个级别下,编译器将进行最少的优化,以保持代码的简单性和可读性。
-
优化级别 O1 :这个级别会应用一些轻量级的优化,以提高代码的执行速度和空间效率。编译时间通常较短,但优化效果有限。
-
优化级别 O2 :这是默认的优化级别。它会开启更多的优化选项,包括内联函数、循环展开、变量替代等,以提高代码的执行速度。编译时间可能会比 O1 长一些。
-
优化级别 O3 :这个级别会比 O2 应用更多的优化,但可能会导致编译时间显著增加。它会更加注重代码的性能优化,可能会牺牲一些可读性和可维护性。
-
优化级别 Os :这个级别的优化旨在减小生成的可执行文件的大小,而不是提高执行速度。它可以通过减小代码的体积来节省内存和存储空间。
-
优化级别 Oz :这是一个综合了优化级别
O2
和优化级别Os
的级别。它会尽量减小代码的体积,并使用更多的性能优化。
同时, 在 Xcode 编译项目工程时, 我们也是可以通过
Build Settings
→Optimization Level
调整中间代码的优化级别.
上述步骤就是编译前端做的工作, 下面来说一下编译后端都做了哪些工作.
-
-
目标代码生成(Code Generation)
将优化后的中间代码转换为目标机器代码(可执行代码)。代码生成器负责将中间代码转换为目标机器的指令序列,包括寄存器分配、指令选择和指令调度等。
对于iOS来说, 我们主要根据不同架构的CPU转换成汇编代码, 然后再生成对应的可执行文件. 这样CPU就可以执行了.
生成汇编代码, 我们主要利用到以下指令
clang -S -o - helloworld.c | open -f
这时候, 汇编代码生成结果如下所示.
.section __TEXT,__text,regular,pure_instructions.build_version macos, 13, 0 sdk_version 13, 3.section __DATA,__data.globl _a ## @a.p2align 2 _a:.long 7 ## 0x7.subsections_via_symbols
-
可执行文件的生成(Create Mach-O File)
这一个步骤应该算在
目标代码生成
的一部分, 在生成目标平台的机器代码后,Clang
还需要进行链接操作,将生成的机器代码与所需的库文件和其他依赖项进行链接,以生成最终的Mach-O
可执行文件。注: 由于
helloworld.c
中的内容为int a = 1 + 3 * 2;
, 并不能执行, 所以我们要在helloworld.c
写一个完整的C语言main函数. 如下所示.#include <stdio.h> int main(int argc, char *argv[]) {printf("Hello World!\n");return 0; }
我们可以通过下面终端指令生成
Mach-O
文件.clang helloworld.c -o helloworld.out
然后, 我们可以在终端使用
./helloworld.out
正常调用Mach-O文件了.shenjingsaodong@Mac Desktop % ./helloworld.out
Hello World!
然后, 我们可以通过 otool 工具可以查看生成的可执行文件的
section
和segment
, 这里我就不懂了… GGSegment __PAGEZERO: 0x100000000 (zero fill) (vmaddr 0x0 fileoff 0) Segment __TEXT: 0x4000 (vmaddr 0x100000000 fileoff 0)Section __text: 0x2c (addr 0x100003f70 offset 16240)Section __stubs: 0x6 (addr 0x100003f9c offset 16284)Section __cstring: 0xe (addr 0x100003fa2 offset 16290)Section __unwind_info: 0x48 (addr 0x100003fb0 offset 16304)total 0x88 Segment __DATA_CONST: 0x4000 (vmaddr 0x100004000 fileoff 16384)Section __got: 0x8 (addr 0x100004000 offset 16384)total 0x8 Segment __LINKEDIT: 0x4000 (vmaddr 0x100008000 fileoff 32768) total 0x10000c000
-
符号表管理(Symbol Table Management)
维护符号表,记录程序中定义的变量、函数、常量等符号的信息。符号表包括符号名称、类型、作用域等信息,用于语义分析和代码生成阶段的符号查找和类型检查。
当在编译过程中发生错误时, 我们是可以利用
符号表
来定位到报错位置在源文件中的位置的.通过, 我们在项目上线后, 当线上发生事故异常时, 我们也是可以利用
符号表
来确定问题所在的.基于Clang编译器,符号表管理的过程如下:
-
符号表的组织
Clang使用哈希表、树等数据结构来实现符号表。哈希表可以提供快速的查找和插入操作,树结构可以支持符号作用域的嵌套和查找。
-
符号的添加
在词法分析和语法分析过程中,当遇到变量、函数、类型定义等符号的声明和定义时,Clang将根据符号的属性创建一个符号表项,并将其添加到符号表中。符号表项包括符号名称、类型、作用域、存储位置等信息。
-
符号的查找
在进行语义分析和代码生成时,Clang需要查找符号表来获取符号的属性信息,如类型、存储位置等。通过符号名称和作用域,Clang可以在符号表中快速定位到符号表项,以提供相应的属性信息。
-
符号的作用域
符号表管理也涉及符号的作用域管理。Clang会维护一个作用域栈来跟踪当前有效的作用域。当进入一个新的作用域时,例如函数、循环或块作用域,Clang会将该作用域压入作用域栈中,并将内部的符号添加到符号表中。当离开作用域时,Clang会将该作用域从作用域栈中弹出。
-
符号的重定义和重复定义检查
Clang会检查符号表中是否存在重复定义或重定义的符号。如果发现重定义或重复定义的符号,Clang会生成相应的错误信息。
-
-
错误管理(Error Management)
在编译过程难免会遇到编译错误, 这时候, 出错管理体现着无与伦比的作用, 它帮助程序员发现和解决代码中的错误. 提高代码的可靠性和质量。在编译器的实现中,错误管理需要综合考虑准确性、恢复能力和用户友好性,以提供有效的错误处理和提示信息。
以下是编译过程中的错误管理部分:
-
错误检测(Error Detection)
编译器在进行词法分析、语法分析、语义分析等阶段会检测代码中的语法错误、类型错误和其他语义错误。一旦检测到错误,编译器会生成相应的错误消息。
-
错误报告(Error Reporting)
编译器在检测到错误后,会将错误信息记录下来,并向用户报告。错误报告通常包括错误类型、错误位置和错误描述等信息,以帮助程序员找出和解决错误。
-
错误恢复(Error Recovery)
当编译器遇到错误后,可以尝试进行错误恢复来继续编译。错误恢复策略可以包括跳过错误部分、补全缺失部分、重新同步语法等,以尽可能提供更多的错误信息和继续编译的机会。
-
总结
这一篇博客还是比较偏向于理论知识的, 下一篇博客, 我们就着 词法分析
语法分析
语义分析
中间代码生成
这几个前端过程来构建我们的 PrattParser 解释器. 如果有任何问题, 欢迎留言. 欢迎持续关注骚栋.
点击查看 基于Swift的PrattParser项目
相关文章:

手写一个PrattParser基本运算解析器1: 编译原理概述
点击查看 基于Swift的PrattParser项目 编译原理概述 编译原理是我们每一个程序猿必须要了解的技能, 编译原理实际上并没有啥高深的技术, 我们如果在做业务开发, 也很少会用到编译开发的知识, 但是编译原理又是我们必备的基础知识之一. 所以我们需要对编译原理的内容有一个大概的…...

ZKP3.2 Programming ZKPs (Arkworks Zokrates)
ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 3: Programming ZKPs (Guest Lecturers: Pratyush Mishra and Alex Ozdemir) 3.3 Using a library ( tutorial) R1CS Libraries A library in a host language (Eg: Rust, OCaml, C, Go, …)Key type: constraint system Mai…...

mysqld: File ‘./binlog.index‘ not found (OS errno 13 - Permission denied) 问题解决
问题背景 Centos7 安装Mysql 8后启动时遇到的问题,看了好几个博客方案无效,搞了半小时才找到正解,在此次进行记录。 在此假设你已经修改了对应目录的权限,比如配置的mysql data目录初始化后已经执行了chown -R mysql:mysql /XXX/…...
Python 环境构建最佳实践:Mamba + Conda + PIP
此前,我们单独介绍过 PIP 和 Conda,在后续的实际应用中,还是遇到了不少 Python 环境构建的问题,特别是在 Windows 系统上,最突出的表现是:虽然PIP的包依赖解析和下载都很快,但在 Windows 上经常会因为缺失底层依赖的程序库(例如某些dll文件)而导致 Python 程序启动时报…...
华为OD 最多团队(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...

2023“龙芯杯”信创攻防赛 | 赛宁网安技术支持
2023年10月19日,为深入贯彻国家网络强国战略思想,宣传国家网络安全顶层设计,落实《网络安全法》《数据安全法》等法律法规。由大学生网络安全尖锋训练营主办,龙芯中科技术股份有限公司承办,山石网科通信技术股份有限公…...
代码随想录算法训练营第五十八天| 583. 两个字符串的删除操作 72. 编辑距离
今日学习的文章链接和视频链接 两个字符串的删除操作 https://programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html 编辑距离 https://programmercarl.com/0072.%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB…...
leetcode做题笔记191. 位1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 1 的个数(也被称为汉明重量)。 提示: 请注意,在某些语言(如 Java)中…...

Git基本命令和使用
文章目录 1、Git本地库命令1.1、初始化本地库1.2、设置用户签名1.3、查看本地库状态1.4、将工作区的修改添加到暂存区1.5、将暂存区的修改提交到本地库1.6、历史版本1.7、取消commit1.8、取消暂存文件 2、分支操作2.1、查看分支2.2、创建分支2.3、分支合并时产生冲突 3、Gitee远…...

50springboot私人健身与教练预约管理系统
大家好✌!我是CZ淡陌。一名专注以理论为基础实战为主的技术博主,将再这里为大家分享优质的实战项目,本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路…...

测试Android webview 加载本地html
最近开发一个需要未联网功能的App, 不熟悉使用Java原生开发界面,于是想使用本地H5做界面,本文测试了使用本地html加载远程数据。直接上代码: MainActivity.java package com.alex.webviewlocal;import androidx.appcompat.app.AppCompatAct…...
ubuntu安装pgsql
ubuntu安装postgresSQL 官网地址: https://www.postgresql.org/download/ 1.安装 # 添加源 sudo sh -c echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list # 安装数字签名 w…...

利用ArcGIS获取每一个冰川的中心位置经纬度坐标:要素转点和要素折点转点的区别
问题概述:下图是天山地区的冰川的分布,我们可以看到每一条冰川是一个面要素,要求得到每一个冰川(面要素)的中心经纬度坐标。 1.采用要素转点功能 选择工具箱的【数据管理工具】-【要素】-【要素转点】。完成之后再采用…...

数据结构中的七大排序(Java实现)
目录 一、直接插入排序 二、希尔排序 三、直接选择排序 四、堆排序 五、冒泡排序 六、快速排序 七、归并排序 一、直接插入排序 思想: 定义i下标之前的元素全部已经有序,遍历一遍要排序的数组,把i下标前的元素全部进行排序࿰…...
深度学习基础算法
算法 1.K近邻算法 机器学习--K-近邻算法(KNN)_k近邻-CSDN博客 2. 数据库样本: CIFAR-10 CIFAR-10数据集(介绍、下载读取、可视化显示、另存为图片)_cifar10数据集-CSDN博客...
LuatOS-SOC接口文档(air780E)-- ir - 红外遥控
ir.sendNEC(pin, addr, cmd, repeat, disablePWM)# 发送NEC数据 参数 传入值类型 解释 int 使用的GPIO引脚编号 int 用户码(大于0xff则采用Extended NEC模式) int 数据码 int 可选,引导码发送次数(110ms一次࿰…...

Java虚拟机常见面试题总结
梳理Java虚拟机相关的面试题,主要参考《深入理解Java虚拟机 JVM高级特性与最佳实践》(第2版, 周志明 著)一书,其余部分整合网络相关内容。注意,关于Java并发编程的面试题因为内容较多,单独整理。Java基础相关的面试题可以参考Java…...

NVIDIA NCCL 源码学习(十一)- ring allreduce
之前的章节里我们看到了nccl send/recv通信的过程,本节我们以ring allreduce为例看下集合通信的过程。整体执行流程和send/recv很像,所以对于相似的流程只做简单介绍,主要介绍ring allreduce自己特有内容。 单机 搜索ring 在nccl初始化的过…...
前端--性能优化【上篇】--网络优化与页面渲染优化
一、网络优化 1、DNS预解析 link标签的rel属性设置dns-prefetch,提前获取域名对应的IP地址 2、CDN(网络分发系统) 用户与服务器的物理距离对响应时间也有影响。 内容分发网络(CDN)是一组分散在不同地理位置的 web…...
git 删除分支
目录 1,查看分支2,删除本地分支3,删除远程分支 1,查看分支 # 查看本地分支 git branch# 查看远程分支 git branch -r# 查看所有分支 git branch -a2,删除本地分支 # -d 是 --delete 的简写,会在删除前检查…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...