【免费下载链接】libmodbus
A Modbus library for Linux, Mac OS, FreeBSD and Windows
项目地址:
https://gitcode.com/gh_mirrors/li/libmodbus
点击查看 免费下载
导读:本文以 libmodbus 官方文档 docs/modbus_mapping_new.md 为主线,深入讲解 modbus_mapping_new() 这个专为 Modbus 服务器/从站设计的映射分配函数。你将掌握它的函数原型、四类数据区的内存布局与零值语义、返回值与错误处理方式,并通过仓库内的测试服务器源码(如 tests/unit-test-server.c)理解它如何配合 modbus_reply() 完成请求应答,最终能够独立写出可运行、可释放的 Modbus 服务器数据映射代码。
modbus_mapping_new() 是 libmodbus 中用于在 Modbus 服务器/从站(server/slave)侧分配数据存储区的核心 API。它一次调用即可为 Modbus 标准的四类地址空间——线圈(Coils,0x)、离散输入(Discrete Inputs,1x)、保持寄存器(Holding Registers,4x)和输入寄存器(Input Registers,3x)——分别创建存储数组,并将指针统一封装进 modbus_mapping_t 结构体中,供后续的请求处理函数使用。
函数原型与基本用途
modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, int nb_registers, int nb_input_registers);
该函数会分配四个数组,分别用于存储:
| nb_bits | 线圈(Coils) | uint8_t | 0x |
| nb_input_bits | 离散输入(Discrete Inputs) | uint8_t | 1x |
| nb_registers | 保持寄存器(Holding Registers) | uint16_t | 4x |
| nb_input_registers | 输入寄存器(Input Registers) | uint16_t | 3x |
四个数组的指针被保存在 modbus_mapping_t 结构体中。该结构体的定义位于 src/modbus.h,除数组指针外还记录了各数据区的数量(nb_*)和起始地址(start_*):
typedef struct _modbus_mapping_t {
int nb_bits;
int start_bits;
int nb_input_bits;
int start_input_bits;
int nb_input_registers;
int start_input_registers;
int nb_registers;
int start_registers;
uint8_t *tab_bits;
uint8_t *tab_input_bits;
uint16_t *tab_input_registers;
uint16_t *tab_registers;
} modbus_mapping_t;
所有数组中的元素都会被初始化为零。这一点在实现中由 memset() 保证:分配成功后立即将整块内存清零(见 src/modbus.c),因此新建的映射天然处于“全部为 0/0x0000”的初始状态,无需手工清空。
内部实现:它是 modbus_mapping_new_start_address() 的便捷封装
从源码看,modbus_mapping_new() 并不是独立实现的分配逻辑,而是对 modbus_mapping_new_start_address 的简单封装。其实现位于 src/modbus.c:
modbus_mapping_t *modbus_mapping_new(int nb_bits,
int nb_input_bits,
int nb_registers,
int nb_input_registers)
{
/* Reject negative counts: they would otherwise be converted to very large
unsigned dimensions and drive huge allocations. */
if (nb_bits < 0 || nb_input_bits < 0 || nb_registers < 0 ||
nb_input_registers < 0) {
errno = EINVAL;
return NULL;
}
return modbus_mapping_new_start_address(
0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers);
}
这里有两个值得注意的实现细节:
零值语义:跳过不需要的数据区
Modbus 服务器不一定需要暴露全部四类数据区。例如纯输出型设备可能没有输入寄存器,此时可将对应参数传 0,该数组不会被分配,对应的指针为 NULL:
/* 只分配线圈与保持寄存器,离散输入、输入寄存器不分配 */
mb_mapping = modbus_mapping_new(500, 0, 500, 0);
/* 此时 mb_mapping->tab_input_bits 与 tab_input_registers 均为 NULL */
这种零值语义在源码中体现为“按区分配”的结构:modbus_mapping_new_start_address() 对每个数据区单独判断 nb_* == 0,为零时指针置 NULL 且不执行 malloc,否则分配并清零(见 src/modbus.c)。仓库的随机测试服务器正是这么做的:
mb_mapping = modbus_mapping_new(500, 500, 500, 500);
见 tests/random-test-server.c。
尺寸上限:65536 与 MODBUS_MAX_TABLE_SIZE
由于 Modbus 地址空间为 16 位,每个数据区最多只能寻址 65536 个条目。nb_bits、nb_input_bits、nb_registers、nb_input_registers 四个参数均不得超过 65536;更大的值会被拒绝,以避免由不可信配置驱动的超大内存分配。
这一约束在实现中通过 MODBUS_MAX_TABLE_SIZE 宏(定义于 src/modbus.c,值为 65536)落实:
if (nb_bits > MODBUS_MAX_TABLE_SIZE || nb_input_bits > MODBUS_MAX_TABLE_SIZE ||
nb_registers > MODBUS_MAX_TABLE_SIZE ||
nb_input_registers > MODBUS_MAX_TABLE_SIZE) {
errno = EINVAL;
return NULL;
}
见 src/modbus.c。按最大尺寸估算内存开销:线圈与离散输入每个元素占 1 字节(uint8_t),64K 位约 64 KB;保持与输入寄存器每个元素占 2 字节(uint16_t),64K 寄存器约 128 KB。实践中通常远小于此上限。
另外需要注意,单次 Modbus 读请求的数量另有约束:MODBUS_MAX_READ_BITS(2000)与 MODBUS_MAX_READ_REGISTERS(125)定义于 src/modbus.h,映射数组只需覆盖设备实际使用的地址范围即可,无需与请求上限对齐。
返回值与错误处理
- 成功时:返回新分配并已清零的 modbus_mapping_t * 结构体。
- 失败时:返回 NULL 并设置 errno。
可能的错误码如下:
| EINVAL | 无效参数 | 任一请求的数组维度超过 65536 条目(modbus_mapping_new() 中还包括负数参数) |
| ENOMEM | 内存不足 | malloc 分配任一数组或结构体失败 |
值得注意的是,源码对分配失败做了渐进式回滚:例如保持寄存器分配失败时,会依次释放已分配的输入寄存器、离散输入、线圈数组和结构体本身,避免内存泄漏(见 src/modbus.c),调用方无需在失败路径上手工清理部分数组。
官方文档给出的标准错误处理模式如下(见 docs/modbus_mapping_new.md):
/* The first value of each array is accessible from the 0 address. */
mb_mapping = modbus_mapping_new(
BITS_ADDRESS + BITS_NB,
INPUT_BITS_ADDRESS + INPUT_BITS_NB,
REGISTERS_ADDRESS + REGISTERS_NB,
INPUT_REGISTERS_ADDRESS + INPUT_REGISTERS_NB
);
if (mb_mapping == NULL) {
fprintf(
stderr, "Failed to allocate the mapping: %s\\n",
modbus_strerror(errno)
);
modbus_free(ctx);
return -1;
}
注意该示例中使用了 modbus_strerror(errno) 将 errno 转换为可读字符串,并在失败时释放此前已创建的 Modbus 上下文 ctx(由 modbus_free(ctx) 完成),随后以 -1 退出,这是 libmodbus 服务器程序的典型启动序列。
在服务器中的实战用法:填充映射并应答请求
modbus_mapping_new() 的设计初衷是“方便在 Modbus 服务器/从站中处理请求”。分配好映射后,服务器需要:
仓库的单元测试服务器 tests/unit-test-server.c 演示了完整流程(它使用带起始地址的变体,但填充与应答逻辑完全相同):
mb_mapping = modbus_mapping_new_start_address(UT_BITS_ADDRESS,
UT_BITS_NB,
UT_INPUT_BITS_ADDRESS,
UT_INPUT_BITS_NB,
UT_REGISTERS_ADDRESS,
UT_REGISTERS_NB_MAX,
UT_INPUT_REGISTERS_ADDRESS,
UT_INPUT_REGISTERS_NB);
if (mb_mapping == NULL) {
fprintf(stderr, "Failed to allocate the mapping: %s\\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
/* 服务器侧填充只读输入值 */
modbus_set_bits_from_bytes(
mb_mapping->tab_input_bits, 0, UT_INPUT_BITS_NB, UT_INPUT_BITS_TAB);
for (i = 0; i < UT_INPUT_REGISTERS_NB; i++) {
mb_mapping->tab_input_registers[i] = UT_INPUT_REGISTERS_TAB[i];
}
其中 UT_BITS_ADDRESS(0x130)、UT_REGISTERS_ADDRESS(0x160)等测试地址定义在 tests/unit-test.h.in。服务器主循环随后反复调用 modbus_receive() 接收请求,并将映射交给 modbus_reply(ctx, query, rc, mb_mapping) 处理(见 tests/unit-test-server.c),退出循环后调用 modbus_mapping_free(mb_mapping) 释放映射(见 tests/unit-test-server.c)。
带宽测试服务器则展示了“零值跳过 + 请求上限”的组合用法(见 tests/bandwidth-server-one.c 与 tests/bandwidth-server-many-up.c):
modbus_mapping_new(MODBUS_MAX_READ_BITS, 0, MODBUS_MAX_READ_REGISTERS, 0);
即:按单次请求可读取的最大位数(2000)与最大寄存器数(125)分配线圈和保持寄存器,其余两类数据区不分配。
与 modbus_mapping_new_start_address() 的关系与选型
两个函数的行为对比如下:
| 起始地址 | 固定全部为 0 | 每类数据区可独立指定 |
| 参数类型 | int | unsigned int |
| 地址映射关系 | 数组下标 == 地址 | 数组下标 + start == 地址 |
modbus_mapping_new() 适合地址从 0 开始的常规设备。而带起始地址的变体可以把映射“挂”到任意高位地址而不浪费内存——例如只暴露寄存器 340~349,可用 modbus_mapping_new_start_address(0, 0, 0, 0, 340, 10, 0, 0),此时 tab_registers 只分配 10 个元素,客户端访问地址 340 对应数组下标 0(详见 docs/modbus_mapping_new_start_address.md)。
释放映射:modbus_mapping_free()
与分配配对的是释放函数(原型见 docs/modbus_mapping_free.md):
void modbus_mapping_free(modbus_mapping_t *mb_mapping);
它依次释放四个数组(tab_input_registers、tab_registers、tab_input_bits、tab_bits),最后释放 modbus_mapping_t 结构体本身;传入 NULL 时安全返回,无返回值(见 src/modbus.c)。
务必区分两组释放函数:
- modbus_mapping_free(mb_mapping) —— 释放数据映射;
- modbus_free(ctx) —— 释放 Modbus 上下文。
两者相互独立,服务器退出时都需要调用,顺序通常为先释放映射再释放上下文(见 tests/unit-test-server.c)。
小结
modbus_mapping_new() 是 libmodbus 服务器端编程的入口级 API:一次调用完成四类数据区的分配与清零,零值参数跳过不需要的数据区,65536 上限与负数检查提供了防御性保护,失败时 errno 区分参数错误(EINVAL)与内存不足(ENOMEM)。结合 modbus_reply() 与 modbus_mapping_free(),即可搭建完整可用的 Modbus 服务器。相关文档与源码可继续阅读:
- 官方文档:docs/modbus_mapping_new.md、docs/modbus_mapping_new_start_address.md、docs/modbus_mapping_free.md
- 核心实现:src/modbus.c(分配与释放)、src/modbus.h(结构体定义)
- 参考实现:tests/unit-test-server.c(完整服务器流程)、tests/unit-test.h.in(地址常量定义)
赞
【免费下载链接】libmodbus
A Modbus library for Linux, Mac OS, FreeBSD and Windows
项目地址:
https://gitcode.com/gh_mirrors/li/libmodbus
点击查看 免费下载
相关推荐
CANN/asc-devkit核间同步到达API
CANN ops-nn 图融合 Pass 深度解析:WeightQuantBatchMatmulV2 Transpose 融合机制、约束与源码实现
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心




评论前必须登录!
注册