如何从SIGBUS恢复?FPGA触发合法memcpy访问SIGBUS后的应用重置方案
FPGA通信场景下SIGBUS触发后的应用重置方案
问题背景
设计FPGA时,将合法memcpy访问mmap地址时偶尔产生的SIGBUS信号作为特殊通信机制,当前采用sigsetjmp/siglongjmp实现异常跳转重置,但遇到SIGBUS后程序会在memcpy处挂起,即使执行unmap后重新mmap,新地址上的memcpy仍会挂起。
问题根源
- 信号上下文残留:从信号处理函数用
siglongjmp跳回后,memcpy的执行上下文处于中断异常状态,内核未完全清理该内存访问的异常标记,直接重试会重复触发问题。 - FPGA状态未复位:SIGBUS是FPGA主动触发的,说明此时FPGA处于特殊状态,仅操作内存映射无法解决硬件层面的异常。
- 代码参数错误:原代码中
memcpy(mmapAddr, offset, data, sizeBytes)参数完全错误,memcpy仅接受3个参数,正确写法应为memcpy(static_cast<char*>(mmapAddr) + offset, data, sizeBytes),参数错误本身就会导致非法访问。
最佳重置方案
1. 修正信号处理与跳转逻辑
信号处理函数必须保证异步安全,且跳转前需确保信号掩码正常:
static sigjmp_buf jmpbuf; static volatile int sigbus_triggered = 0; void sigbus_handler(int signum) { // 异步安全输出,避免使用std::cout(非异步安全) const char notify[] = "[INFO] SIGBUS triggered, entering reset flow\n"; write(STDERR_FILENO, notify, sizeof(notify)-1); sigbus_triggered = signum; // 跳回sigsetjmp保存的上下文 siglongjmp(jmpbuf, signum); }
2. 标准化重置流程
跳回sigsetjmp分支后,必须按硬件复位→清理映射→重建映射→状态初始化的顺序执行:
// 初始化信号处理 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigbus_handler; sa.sa_flags = SA_NODEFER; // 允许嵌套触发SIGBUS(根据需求调整) sigaction(SIGBUS, &sa, NULL); // 核心访问逻辑 while (true) { if (sigsetjmp(jmpbuf, 1)) { // 步骤1:复位FPGA硬件状态(关键!优先处理硬件) memory.resetFPGA(); // 实现:向FPGA控制寄存器写入复位命令 // 步骤2:清理旧内存映射 if (memory.mmapAddr != MAP_FAILED) { munmap(memory.mmapAddr, memory.mmapSize); memory.mmapAddr = MAP_FAILED; } // 步骤3:重建内存映射 if (!memory.recreateMmap()) { const char err[] = "[ERROR] Failed to recreate mmap\n"; write(STDERR_FILENO, err, sizeof(err)-1); continue; } // 步骤4:初始化应用内部状态 memory.fixThings(); const char done[] = "[INFO] Reset completed, resuming operation\n"; write(STDERR_FILENO, done, sizeof(done)-1); } else { // 正常执行内存写入(先修正memcpy参数) memory.writeMem(offset, dataPtr, size); } }
3. 修正内存写入函数
修复memClass::writeMem的memcpy参数错误:
void memClass::writeMem(uint32_t offset, void* data, uint32_t sizeBytes) { // 确保mmapAddr有效,且偏移不超过映射范围 if (mmapAddr == MAP_FAILED || (offset + sizeBytes) > mmapSize) { const char err[] = "[ERROR] Invalid memory access parameters\n"; write(STDERR_FILENO, err, sizeof(err)-1); return; } // 正确计算目标地址:mmap基地址 + 偏移 memcpy(static_cast<char*>(mmapAddr) + offset, data, sizeBytes); }
4. 更可靠的替代方案(线程隔离)
如果sigsetjmp/siglongjmp的上下文残留问题难以解决,可采用线程隔离方案:
- 将内存访问逻辑放到独立线程中,SIGBUS触发时直接终止该线程。
- 主线程收到信号后,先复位FPGA,再重新创建线程并初始化内存映射,彻底清除异常上下文。
内容的提问来源于stack exchange,提问作者fm_user8
相关产品推荐
相关产品推荐

