崩溃时日志易丢失,须用预分配环形缓冲区+信号拦截+原子操作+write系统调用实现无锁安全日志;单条日志≤4KB,导出前需按\n截取完整行。
崩溃时没日志?得用双缓冲+信号拦截
直接写磁盘的日志在进程崩溃瞬间大概率丢失,因为
或
的缓冲未刷新、文件句柄可能已损坏。必须把日志先攒在内存里,再通过
捕获(如
、
)触发强制导出。但注意:信号处理函数里只能调用异步信号安全函数,
、
、
、
全部禁止——这意味着你不能在信号处理函数里做任何动态内存分配或 STL 操作。
用预分配的环形缓冲区 + 原子索引控制写入
避免运行时分配,整个缓冲区必须在程序启动时静态/全局分配。推荐用
或裸
,配合两个原子整数记录写入位置和已提交长度:
:当前可写起始偏移(多线程写入时用
)
:已完整写入且可被导出的日志字节数(仅由写入线程更新)
每次写入前检查剩余空间:
→ 回绕或丢弃(不阻塞)
写入后用
标记完成(确保内存序为
)
示例关键片段:
信号处理函数里只能用
导出,且必须重入安全
处理器中禁止调用
、
、
、
等——它们内部可能锁、分配、调用非 async-signal-safe 函数。唯一可靠方式是用系统调用
直接写文件描述符:
C知道
CSDN推出的一款AI技术问答工具
下载
立即学习
“
C++免费学习笔记(深入)
”;
提前用
打开日志文件(如
),并在
开头完成
信号处理器中只做三件事:读
、计算有效日志范围、调用
必须用
标记信号标志位(如
),避免编译器优化掉轮询判断
不要在 handler 中调用
或
—— 它们本身可能触发二次崩溃;导出后用
(不刷 stdio 缓冲、不调析构)立即终止
多线程写入需规避锁,但要注意内存可见性
多个线程并发调用
时,不能用互斥锁(
在信号上下文中不安全,且可能死锁)。环形缓冲区靠原子索引 + 无锁写入即可,但有两点硬约束:
单条日志必须短于缓冲区一半(否则回绕时无法保证原子写入),建议限制单条 ≤ 4KB
写入内容需是纯 ASCII 或 UTF-8 字节流,避免宽字符、locale 依赖操作
若需格式化(如带时间戳、线程ID),必须在进入
前完成,并存为 C 风格字符串(
),再传入
导出时,
必须配对
,否则可能读到中间态数据
真正棘手的是:崩溃可能发生在日志写入一半时(比如
被中断)。所以导出逻辑要跳过不完整行——按换行符切分,只保存以
结尾的完整行。这需要在信号 handler 中手动扫描,不能依赖
。
std::ofstreamfprintfsignalSIGSEGVSIGABRTstd::stringstd::vectornewmallocstd::arraychar buffer[N]std::atomic write_pos{0} fetch_addstd::atomic commit_len{0} if (write_pos.load() + len > buffer.size())commit_len.store(write_pos.load())memory_order_releasestatic char g_log_buffer[1024 * 1024];
static std::atomic g_write_pos{0};
static std::atomic g_commit_len{0};
void safe_log(const char* msg) {
size_t pos = g_write_pos.fetch_add(len, std::memory_order_relaxed);
if (pos + len >= sizeof(g_log_buffer)) {
// 回绕:从头开始,覆盖旧日志
pos = 0;
g_write_pos.store(len, std::memory_order_relaxed);
}
memcpy(g_log_buffer + pos, msg, len);
g_commit_len.store(pos + len, std::memory_order_release); // 标记有效终点
}
write()signal()fopenfprintfstd::coutstd::string::c_str()write()open()log_fd = open("crash.log", O_WRONLY | O_CREAT | O_APPEND, 0644)main()g_commit_lenwrite(log_fd, ...)volatile sig_atomic_tvolatile sig_atomic_t g_crashed = 0exit()abort()_exit(128)safe_log()pthread_mutex_locksafe_log()snprintf(buf, sizeof(buf), "[%ld][%d] %s", time(NULL), gettid(), msg)g_commit_len.load(std::memory_order_acquire)memory_order_releasememcpy\nstrtok