EMS 知识库
首页 / knowledge/代码解析/data/net_event_log

net_event_log.c

源文件路径: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_bufstatic 数组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)各执行一次相同流程:

  1. Flash_Read(s_u8_data_buf, <sector>, FLASH_SECTOR_SIZE),失败即 return。
  2. 从 i = 0 起扫描,遇到连续 4 个 0xFF 即认为是"第一个空闲槽"并 break;循环上限 i < 4096 - 4096%4 = 4092(防止越界读 buf[i+3])。
  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)

偏移长度字段取值来源
02u16Head固定 0x55AA
21年SYSTEM_DATETIME.bYears
31月SYSTEM_DATETIME.bMonths
41日SYSTEM_DATETIME.bDays
51时SYSTEM_DATETIME.bHours
61分SYSTEM_DATETIME.bMinutes
71秒SYSTEM_DATETIME.bSeconds
81事件码入参 code(置位码或清零码)
92CRCGetCrcCode_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,则写一条"清零码"记录。

事件位掩码位生效条件写入码
物理连接建立bHardLinkbBit0新值 = TRUEu8SetCode[0x00]
物理连接断开bHardLinkbBit0新值 = FALSEu8ClearCode[0x00]
DHCP 成功bDHCPbBit1新值 = TRUEu8SetCode[0x01]
DHCP 失败bDHCPbBit1新值 = FALSEu8ClearCode[0x01]
DNS 成功bDNSbBit2新值 = TRUEu8SetCode[0x02]
DNS 失败bDNSbBit2新值 = FALSEu8ClearCode[0x02]
Socket 状态变化bSocketStatusbBit3新值 = TRUE(无旧值比较)g_stNetSysInfo.u8SocketStatus
心跳 3 分钟无回复bHeartBeatFailbBit4新值 = TRUEu8SetCode[0x04]
连续 9 包心跳失败并复位bHeartBeatRstbBit5新值 = TRUEu8SetCode[0x05]
OSQ 调用失败超限并复位bOSQHandleFailbBit6新值 = TRUEu8SetCode[0x06]
EthTask 运行超时并复位bEthTaskTimeOutbBit7新值 = TRUEu8SetCode[0x07]
EMSTask 运行超时并复位bEMSTaskTimeOutbBit8新值 = TRUEu8SetCode[0x08]
系统严重故障复位bSeriousFaultRstbBit10新值 = TRUEu8SetCode[0x0A]

几个要点:

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);

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否仅在文件内使用

调用的外部函数与变量

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 定位缓冲)。

被依赖

7. 备注与风险点

  1. 整个模块当前是死代码: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,这些状态位目前不会被消费,也不会落盘。
  2. 地址区间重叠: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 尾部的地址记录。
  3. 扇区粒度与宏定义不自洽:FLASH_SECTOR_SIZE 为 4KB,但 END3 - START3 + 1 = 0x1F00(约 7.75KB);write_event_log_addr_to_flash 的扫描上限用的是 FLASH_SECTOR_SIZE(4096),与 START3/END3 的跨度不一致,属于历史遗留的定义混乱。
  4. 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 字节结构)之外多读若干字节。
  5. read_event_log_data 到达区间头部即 break(net_event_log.c:562-566):语义上应"回绕到末尾继续回溯",实际是直接停止,导致最多拿不到 16 条记录;该分支之后 l_u8Res 保留的是上一次 Flash_Read 的返回值,可能不等于 FLASH_OK,调用方据此判断成功会出错。
  6. read_event_log_data 不校验帧头:仅用 CRC 判定有效,u16Head == 0x55AA 从未参与判断(对比 flash_data_store.c 中会同时检查帧头)。
  7. Socket 事件码语义不一:其余事件写的是 u8SetCode[]/u8ClearCode[] 生成的配对码,Socket 事件直接写 g_stNetSysInfo.u8SocketStatus 原始值(net_event_log.c:470),字段含义在同一条日志里不一致;且该分支只处理"建立",没有"断开"记录。
  8. 地址写入的扇区 2 else 分支忽略返回值(net_event_log.c:274),与扇区 1/3 的处理不一致,写失败时不会中断。
  9. g_stEventLogAddrFirst/Second/Third、g_stCmdEventLogInfo、g_stEventLogStoreInfo、以及 event_log_tab_init/event_log_addr_init/write_* 均为非 static 全局符号但未在头文件声明,可能导致命名冲突且无法被其他模块正常引用。
  10. 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 就失败"时才会重试一次。
  11. 无任何并发保护:写指针 g_stEventLogAddr 与 SDRAM 缓冲区 s_u8_data_buf 是全局共享状态,若 scan_system_event() 与 read_event_log_data() 分属不同任务(当前都未被调用,但设计意图如此),会存在竞态。
  12. 魔法数字:16(回溯条数)、11(记录长度,隐式依赖 sizeof(EventLogStoreTab))、2(时间/CRC 长度累加)、3(初始化重试次数)、0x55AA、1024(隐含的每扇区槽数)散落各处;EVENT_CODE_NET_SOCK_STATUS 宏已定义但未被使用。