2、版本号规范:语义化版本号(SemVer)、内部构建号、版本号在基站中的实际应用

版本号这东西,看着简单,但真要在基站这种复杂系统里用好,坑不少。我见过太多团队因为版本号乱打,导致现场升级出问题,最后查了半天才发现是版本匹配错了。今天咱们就聊聊,基站固件里版本号到底该怎么定。

2.1 语义化版本号(SemVer)—— 行业通用标准

语义化版本号,说白了就是 主版本号.次版本号.修订号 这个三段式结构。格式是:

MAJOR.MINOR.PATCH

规则其实很清晰:

  • 主版本号(MAJOR):做了不兼容的 API 修改时递增。比如基站从 4G 升级到 5G 的协议栈,这种改动必须升主版本。
  • 次版本号(MINOR):向下兼容的功能性新增时递增。比如新增了一个调试接口,不影响现有功能。
  • 修订号(PATCH):向下兼容的问题修正时递增。比如修复了一个内存泄漏,或者优化了某个算法。

重要原则:版本号一旦发布,就不能再修改。任何改动都必须发布新版本。

举个例子,我参与过的一个基站项目,初始版本是 1.0.0。后来发现射频校准算法有 bug,修复后发布 1.0.1。再后来增加了对 5G NR 频段的支持,但兼容旧版,就升到 1.1.0。如果哪天协议栈大改,不兼容旧版了,那就直接跳到 2.0.0

2.2 内部构建号 —— 开发阶段的“身份证”

语义化版本号是给用户看的,但开发阶段还需要更细粒度的标识。这就是内部构建号(Build Number)的用处。

我习惯用 日期.流水号 的格式:

20250321.01

这个格式的好处是:

  • 日期部分:一眼就能看出是什么时候编译的。比如 20250321 就是 2025 年 3 月 21 日。
  • 流水号:同一天可能有多次构建,用 .01.02 来区分。

嗯,这里要注意。构建号只在内部使用,不对外发布。对外发布的版本,还是用语义化版本号。

我的习惯:每次 CI 构建时自动生成构建号,并嵌入到固件镜像的头部信息里。这样即使固件跑起来了,也能通过命令行查到这个镜像是什么时候、第几次构建的。

2.3 版本号在基站中的实际应用

基站系统比普通嵌入式设备复杂得多。一个基站通常包含多个子系统:

  • 基带处理单元(BBU):负责协议栈和信号处理
  • 射频拉远单元(RRU):负责射频信号的收发
  • 管理单元(OMC):负责配置管理和监控

每个子系统都有自己的版本号。但问题是,这些版本号之间必须能互相匹配。否则就会出现“BBU 升级了,RRU 还是旧版,结果通信失败”的情况。

我在项目中遇到过这样的问题:有一次现场升级,BBU 从 2.1.0 升到了 2.2.0,但 RRU 还是 1.3.0。结果基站启动后,RRU 上报的数据格式不兼容,BBU 解析出错,整站瘫痪。后来我们加了一个版本兼容性检查机制。

2.3.1 版本兼容性矩阵

我们做了一个版本兼容性矩阵,放在固件包的描述文件里:

BBU 版本 RRU 最低版本 OMC 最低版本 兼容性
2.0.0 1.2.0 1.0.0 兼容
2.1.0 1.3.0 1.1.0 兼容
2.2.0 1.4.0 1.2.0 兼容

升级前,系统会自动检查各子系统的版本是否在兼容范围内。如果不匹配,直接拒绝升级。

2.3.2 版本号在升级包中的体现

一个完整的基站固件升级包,命名规则是这样的:

BS_FW_BBU_2.2.0_RRU_1.4.0_OMC_1.2.0_20250321.01.bin

这个命名包含了:

  • 产品代号:BS_FW
  • 各子系统版本:BBU_2.2.0RRU_1.4.0OMC_1.2.0
  • 构建号:20250321.01

避坑指南:我曾经见过有人把构建号直接当版本号用,结果用户看到 20250321.01 这种版本号,根本不知道这是大版本还是小版本。记住,对外发布一定要用语义化版本号,构建号只用于内部追溯。

2.3.3 版本号的存储与读取

在基站固件中,版本号通常存储在固件镜像的固定偏移位置。比如:

// 版本号结构体
typedef struct {
    uint8_t major;      // 主版本号
    uint8_t minor;      // 次版本号
    uint8_t patch;      // 修订号
    uint8_t build[8];   // 构建号,如 "20250321"
} fw_version_t;

// 存储在固件头部偏移 0x100 处
const fw_version_t __attribute__((section(".version"))) fw_ver = {
    .major = 2,
    .minor = 2,
    .patch = 0,
    .build = "20250321"
};

这样,无论是通过命令行、Web 界面还是 SNMP,都能直接读取到版本信息。我建议在系统启动时,把版本号打印到日志里,方便后续排查问题。

2.4 总结一下

版本号规范这事儿,说白了就是三个要点:

  1. 对外用 SemVer:主版本.次版本.修订号,清晰表达兼容性。
  2. 对内用构建号:日期.流水号,方便追溯每次构建。
  3. 基站场景要联动:各子系统版本必须匹配,用兼容性矩阵来保证。

你想想看,如果版本号乱打,现场升级出问题,那可不是闹着玩的。基站一瘫,可能就是几万个用户没信号。所以,版本号规范这事儿,值得花心思做好。