memfd_create创建的内存fd调用read无数据不阻塞如何解决
问题背景
此前尝试创建行为与字符设备完全一致的内存文件,最初调用memfd_create(path, 0)实现,经测试判断该接口无法满足需求。
核心需求为:通过LD_PRELOAD机制hook open函数,当检测到程序调用open("/dev/input/event0")时,将返回的文件描述符替换为自定义内存fd;后续实现数据队列供被hook的应用读取,要求读取行为与真实设备文件完全一致。
测试中遇到的问题:对memfd_create返回的fd调用read()时,即使当前无可读字节,read也不会阻塞,需要正确的实现方向指引。
附测试代码:
struct input_event ev; int kbd = memfd_create("/dev/input/event0", 0); read(kbd, &ev, sizeof(ev)); //<-- 如何让该read调用默认阻塞?
问题根因
memfd_create创建的是匿名内存支持的普通临时文件,属于普通文件类型,VFS层对普通文件的read操作天生没有阻塞等待逻辑:只要文件当前偏移指向末尾无数据可读,read会直接返回0,不会等待新数据写入。这是普通文件和字符设备的核心语义差异,无论怎么修改fd的文件状态标记都无法改变这个底层行为,因此memfd从设计上就不匹配这个场景的需求。
可行实现方案
- 优先选择:用UNIX域套接字对/匿名管道替代memfd
这两类IPC对象返回的fd天生符合阻塞读语义:当接收缓冲区无数据时,read会自动阻塞,直到有新数据写入、对端关闭才会返回,和字符设备的读行为基本一致。
实现逻辑很简单:hook到open("/dev/input/event0")调用时,调用socketpair(AF_UNIX, SOCK_STREAM, 0, fds)或者pipe(fds)创建一对连通的fd,将读端返回给被hook的应用,写端留在hook逻辑中,后续生成input_event数据时直接往写端写入即可。
适配注意点:真实的/dev/input/event0是input字符设备,除了read之外还支持一系列专属ioctl命令(比如EVIOCGVERSION、EVIOCGBIT、EVIOCGNAME等),同时支持poll/select/epoll事件通知,你需要同步hook这些系统调用,补全对应语义,才能做到和真实设备行为完全一致。
改造后的示例代码如下:#include <sys/socket.h> #include <linux/input.h> #include <unistd.h> int main() { struct input_event ev; int fds[2]; // 创建字节流套接字对 socketpair(AF_UNIX, SOCK_STREAM, 0, fds); int kbd = fds[0]; // 读端返回给业务应用 // 无数据时该read调用会默认阻塞,符合字符设备行为 read(kbd, &ev, sizeof(ev)); close(kbd); close(fds[1]); return 0; } - 备选方案1:基于FUSE实现用户态文件系统
如果需要更精细的语义控制,可以通过FUSE编写一个极简的用户态文件系统,自己实现read、poll、ioctl、release等所有文件操作回调,完全匹配真实input设备的行为,缺点是需要挂载FUSE文件系统,部署步骤比LD_PRELOAD+socketpair的方案复杂。 - 备选方案2:内核态misc字符设备
自行编写内核模块注册一个misc字符设备,在内核层实现数据队列和阻塞逻辑,行为和真实设备完全一致,但需要root权限加载内核模块,适配成本最高,不适合轻量hook场景。
内容的提问来源于stack exchange,提问作者lonelytransistor
相关产品推荐
相关产品推荐

