为何explain_fwrite提示文件被标记为非阻塞,fwrite返回EAGAIN?
问题根因分析
1. EAGAIN报错的原因
你没有主动设置O_NONBLOCK标记不代表文件描述符的标记没有被修改:
- C标准库
fopen返回的FILE*对应的底层文件描述符默认是阻塞模式,但如果你的代码其他位置对fileno(fd)返回的文件描述符执行过fcntl(F_SETFL)操作,误加了O_NONBLOCK标记,就会导致fwrite触发EAGAIN。如果你的项目引入了第三方库,也有可能是第三方库错误修改了已打开的文件描述符标记。 - 少见场景下,如果你写入的
/home目录挂载的是特殊文件系统(比如FUSE用户态文件系统、带特殊挂载参数的NFS/SMB网络文件系统),内核层面也可能直接返回EAGAIN,explain_fwrite只是按照通用场景给出解释。
2. ENOENT报错的原因
explain_fwrite给出的原因是推测性结论,并非100%匹配实际场景:
- 你遇到的ENOENT不是因为文件被删除,是第一次
fwrite触发EAGAIN失败后,FILE*的内部错误标记被置位,你没有清除错误标记就继续调用fwrite,会导致后续所有fwrite直接失败,errno保留旧值或者被错误设置,explain_fwrite读取到错误上下文就给出了不符合实际的原因推测。 - 小概率场景是多线程运行时,你传入的
file_to_write路径变量被其他线程修改,或者路径包含隐性不可见字符,但你确认文件始终存在的话,基本可以排除这类问题。
额外的代码逻辑错误
你的原始代码中fwrite的返回值判断逻辑本身是错误的:你调用fwrite时传入的参数是size=write_size, nmemb=1,这种情况下fwrite成功时返回的是成功写入的块数也就是1,不是write_size,只要write_size>1,你的判断条件n != write_size永远会成立,误判写入失败。
修复方案
- 校验并修正文件描述符的阻塞标记:在
fopen之后、fwrite之前添加代码,清除可能存在的O_NONBLOCK标记:
// 打开文件后添加 int fd_num = fileno(fd); int flags = fcntl(fd_num, F_GETFL, 0); if (flags & O_NONBLOCK) { fcntl(fd_num, F_SETFL, flags & ~O_NONBLOCK); }
- 修正
fwrite的调用参数和判断逻辑,添加失败重试逻辑,保证数据完全写入:
// 将fwrite的参数调整为按字节计数,返回值就是实际写入的字节数 size_t total_written = 0; size_t remain = write_size; char* write_ptr = (char*)h->bufs[i]; while (remain > 0) { size_t n = fwrite(write_ptr, 1, remain, fd); if (n == 0) { int err = errno; // 信号中断、EAGAIN场景清除错误标记后重试 if (err == EINTR || err == EAGAIN) { clearerr(fd); continue; } // 其他错误才判定为永久失败 printf("fwrite permanent failed, errno=%d: %s\n", err, explain_fwrite(write_ptr, 1, remain, fd)); break; } total_written += n; remain -= n; write_ptr += n; }
内容的提问来源于stack exchange,提问作者Brydon Gibson
相关产品推荐
相关产品推荐

