在Linux中,read 函数是最常用的系统调用之一,用于从文件或其他输入设备读取数据。它是低级别的I/O操作的核心,直接与操作系统的内核交互,提供了高效的数据读取方式。
1.函数介绍
头文件:#include <unistd.h>
定义函数: ssize_t read(int fd, void * buf, size_t count);
函数说明:
- fd 是文件描述符,代表了需要读取的文件或设备。文件描述符可以通过调用 open 或其他文件操作函数获取。
- buf 是一个指向用户分配的缓冲区的指针,read 将把读取到的数据写入该缓冲区。
- count 是需要读取的字节数,表示最多读取 count 字节数据。
返回值:
若成功,read 返回读取的字节数(0表示已到达文件末尾)。
若失败,返回 -1,并设置 errno 来指示具体错误。
此外文件读写位置会随读取到的字节移动。当到达文件末尾时,read 会返回 0,表示没有更多的数据可读。这是进程得知文件已经读取完毕的信号。
2.read工作原理
read 函数是一个阻塞调用,意味着如果请求的数据还没有准备好,它会使调用的进程挂起,直到有数据可读或发生错误。内核根据文件描述符查找到对应的文件系统对象,执行相应的读取操作。以下是其工作流程的简要概述:
- 获取文件描述符:文件描述符是打开文件时分配的,它是内核中维护文件表的索引。
- 检查文件状态:内核会检查文件描述符是否有效,文件是否已打开,并且具有读权限。
- 读取数据:从文件或设备中读取数据,并将其复制到用户提供的缓冲区 buf 中。
- 返回结果:返回读取的字节数,若遇到错误则返回 -1,并通过 errno 提供错误信息。
3.常见用法
read 函数经常与 open、close 以及其他系统调用一起使用。以下是一个简单的示例,它展示了如何从文件中读取数据:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
ssize_t bytesRead;
char buffer[1024]; // 定义一个缓冲区用于存储读取的数据
// 打开文件
fd = open("example.txt", O_RDONLY);//O_RDONLY:以只读方式打开文件。
if (fd == -1) {
perror("Failed to open file");
return 1;
}
// 读取数据
bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead == -1) {
perror("Failed to read file");
close(fd);
return 1;
}
// 确保字符串末尾以 '\0' 结束
buffer[bytesRead] = '\0';
// 输出读取到的数据
printf("Read %zd bytes: %s\n", bytesRead, buffer);
// 关闭文件
close(fd);
return 0;
}
在该示例中:
- 使用 open 以只读模式打开文件。(O_RDONLY:以只读方式打开文件。)
- 调用 read 将文件的内容读取到 buffer 中。
- 读取后输出数据,并在最后调用 close 关闭文件。
4.错误处理
read 调用可能会因为多种原因失败,一些常见的错误包括:
- EINTR:调用被信号中断。
- EIO:发生I/O错误(如硬件故障)。
- EINVAL:参数不合法,比如文件描述符不是合法的读取对象。
- EBADF:文件描述符无效,可能因为文件未打开或者以不适合的方式打开(如只写模式下无法读取)。
- EFAULT:缓冲区地址不合法。
在编写系统级程序时,必须对这些错误进行适当的处理。通常,在每次 read 调用后检查返回值是否为 -1,并根据 errno 来做出相应的处理决策。
5.非阻塞读取
read 默认是阻塞的,即当数据不可用时会阻塞进程。然而,可以使用 O_NONBLOCK 标志将文件或设备设置为非阻塞模式。当文件描述符以非阻塞模式打开时,若数据不可用,read 将立即返回 -1,并设置 errno 为 EAGAIN 或 EWOULDBLOCK。
示例如下:
fd = open("example.txt", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("Failed to open file in non-blocking mode");
return 1;
}
bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead == -1 && errno == EAGAIN) {
printf("No data available yet, try again later.\n");
}
6.与缓冲区的关系
read 函数直接与内核缓冲区交互。
文件系统通常维护一个内核缓冲区,用于缓存文件数据,以减少磁盘 I/O 操作的次数。
read 调用从内核缓冲区中读取数据到用户空间,若缓冲区为空,则从磁盘或设备读取数据到缓冲区。
在设计高性能系统时,选择适当的缓冲区大小非常重要。过小的缓冲区会导致频繁的系统调用,从而影响性能;而过大的缓冲区则会消耗过多的内存。
参考文章
1 - C语言read()函数:读文件函数
2 - Linux下read函数详解
文章2包含read 在不同设备中的应用:从标准输入读取、从管道读取、从套接字读取