ARM平台ASIO用udmabuf DMA缓冲区async_write()随机报EFAULT错误
使用Boost ASIO async_write写入udmabuf映射内存时随机出现"Bad Address"错误
环境与场景
运行于ARM Cortex-A53平台,通过g交叉编译的C程序:
- 从udmabuf管理的65536字节(64KiB)内存映射缓冲区读取数据,该缓冲区物理地址固定,通过/dev/udmabuf0暴露给用户空间,以O_SYNC方式映射(禁用CPU缓存)
- 硬件通过DMA填充缓冲区后,调用
write_data函数,使用Boost 1.74 ASIO的async_write将数据发送至远端客户端(客户端处理速度快于主机发送速度) - io_context为单线程运行:
boost::asio::io_context io_context_ {1}; ... io_context_.run();
核心代码
Session类中负责发送缓冲区数据的函数:
class Session: public std::enable_shared_from_this<Session> { private: boost::asio::ip::tcp::socket socket_; ... public: void write_data(void * buffer_mm, std::size_t total_bytes_transferred) { auto buffer = boost::asio::const_buffer(static_cast<const uint8_t *>(buffer_mm)); boost::asio::async_write( socket_, buffer, [total_bytes_transferred, me = shared_from_this()] (const error_code & ec, std::size_t bytes_transferred) { if (ec) { BOOST_LOG_TRIVIAL(error) << "write_data [" << ec << "] " << ec.message(); // stop sending data. } else { BOOST_LOG_TRIVIAL(debug) << "write_data " << bytes_transferred << " bytes"; // continue writing data... me->write_footer(total_bytes_transferred + bytes_transferred); } }); } };
程序中还有write_header()和write_footer()函数用于发送静态数据,这两个异步写操作从未出现错误。
错误现象
循环执行「DMA填充缓冲区→写入Socket」流程数百次(无额外延迟,尽可能快执行),随机次数迭代后(最少5次,最多50或偶尔100次),async_write的完成Lambda会返回*"Bad Address"错误(对应系统错误码14,即EFAULT*,表示系统调用收到无效用户空间内存指针)。
但每次传入async_write的都是同一缓冲区地址,并未发生变更,socket状态看似正常。
排查细节
- 延迟影响:在
async_write前添加45ms延迟,错误不再出现;40ms延迟会降低错误出现频率;35ms延迟无效果。 - 数据大小相关性:错误仅在传输大小处于[65524, 65536]区间且为4字节对齐时触发;多4字节或少16字节均无错误,似乎仅与传入
async_write的数据大小有关,与底层缓冲区大小无关。 - 同步/异步一致性:使用同步
write()替代async_write(),仍会出现相同错误。 - 性能特征:udmabuf禁用CPU缓存,数据无需经过CPU即可传输至Socket,吞吐量可达近900 Mbps。
补充信息
数月前最初编写代码时,曾尝试将头部、数据、尾部通过std::vector<boost::asio::const_buffer>整合为一个async_write调用(分散/聚集写入),当时若头部长度不是4字节的倍数,调用会偶尔返回*"Bad Address"*错误,无明确触发规律。后来改为三次独立的async_write调用,问题暂时消失,直到现在复现。
新发现
- 当65536字节写入触发错误时,
bytes_transferred始终为32737字节(比32768少31),客户端也能读取到对应字节数。 - 若将所有Socket写操作的长度改为8字节的倍数,问题完全解决,但会破坏协议(需额外填充数据)。
- 错误触发无确定性:相同输入的测试有时触发(概率约50%),有时多次运行无错误,推测与系统内部状态有关。
疑问
- 同一缓冲区地址为何会突然变为无效?
async_write的调用方式是否存在根本问题?Session对象的生命周期是否正确?- 单线程io_context场景下是否需要使用strand?原以为strand仅用于多线程io_context场景。
- 32737这个数字的意义是什么?
内容的提问来源于stack exchange,提问作者davidA
相关产品推荐
相关产品推荐

