为何字符设备驱动收到的读取长度远超系统调用指定的1字节?
字符设备驱动读取长度不匹配问题解析
问题背景
我正在测试一款可输出单个字符的字符设备,使用如下C++程序进行测试:
int main() // c++ program { while (true) { char tmp = 'x'; std::ifstream ms_fd; ms_fd.open(device_path, std::ios::binary); ms_fd.read(&tmp, 1); std::cout << "val: " << tmp << std::endl; ms_fd.read(&tmp, 1); assert(ms_fd.eof()); ms_fd.close(); sleep(2); } return 0; }
代码中每次调用read请求读取1字节,但设备驱动的read函数收到的读取长度却为8191。
驱动日志信息如下:
[ 204.738324] READDRIVE: reading len: [8191] offset: [0] [ 204.738590] READDRIVE: reading len: [8191] offset: [1] [ 206.739116] READDRIVE: reading len: [8191] offset: [0] [ 206.739235] READDRIVE: reading len: [8191] offset: [1]
驱动read函数实现:
static ssize_t pir_ms_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info("READDRIVE: reading len: [%d] offset: [%d]\n", len, *off); ssize_t retval; if (*off > 0) { retval = 0; } else { put_user(buf_state, buf); retval = 1; *off+=1; } return retval; }
为何驱动未接收到指定的1字节读取请求?
核心原因
问题出在C++标准库的std::ifstream默认行为上:它会启用用户态缓冲区优化,不会直接将代码中指定的1字节读取请求传递给内核的read系统调用。
具体流程:
- 当你调用
ms_fd.read(&tmp, 1)时,ifstream会主动向内核发起一个批量读取请求(默认缓冲区大小通常为8191字节),将数据读取到自己维护的用户态缓冲区中。 - 之后
ifstream再从自身缓冲区中取出1字节返回给你的代码,因此内核驱动收到的是缓冲区大小级别的读取请求,而非你指定的1字节。
解决方案
要让读取请求精确传递给驱动,需要绕过ifstream的缓冲区机制,有两种可行方式:
1. 禁用ifstream的缓冲区
在打开设备文件后,添加以下代码关闭缓冲区同步并禁用流缓冲区:
ms_fd.open(device_path, std::ios::binary); ms_fd.sync_with_stdio(false); // 关闭与C标准IO的同步 ms_fd.rdbuf()->pubsetbuf(nullptr, 0); // 禁用流自身的缓冲区
2. 直接使用系统调用read
绕过C++标准库封装,直接调用Linux系统级read函数,确保请求参数直接传递给驱动:
#include <fcntl.h> #include <unistd.h> int main() { while (true) { char tmp = 'x'; int fd = open(device_path, O_RDONLY | O_BINARY); read(fd, &tmp, 1); std::cout << "val: " << tmp << std::endl; ssize_t ret = read(fd, &tmp, 1); assert(ret == 0); // 此时应该返回EOF(0) close(fd); sleep(2); } return 0; }
驱动代码优化建议
你的驱动read函数存在两个潜在问题:
- 格式符使用错误:
size_t和loff_t属于宽度不确定的类型,不能用%d打印,应替换为:pr_info("READDRIVE: reading len: [%zu] offset: [%lld]\n", len, *off); - EOF处理逻辑:当
*off > 0时返回0符合EOF规范,但如果设备是单次打开仅能读取一个字符,当前逻辑是合理的,无需修改。
内容的提问来源于stack exchange,提问作者shafe
相关产品推荐
相关产品推荐

