Linux read系统调用详解
read() 系统调用详解
read() 是 Linux 中最基础、最常用的系统调用之一,用于从文件描述符中读取数据。它是 POSIX 标准定义的底层无缓冲 I/O 接口。
一、函数原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
【参数说明】
| 参数 | 说明 |
|---|---|
fd |
文件描述符,表示要读取的文件或其他输入源(如管道、终端、套接字等),必须是以读方式打开的 |
buf |
指向缓冲区的指针,用于存储读取的数据 |
count |
请求读取的字节数 |
【返回值类型说明】
返回值类型 ssize_t 是有符号的 size_t,既能返回正数、0(表示到达文件末尾),也能返回 -1(表示出错)。
二、返回值详解
| 返回值 | 含义 | 说明 |
|---|---|---|
| >0 | 实际读取的字节数 | 成功读取到数据,返回值表示此次调用实际写入 buf 的字节数 |
| =0 | 读到文件结尾(EOF) | 文件偏移位于或超过文件末尾,没有字节可读 |
| -1 | 发生错误 | 调用失败,具体错误原因通过 errno 获取 |
【取返回值小于 count 的情况】
实际读取的字节数可能小于请求的 count 值,这并不意味着错误,常见原因包括:
-
接近文件结尾:距文件末尾剩余的字节数少于
count -
从管道或终端读取:管道/终端的可用数据量少于请求的字节数
-
被信号中断:
read()在读取到部分数据后被信号中断 -
非阻塞 I/O:在非阻塞模式下,当前可用的数据少于请求量
【异步信号安全说明】read() 是异步信号安全函数,可以在信号处理函数中安全调用。
三、errno 错误码速查表
发生错误时,read() 返回 -1 并设置 errno 变量。常见错误码如下:
| 错误码 | 含义 | 典型场景 |
|---|---|---|
EINTR |
读取到数据之前被信号中断 | 慢速设备(如终端、套接字)上阻塞时收到信号 |
EAGAIN |
当前无数据可读(非阻塞模式) | 以 O_NONBLOCK 标志打开文件,但尚未有数据到达 |
EBADF |
无效的文件描述符 | fd 不合法,或不是以读方式打开的 |
EFAULT |
buf 超出可访问的地址空间 |
缓冲区指针指向了不合法的内存区域 |
EINVAL |
fd 关联的对象不可读 |
文件描述符对应的对象不支持读操作 |
EIO |
底层 I/O 错误 | 磁盘错误、后台进程组读取控制终端失败等 |
EISDIR |
fd 指向一个目录 |
尝试从目录中读取数据(目录应使用 readdir 相关接口) |
【EINTR 与 EAGAIN 的特殊处理】
这两个错误码在编程中需要特别关注:
-
EINTR:对于慢速设备(终端、套接字、管道等),read()可能在阻塞期间被信号中断。应用程序收到此错误时应考虑重试read()调用。 -
EAGAIN(部分系统同时定义为EWOULDBLOCK):在非阻塞 I/O 模式下,如果没有立即可用的数据,read()会返回此错误,表示"没有数据,稍后再试"。调用者应在适宜时机重试。
【EWOULDBLOCK说明】
在实际的 Linux 系统中,EAGAIN 和 EWOULDBLOCK 被定义为相同的错误码值,编程时可统一按 EAGAIN 处理。
四、阻塞行为与模式
read() 的阻塞行为取决于打开文件时的标志设置:
| I/O 模式 | read() 行为 |
|---|---|
| 阻塞模式(默认) | 如果当前没有数据可读,read() 会阻塞等待,直到有数据到达、或发生错误、或被信号中断 |
| 非阻塞模式 | 如果当前没有数据可读,read() 不会阻塞,而是立即返回 -1 并将 errno 设置为 EAGAIN(或 EWOULDBLOCK) |
⚠️ 重要:阻塞模式对常规文件无效——常规文件的 read() 通常总是立即返回(除非文件被锁定或发生 I/O 错误),真正的阻塞行为主要发生在管道、FIFO、终端和套接字等慢速设备上。
五、文件偏移管理
每个打开的文件描述符在内核中维护一个当前文件偏移(file offset),指示下一次读/写操作的位置。
-
read()从当前文件偏移处开始读取 -
读取完成后,文件偏移自动增加实际读取的字节数
-
可以使用
lseek()系统调用手动调整偏移位置
【原子性问题与 pread(2)】
如果在多线程或多进程环境中,先执行 lseek() 再执行 read() 会破坏原子性。为解决此问题,Linux 提供了 pread() 系统调用:
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
六、内核实现原理
read() 系统调用的执行流程:-
-
用户态调用:应用程序调用
read()函数 -
系统调用入口:CPU 通过
int 0x80中断(或更现代的syscall指令)从用户态切换到内核态 -
系统调用分发:内核根据系统调用号(
__NR_read)从系统调用表中找到对应的内核函数sys_read() -
具体处理:
sys_read()根据文件类型调用相应的驱动程序读取数据 -
数据拷贝:数据从内核缓冲区拷贝到用户空间的
buf中 -
返回用户态:内核切换回用户态,返回读取的字节数
对于需要物理磁盘 I/O 的读取,sys_read() 最终会调用 ll_rw_block() 等底层块 I/O 函数来读取磁盘数据。
七、与相关函数的对比
vs read() vs pread()
| 特性 | read() |
pread() |
|---|---|---|
| 读取位置 | 从当前文件偏移开始 | 从 offset 参数指定的位置开始 |
| 是否改变文件偏移 | 是,自动增加 | 否,保持原偏移不变 |
| 原子性 | 不保证(lseek+read 非原子) |
原子的,定位和读取不可分割 |
| 适用场景 | 顺序读/写文件 | 多线程/进程并发读取同一文件 |
vs read() vs fread() -
| 特性 | read() |
fread() |
|---|---|---|
| 类型 | 系统调用 | C 标准库函数 |
| 缓冲区 | 无缓冲,直接与内核交互 | 有用户态缓冲区(stdio 缓冲) |
| 性能 | 每次调用都进行系统调用,频繁小 I/O 效率低 | 减少系统调用次数,小 I/O 场景效率更高 |
| 移植性 | POSIX 标准,仅限于 POSIX 系统 | 跨平台,任何支持 C 标准库的环境 |
| 典型场景 | 需要精确控制 I/O 行为的底层编程 | 常规文件读写,如文本处理 |
八、代码示例
以下是一个完整的 read() 使用示例,展示了从标准输入读取数据,并正确处理各种情况:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main() {
char buffer[1024];
ssize_t bytes_read;
size_t total_bytes = 0;
printf("Enter some text: ");
/* 从标准输入(fd = 0)读取最多 1023 字节,留 1 个字节给 '\0' */
bytes_read = read(0, buffer, sizeof(buffer) - 1);
if (bytes_read == -1) {
/* 处理错误:EINTR 和 EAGAIN 需要重试,其他错误直接退出 */
if (errno == EINTR || errno == EAGAIN) {
/* 实际应用中应该在这里放重试逻辑 */
perror("Read interrupted/try again");
} else {
perror("Error reading from stdin");
}
return 1;
}
if (bytes_read == 0) {
/* 文件末尾(EOF) */
printf("End of file reached.\n");
return 0;
}
/* bytes_read > 0: 成功读取数据 */
buffer[bytes_read] = '\0'; /* 添加字符串结束符 */
printf("You entered (%ld bytes): %s", bytes_read, buffer);
return 0;
}
【示例要点】
-
读取的字节数可能小于请求的
count,程序应做好处理 -
对于
EINTR和EAGAIN错误,健壮的程序应当实现重试逻辑 -
使用
perror()可输出包含具体错误信息
九、编程注意事项
-
不要假设能一次读完:由于
read()可能返回小于count的值,建议在循环中调用read()直到读取所需数量的数据或遇到 EOF/错误 -
正确处理 EINTR:对于慢速设备,信号中断是常见情况,应用应该重试
read()调用 -
非阻塞模式的轮询:当使用非阻塞 I/O 时,通常需要配合
select()、poll()或epoll()等多路复用机制 -
缓冲区安全性:确保
buf指向的内存足够容纳count字节,避免缓冲区溢出 -
文件类型限制:
read()不能用于读取目录(应使用readdir),对于某些套接字类型也可能不适用
更多推荐




所有评论(0)