net_event_log.c
event_log_tab_init,net_event_log.c:40)4.2 地址三元表决(event_log_addr_init,net_event_log.c:73)4.3 写指针落盘(write_event_log_addr_to_flash,net_event_log.c:214)4.4 记录落盘与环形回绕(write_event_log_data_to_flash,net_event_log.c:313)4.5 记录帧格式(save_event_log_data,net_event_log.c:357)4.6 事件差分状态机(scan_system_event,net_event_log.c:398)4.7 记录回溯与 JSON 上报(read_event_log_data,net_event_log.c:546)4.8 与 uCOS-II 任务/调度的关系5. 对外接口导出的函数导出的全局变量调用的外部函数与变量6. 依赖关系依赖被依赖7. 备注与风险点源文件路径:
data/net_event_log/net_event_log.c所属模块:数据管理 / 网络事件日志(data/net_event_log) 代码行数:620
1. 文件职责
本文件实现 EMS 的"网络/系统事件日志"记录与上报:把以太网链路、DHCP、DNS、Socket、心跳失败、心跳复位、OSQ 处理失败、EthTask/EMSTask 运行超时、严重故障复位共 10 类系统事件按"置位码/清零码"配对写入 SPI Flash 的事件日志环形区(0xFDF000~0xFE6FFF)。每条记录 11 字节,含 0x55AA 帧头、6 字节 BCD 时间、1 字节事件码和 Modbus CRC16,写入位置由三元冗余保存的当前写指针决定。向上它提供 read_event_log_data(),把最近 16 条记录拼成 JSON 文本 {"MsgType":"EventLog",...}。事件的原始触发源在 midware/ethernt/ethernet.c(如 ethernet.c:393/678/1038/1385/1434/1445/1460/1524 直接写 g_stNetEventLogInfo.stNewNetEvent 的位),本文件只做差分检测与落盘。注意:当前版本里 scan_system_event()、event_log_init() 及 read_event_log_data() 的唯一调用者 obtain_event_log_data() 在本工程中均无调用点,整个模块处于"已编译但未被使用"的状态。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
DEBUG_EVENT_LOG | 宏 | 0,本模块调试打印开关 |
EVENT_LOG_ADDR_RECORD_START1/END1 | 宏 | 0xFDC000 / 0xFDCFFF,地址备份区 1 |
EVENT_LOG_ADDR_RECORD_START2/END2 | 宏 | 0xFDD000 / 0xFDDFFF,地址备份区 2 |
EVENT_LOG_ADDR_RECORD_START3/END3 | 宏 | 0xFDE000 / 0xFDFEFF,地址备份区 3 |
EVENT_LOG_ADDR_START / _END | 宏 | 0xFDF000 / 0xFE6FFF,事件日志数据环形区(32KB) |
EVENT_CODE_SIZE | 宏 | 32,事件码表深度 |
EVENT_CODE_NET_HARD_LINK 等 | 宏 | 事件索引:0x00 物理连接、0x01 DHCP、0x02 DNS、0x03 Socket 状态、0x04 心跳超时、0x05 心跳复位、0x06 OSQ 失败、0x07 EthTask 超时、0x08 EMSTask 超时、0x0A 严重故障复位 |
NetEventTab | 联合体(#pragma pack(1)) | u32 u32NetCode 与 32 个位域叠加:bHardLink、bDHCP、bDNS、bSocketStatus、bHeartBeatFail、bHeartBeatRst、bOSQHandleFail、bEthTaskTimeOut、bEMSTaskTimeOut、bBit9、bSeriousFaultRst、bBit11~bBit31 |
CmdEventTab | 联合体(pack(1)) | 命令事件位域,仅 bSysRst 有语义,其余 bBit1~bBit31 占位(本文件未做扫描) |
EventMask | 联合体(pack(1)) | 事件上报开关掩码,u32Mask 与 bBit0~bBit31 叠加;默认 0xffffffff(全开) |
EventLogAddr | 结构体(pack(1)) | 仅一个 u32 u32EventLogAddr(4 字节),地址备份单元 |
NetEventLogTab | 结构体(pack(1)) | stOldNetEvent、stNewNetEvent、stMask、u8SetCode[32]、u8ClearCode[32] |
CmdEventLogTab | 结构体(pack(1)) | 同上结构,但位域类型为 CmdEventTab |
EventLogStoreTab | 结构体(pack(1)) | 落盘记录:u16 u16Head(0x55AA)、u8 u8Time[6](年/月/日/时/分/秒)、u8 u8EventCode、u16 u16CRC,共 11 字节 |
s_u8_data_buf | static 数组 | u8[FLASH_SECTOR_SIZE],__attribute__((at(SDRAM_NET_EVENT_LOG_DATA_ADDR))) 定位在 SDRAM 的 4KB 扇区读写缓冲 |
g_stEventLogAddr | 全局变量 | 当前写指针(三元表决结果) |
g_stEventLogAddrFirst/Second/Third | 全局变量 | 三个备份扇区各自解出的写指针 |
g_stNetEventLogInfo | 全局变量 | NetEvent 完整上下文,导出到 net_event_log.h:209 |
g_stCmdEventLogInfo | 全局变量 | CmdEvent 上下文(本文件仅初始化,无使用) |
g_stEventLogStoreInfo | 全局变量 | 落盘用的记录模板 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
event_log_tab_init | 全局 | 初始化 NetEvent/CmdEvent 两套上下文:旧值与新值清零、掩码置 0xffffffff、逐项生成置位码 i*2+1 与清零码 i*2+2 |
event_log_addr_init | 全局 | 从 3 个备份扇区各解出一个写指针,三元两两比对表决出 g_stEventLogAddr |
event_log_init | 全局 | 最多重试 3 次 event_log_addr_init 直到 FLASH_OK,随后调用 event_log_tab_init |
write_event_log_addr_to_flash | 全局 | 把当前写指针追加写入 3 个备份扇区(找第一个空闲槽) |
write_event_log_data_to_flash | 全局 | 把一条记录写入日志区,处理越过 EVENT_LOG_ADDR_END 的回绕,回绕后写指针回 EVENT_LOG_ADDR_START |
save_event_log_data | 全局 | 组装一条 11 字节记录(帧头 + RTC 时间 + 事件码 + CRC16)并落盘 |
scan_system_event | 全局 | 差分扫描 10 类系统事件,把变化翻译成置位码/清零码后调用 save_event_log_data |
read_event_log_data | 全局 | 从当前写指针向前回溯最多 16 条记录,CRC 校验后拼成 JSON 文本写入 buf |
4. 核心逻辑解析
4.1 事件码表初始化(event_log_tab_init,net_event_log.c:40)
stOldNetEvent.u32NetCode = 0; stNewNetEvent.u32NetCode = 0; stMask.u32Mask = 0xffffffff;
for (i = 0; i < 32; i++) {
u8SetCode[i] = i * 2 + 0x01; // 事件 i 的"发生"码
u8ClearCode[i] = i * 2 + 0x02; // 事件 i 的"清除"码
}
于是事件索引 0~8、10 分别对应码 0x01/0x02、0x03/0x04、…、0x11/0x12、0x15/0x16。CmdEvent 用完全相同的方式初始化,但后续没有任何代码读取 g_stCmdEventLogInfo。
4.2 地址三元表决(event_log_addr_init,net_event_log.c:73)
对 3 个备份扇区(0xFDC000/0xFDD000/0xFDE000)各执行一次相同流程:
Flash_Read(s_u8_data_buf, <sector>, FLASH_SECTOR_SIZE),失败即return。- 从
i = 0起扫描,遇到连续 4 个0xFF即认为是"第一个空闲槽"并break;循环上限i < 4096 - 4096%4 = 4092(防止越界读buf[i+3])。 - 若
i >= sizeof(EventLogAddr)(即 4),则把buf[i-4..i-1]按小端拼成u32作为写指针;否则(扇区全空)取默认值EVENT_LOG_ADDR_START。
三个结果做三选二表决(net_event_log.c:170-177):
若 First == Second 或 First == Third → 取 First
否则若 Second == Third → 取 Second
否则 → 取默认 EVENT_LOG_ADDR_START
event_log_init(net_event_log.c:192)最多重试 3 次读地址,只要一次返回 FLASH_OK 就退出重试(注意:即使返回值是 FLASH_OK 但三次都无有效记录,最终写指针也会是默认值),最后调用 event_log_tab_init()。
4.3 写指针落盘(write_event_log_addr_to_flash,net_event_log.c:214)
对三个备份扇区重复同一流程:
l_u8Flag = FALSE;
读扇区 → 扫描首个连续 4 字节 0xFF 的位置 i
(循环中若 i + 4 > 4096 则置 l_u8Flag = TRUE 并 break)
if (l_u8Flag) 写 (u8*)&g_stEventLogAddr 到 <sector> // 扇区写满,覆盖第一个槽
else 写 (u8*)&g_stEventLogAddr 到 <sector> + i // 追加到空闲槽
即每个备份扇区最多保存 1024 个历史写指针,写满后从头覆盖。扇区 2 与扇区 3 的 else 分支存在差异:扇区 2 的 else 分支未检查 Flash_Write 返回值(net_event_log.c:274),写失败会被静默忽略。
4.4 记录落盘与环形回绕(write_event_log_data_to_flash,net_event_log.c:313)
l_u32Size = len;
if (cur_addr + len > EVENT_LOG_ADDR_END) { // 需要跨越区间末尾
l_u16Remain = FLASH_SECTOR_SIZE - cur_addr % FLASH_SECTOR_SIZE; // 到本扇区末尾的字节数
Flash_Write(buf, cur_addr, l_u16Remain); // 先写右侧零头
if (l_u16Remain > l_u32Size) return FALSE; // 超长保护
l_u32Size -= l_u16Remain;
Flash_Write(buf + l_u16Remain, EVENT_LOG_ADDR_START, l_u32Size); // 回绕到头部续写
cur_addr = EVENT_LOG_ADDR_START + l_u32Size;
} else {
Flash_Write(buf, cur_addr, l_u32Size);
cur_addr += l_u32Size;
}
if (写成功) write_event_log_addr_to_flash(); // 只要有一次成功就保存指针
4.5 记录帧格式(save_event_log_data,net_event_log.c:357)
| 偏移 | 长度 | 字段 | 取值来源 |
|---|---|---|---|
| 0 | 2 | u16Head | 固定 0x55AA |
| 2 | 1 | 年 | SYSTEM_DATETIME.bYears |
| 3 | 1 | 月 | SYSTEM_DATETIME.bMonths |
| 4 | 1 | 日 | SYSTEM_DATETIME.bDays |
| 5 | 1 | 时 | SYSTEM_DATETIME.bHours |
| 6 | 1 | 分 | SYSTEM_DATETIME.bMinutes |
| 7 | 1 | 秒 | SYSTEM_DATETIME.bSeconds |
| 8 | 1 | 事件码 | 入参 code(置位码或清零码) |
| 9 | 2 | CRC | GetCrcCode_Modbus((u8*)&u16Head, l_u32Len),l_u32Len 累计为 9 |
注意 l_u32Len 的累计顺序是 2(帧头) + 6(时间) + 1(事件码) = 9,CRC 覆盖前 9 字节;最后 l_u32Len += 2 得到 11 作为写入长度。写入入口是 &g_stEventLogStoreInfo.u16Head,依赖结构体紧凑布局(#pragma pack(1))。
4.6 事件差分状态机(scan_system_event,net_event_log.c:398)
对每一类事件采用同一模板:比较 stOldNetEvent 与 stNewNetEvent 的对应位;若不同且新值为 TRUE 且掩码对应位为 1,则更新旧值并写一条"置位码"记录;若不同且新值为 FALSE,则写一条"清零码"记录。
| 事件 | 位 | 掩码位 | 生效条件 | 写入码 |
|---|---|---|---|---|
| 物理连接建立 | bHardLink | bBit0 | 新值 = TRUE | u8SetCode[0x00] |
| 物理连接断开 | bHardLink | bBit0 | 新值 = FALSE | u8ClearCode[0x00] |
| DHCP 成功 | bDHCP | bBit1 | 新值 = TRUE | u8SetCode[0x01] |
| DHCP 失败 | bDHCP | bBit1 | 新值 = FALSE | u8ClearCode[0x01] |
| DNS 成功 | bDNS | bBit2 | 新值 = TRUE | u8SetCode[0x02] |
| DNS 失败 | bDNS | bBit2 | 新值 = FALSE | u8ClearCode[0x02] |
| Socket 状态变化 | bSocketStatus | bBit3 | 新值 = TRUE(无旧值比较) | g_stNetSysInfo.u8SocketStatus |
| 心跳 3 分钟无回复 | bHeartBeatFail | bBit4 | 新值 = TRUE | u8SetCode[0x04] |
| 连续 9 包心跳失败并复位 | bHeartBeatRst | bBit5 | 新值 = TRUE | u8SetCode[0x05] |
| OSQ 调用失败超限并复位 | bOSQHandleFail | bBit6 | 新值 = TRUE | u8SetCode[0x06] |
| EthTask 运行超时并复位 | bEthTaskTimeOut | bBit7 | 新值 = TRUE | u8SetCode[0x07] |
| EMSTask 运行超时并复位 | bEMSTaskTimeOut | bBit8 | 新值 = TRUE | u8SetCode[0x08] |
| 系统严重故障复位 | bSeriousFaultRst | bBit10 | 新值 = TRUE | u8SetCode[0x0A] |
几个要点:
- 除 Socket 分支外,其余分支在任何方向变化时都会把
stOldNetEvent的对应位同步为新值,因此同一位的状态变化每次只产生一条记录。 bBit9(事件索引EVENT_CODE_NET_SOCK_STATUS= 0x03 的掩码位附近)没有对应处理分支;bBit9、bBit11~bBit31均为占位。- Socket 分支不复位比较、也不做"清零"记录:它检查新值是否为 TRUE,是则清标志并直接把
g_stNetSysInfo.u8SocketStatus(一个原始状态字节,非u8SetCode生成的配对码)作为事件码写入,语义与其它分支不一致。 - 事件位的生产者是
midware/ethernt/ethernet.c(DHCP/DNS 状态、Socket 建立、心跳超时与复位、OSQ 失败、任务超时、严重故障复位等),本文件只消费。
4.7 记录回溯与 JSON 上报(read_event_log_data,net_event_log.c:546)
l_u32Addr = g_stEventLogAddr.u32EventLogAddr;
for (i = 0; i < 16; i++) {
l_u32Addr -= 11; // 退到上一条记录
if (l_u32Addr < EVENT_LOG_ADDR_START) { // 已到区间头部
l_u32Addr = EVENT_LOG_ADDR_END; // 置为末尾并 break(注意:不继续读)
break;
}
Flash_Read(l_u8LogBuf, l_u32Addr, 11);
crc = buf[10] << 8 | buf[9];
if (crc == GetCrcCode_Modbus(buf, 9)) {
拼 ASCII:"YY/MM/DD HH:MM:SS CC;" 追加到 l_s8Temp[]
}
}
sprintf(buf, "{\"MsgType\":\"EventLog\",\"MsgContent\":\"%s\",\"Description\":\"OK\"}", l_s8Temp);
- 时间字段按十进制拆成两位字符(
v/10 + '0'、v%10 + '0'),因此落盘的u8Time必须是十进制编码而非 BCD;若 RTC 给出的是 BCD 值,显示与写入会不一致。 - 每条记录 21 个字符,16 条最多 336 字节,
l_s8Temp[512]不会溢出。 - 只校验 CRC,不校验
u16Head == 0x55AA:若某个空闲位置恰好 CRC 巧合,会把垃圾数据当记录输出。 - 回溯命中区间头部时直接
break,因此"写指针刚回绕"时最多只能取到回绕点之后的少数记录,不会跨越到区间末尾继续读数。
4.8 与 uCOS-II 任务/调度的关系
本文件内部不使用任何信号量、消息邮箱或临界区,全部为同步 Flash 读写。按设计意图它应由网络任务(task_ethernet 或 task_net_app)周期调用:scan_system_event() 做采集、obtain_event_log_data()(位于 net/ems.c:2414)做上报取数。当前两个函数都没有调用点,见第 7 节。
5. 对外接口
导出的函数
net_event_log.h:212-214 仅导出 3 个:
| 函数 | 头文件声明 | 说明 |
|---|---|---|
event_log_init | 有 | 地址恢复 + 事件表初始化 |
scan_system_event | 有 | 周期采集入口 |
read_event_log_data | 有 | 生成 JSON 文本 |
以下函数为全局但未在头文件声明,只能被本文件或自行 extern 的文件调用:event_log_tab_init、event_log_addr_init、write_event_log_addr_to_flash、write_event_log_data_to_flash、save_event_log_data。
导出的全局变量
| 变量 | 是否在头文件声明 | 说明 |
|---|---|---|
g_stNetEventLogInfo | 是(net_event_log.h:209) | 被 midware/ethernt/ethernet.c 大量写入 stNewNetEvent 各状态位 |
g_stEventLogAddr、g_stEventLogAddrFirst/Second/Third | 否 | 仅在文件内使用 |
g_stCmdEventLogInfo、g_stEventLogStoreInfo | 否 | 仅在文件内使用 |
调用的外部函数与变量
Flash_Read/Flash_Write(hardware/flash/flash.h)、FLASH_SECTOR_SIZE、FLASH_OKGetCrcCode_Modbus(common/crc.h)SYSTEM_DATETIME(data/data.h)g_stNetSysInfo.u8SocketStatus(midware/ethernt/ethernet.h:274)ShowData(仅DEBUG_EVENT_LOG == 1时,来自调试库)sprintf/memset/strlen(标准 C 库)
6. 依赖关系
依赖
includes.h(工程总头)、ethernet.h(g_stNetSysInfo)、net_event_log.h(自身)、flash_data_store.h(借用 FLASH_SECTOR_SIZE 与 Flash 接口)、key.h、crc.h、sdram.h(SDRAM_NET_EVENT_LOG_DATA_ADDR 定位缓冲)。
被依赖
net/ems.c:43include 本头文件,并在obtain_event_log_data()(net/ems.c:2414)中调用read_event_log_data(buf);obtain_event_log_data()本身无调用点。midware/ethernt/ethernet.c:20include 本头文件,直接写g_stNetEventLogInfo.stNewNetEvent的 10 个状态位。data/net_event_log/net_event_log.c被收录在user/EMS_3_0.uvprojx中参与编译。
7. 备注与风险点
- 整个模块当前是死代码:
scan_system_event()、event_log_init()、event_log_tab_init()、event_log_addr_init()、write_event_log_addr_to_flash()、write_event_log_data_to_flash()、save_event_log_data()在工程中均无调用点;read_event_log_data()的唯一入口obtain_event_log_data()(net/ems.c:2414)同样无调用点(全工程 grep 仅命中定义处)。而midware/ethernt/ethernet.c仍在持续写g_stNetEventLogInfo.stNewNetEvent,这些状态位目前不会被消费,也不会落盘。 - 地址区间重叠:
EVENT_LOG_ADDR_RECORD_START3 = 0xFDE000,其声明结束地址EVENT_LOG_ADDR_RECORD_END3 = 0xFDFEFF,而事件数据区起点EVENT_LOG_ADDR_START = 0xFDF000正落在[0xFDE000, 0xFDFEFF]区间内。即"备份区 3"与"数据区"在0xFDF000~0xFDFEFF这 4KB 上重叠。当备份区 3 被追加写到该地址段(空闲槽位置i落到 0x1000 之后)时,会覆盖事件数据;反之事件数据回绕写入头部时也会破坏备份区 3 尾部的地址记录。 - 扇区粒度与宏定义不自洽:
FLASH_SECTOR_SIZE为 4KB,但END3 - START3 + 1 = 0x1F00(约 7.75KB);write_event_log_addr_to_flash的扫描上限用的是FLASH_SECTOR_SIZE(4096),与START3/END3的跨度不一致,属于历史遗留的定义混乱。 write_event_log_data_to_flash可能越界读调用方缓冲(net_event_log.c:324):跨区间末尾时先按l_u16Remain(到本扇区末尾的剩余字节数,最大 4096)写入,随后才判断l_u16Remain > l_u32Size。当l_u16Remain > len时,已经用len字节的实际数据去写了l_u16Remain字节,Flash_Write会从g_stEventLogStoreInfo(11 字节结构)之外多读若干字节。read_event_log_data到达区间头部即break(net_event_log.c:562-566):语义上应"回绕到末尾继续回溯",实际是直接停止,导致最多拿不到 16 条记录;该分支之后l_u8Res保留的是上一次Flash_Read的返回值,可能不等于FLASH_OK,调用方据此判断成功会出错。read_event_log_data不校验帧头:仅用 CRC 判定有效,u16Head == 0x55AA从未参与判断(对比flash_data_store.c中会同时检查帧头)。- Socket 事件码语义不一:其余事件写的是
u8SetCode[]/u8ClearCode[]生成的配对码,Socket 事件直接写g_stNetSysInfo.u8SocketStatus原始值(net_event_log.c:470),字段含义在同一条日志里不一致;且该分支只处理"建立",没有"断开"记录。 - 地址写入的扇区 2
else分支忽略返回值(net_event_log.c:274),与扇区 1/3 的处理不一致,写失败时不会中断。 g_stEventLogAddrFirst/Second/Third、g_stCmdEventLogInfo、g_stEventLogStoreInfo、以及event_log_tab_init/event_log_addr_init/write_*均为非 static 全局符号但未在头文件声明,可能导致命名冲突且无法被其他模块正常引用。event_log_init的重试语义可疑(net_event_log.c:196-203):if(l_u8Res == FLASH_OK) break;会在*第一次读数成功*时立即退出,而event_log_addr_init内部三次Flash_Read的失败都会提前return,因此这个 3 次重试实际上只在"第一次Flash_Read就失败"时才会重试一次。- 无任何并发保护:写指针
g_stEventLogAddr与 SDRAM 缓冲区s_u8_data_buf是全局共享状态,若scan_system_event()与read_event_log_data()分属不同任务(当前都未被调用,但设计意图如此),会存在竞态。 - 魔法数字:
16(回溯条数)、11(记录长度,隐式依赖sizeof(EventLogStoreTab))、2(时间/CRC 长度累加)、3(初始化重试次数)、0x55AA、1024(隐含的每扇区槽数)散落各处;EVENT_CODE_NET_SOCK_STATUS宏已定义但未被使用。