使用sendfile()出现EINVAL错误,但read()正常的问题排查
关于sendfile()返回EINVAL错误的排查分析
问题场景
- 调用
sendfile()向socket发送文件时返回EINVAL Invalid argument错误,但相同参数使用read()可正常工作 - 尝试传入两个普通文件描述符调用
sendfile()仍触发相同错误 - 运行环境:VirtualBox 6.1.38 托管 Ubuntu 22.04.1 LTS,内核5.15.0-52-generic,glibc 2.35
- 涉及测试程序:基于
sendfile()的文件复制程序、向TCP客户端发送文件的服务器程序(替换为send()可正常运行)
常见触发EINVAL的核心原因(Linux环境)
1. 文件描述符类型不兼容
sendfile()对两个文件描述符有严格的类型要求:
- 输入描述符
in_fd必须是支持mmap/seek操作的常规文件(不能是管道、socket、字符设备等) - 输出描述符
out_fd:- 内核5.10及以后版本支持常规文件,但需确保内核开启了
CONFIG_SENDFILE和CONFIG_SENDFILE_SYSCALL配置 - 若
out_fd是socket,必须是已连接的TCP socket(UDP socket不支持sendfile())
只要其中一个fd不符合要求,直接触发EINVAL。
- 内核5.10及以后版本支持常规文件,但需确保内核开启了
2. 参数值非法
offset指针为NULL,但in_fd是socket(socket不支持偏移定位,此时in_fd本身就不符合要求)count参数为0,或超过系统限制SSIZE_MAXoffset指向的内存地址不可读(比如野指针、未初始化的指针)
3. 文件打开模式错误
in_fd未以可读模式打开(必须是O_RDONLY或O_RDWR)out_fd未以可写模式打开(必须是O_WRONLY或O_RDWR)
4. 虚拟机/内核特殊限制
- VirtualBox的虚拟IO层可能对文件描述符的seek能力做了限制,导致内核认为
in_fd不是常规文件 - 部分glibc版本对
sendfile()的封装会做额外参数校验,比内核逻辑更严格
快速排查步骤
验证文件描述符类型
使用fstat()检查两个fd的类型:#include <sys/stat.h> // 检查in_fd是否为常规文件 struct stat st_in; if (fstat(in_fd, &st_in) == -1 || !S_ISREG(st_in.st_mode)) { fprintf(stderr, "in_fd is not a regular file\n"); } // 检查out_fd是否为socket或常规文件 struct stat st_out; if (fstat(out_fd, &st_out) == -1) { perror("fstat out_fd"); } else if (!S_ISSOCK(st_out.st_mode) && !S_ISREG(st_out.st_mode)) { fprintf(stderr, "out_fd is not a socket or regular file\n"); }检查参数合法性
- 确保
count > 0且count <= SSIZE_MAX - 若
in_fd是常规文件,offset可以是有效指针或NULL(NULL表示从当前文件偏移开始);绝对不能给socket类型的in_fd传非NULL的offset
- 确保
验证文件打开模式
检查open调用的参数:// 示例:正确的打开方式 int in_fd = open("test.txt", O_RDONLY); int sock_fd = accept(listen_fd, NULL, NULL); // 已连接的TCP socket运行最小测试用例
编译运行以下极简程序,确认基础功能是否正常:#include <sys/sendfile.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { int in_fd = open("input.txt", O_RDONLY); int out_fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (in_fd == -1 || out_fd == -1) { perror("open"); exit(EXIT_FAILURE); } off_t offset = 0; ssize_t ret = sendfile(out_fd, in_fd, &offset, 1024); if (ret == -1) { perror("sendfile"); } else { printf("Sent %zd bytes\n", ret); } close(in_fd); close(out_fd); return EXIT_SUCCESS; }查看内核日志
执行dmesg | grep -i sendfile,检查是否有内核层面的错误提示。
内容的提问来源于stack exchange,提问作者kancler
相关产品推荐
相关产品推荐

