版权信息
warning
本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
文件描述符:内核分发的身份证
在 Linux 系统中,文件描述符(File Descriptor,简称 FD)是一个核心概念,它是一个非负整数,用于唯一标识一个已打开的文件。这个文件可以是普通文件、目录、网络套接字(socket),甚至是设备(如键盘、显示器)。
文件描述符的设计主要有以下几个优点:
-
简化了 I/O 接口:无论是读写本地文件,还是进行网络通信,开发者都可以使用一套统一的系统调用(
read,write,close等),而无需关心底层的具体类型。这种“一切皆文件”的设计哲学是 Linux 简洁和强大的体现。 -
隔离了应用程序与内核:应用程序只知道一个简单的整数,而不需要知道文件在磁盘上的物理位置,也不用关心内核是如何管理文件的。所有复杂的操作都由内核在幕后完成。
-
管理系统资源:内核通过文件描述符表来管理每个进程打开的文件。当一个进程结束时,内核可以根据这个表自动关闭所有打开的文件,避免资源泄露。
文件描述符的几个重要特点:
-
唯一性:在一个进程中,每个打开的文件都有一个独一无二的文件描述符。
-
非负整数:文件描述符的值通常从 3 开始分配,因为 0、1、2 这三个描述符被系统预留给标准输入、标准输出和标准错误。
-
0:标准输入(
stdin),通常是键盘。 -
1:标准输出(
stdout),通常是显示器。 -
2:标准错误(
stderr),也通常是显示器。
-
-
进程私有:文件描述符是在进程内部使用的,不同进程的文件描述符互不影响。例如,进程 A 的文件描述符 3 和进程 B 的文件描述符 3 可能指向完全不同的文件。
最基础的系统调用函数
Linux I/O 编程中常见的系统调用函数,这些函数位于 unistd.h(以及 fcntl.h 等)中,是用户态和内核态交互的基础接口。
1. open
#include <fcntl.h> // open flags
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
作用
打开或创建一个文件,返回一个 文件描述符(fd,整数),后续 I/O 都依赖它。
参数
-
pathname:文件路径。 -
flags:打开方式,常用值:-
O_RDONLY:只读 -
O_WRONLY:只写 -
O_RDWR:读写 -
O_CREAT:若文件不存在则创建,需要配合mode参数 -
O_TRUNC:打开时清空文件 -
O_APPEND:写时追加到文件尾部 -
O_NOBLOCK:非阻塞模式
-
-
mode:创建文件时的权限(如0666,受 umask 影响)。
返回值
-
成功:文件描述符(非负整数)
-
失败:
-1,并设置errno
2. write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
作用
向文件(或设备)写入数据。
参数
-
fd:文件描述符 -
buf:要写入的数据缓冲区指针 -
count:要写入的字节数
返回值
-
成功:实际写入的字节数(可能小于
count) -
失败:
-1
注意
-
对于普通文件,通常会写入全部数据。
-
对于管道、socket 等,可能分多次写入,需要循环写。
3. read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
作用
从文件(或设备)读取数据。
参数
-
fd:文件描述符 -
buf:存放读出数据的缓冲区 -
count:最大读取字节数
返回值
-
成功:实际读取的字节数
0表示到达文件结尾 (EOF)
-
失败:
-1
注意
-
实际读取字节数可能小于
count。 -
对于阻塞 I/O,如果没有数据,可能会挂起等待。
4. lseek
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
作用
移动文件读写位置(文件指针)。
参数
-
fd:文件描述符 -
offset:偏移量(可正可负) -
whence:偏移起始点-
SEEK_SET:从文件开头 -
SEEK_CUR:从当前位置 -
SEEK_END:从文件末尾
-
返回值
-
成功:新的文件偏移位置
-
失败:
-1
注意
-
常用来实现随机读写。
-
对某些设备文件可能不支持(如管道)。
5. close
#include <unistd.h>
int close(int fd);
作用
关闭一个打开的文件描述符,释放内核资源。
参数
fd:文件描述符
返回值
-
成功:
0 -
失败:
-1
常见函数查找表
| 函数 | 头文件 | 功能 | 主要参数 | 返回值 | 常见用途 |
|---|---|---|---|---|---|
open |
<fcntl.h>, <unistd.h> |
打开或创建文件,返回文件描述符 | pathname:路径flags:打开方式(如 O_RDONLY)mode:权限(如 0644,仅在 O_CREAT 时有效) |
成功:文件描述符 (≥0)失败:-1 |
打开文件/设备,获得 fd |
write |
<unistd.h> |
向文件写入数据 | fd:文件描述符buf:数据缓冲区count:要写入字节数 |
成功:实际写入字节数失败:-1 |
向文件、管道、socket 写数据 |
read |
<unistd.h> |
从文件读取数据 | fd:文件描述符buf:存放数据的缓冲区count:最大读取字节数 |
成功:实际读取字节数0:文件结尾失败:-1 |
读取文件、键盘输入、网络数据 |
lseek |
<unistd.h> |
改变文件读写位置(文件指针) | fd:文件描述符offset:偏移量whence:参考位置(SEEK_SET/SEEK_CUR/SEEK_END) |
成功:新的偏移位置失败:-1 |
随机读写文件、获取文件大小 |
close |
<unistd.h> |
关闭文件描述符,释放资源 | fd:文件描述符 |
成功:0失败:-1 |
程序结束或文件不再使用时关闭 |
sync |
<unistd.h> |
将内核缓冲区中所有修改过的数据(脏页)写入磁盘 | 无 | 无返回值 | 全局刷新,影响所有文件,效率较低 |
fsync |
<unistd.h> |
将指定文件的缓存数据强制写入磁盘 | fd:文件描述符 |
成功:0,失败:-1 |
精确到单个文件,常用于数据库、日志写入 |
pipe |
<unistd.h> |
创建匿名管道,用于进程间通信 | int pipefd[2] |
成功:0,失败:-1 | pipefd[0] 读端,pipefd[1] 写端 |
unlink |
<unistd.h> |
删除一个文件(目录用 rmdir) |
pathname:文件路径 |
成功:0,失败:-1 | 实际删除在引用计数归零时发生 |
access |
<unistd.h> |
检查文件是否存在及权限 | pathname, mode |
成功:0,失败:-1 | mode 可取 R_OK, W_OK, X_OK, F_OK |
对比C 标准库IO操作
使用c标准库的IO操作,小量频繁读写的效率更高,因为其内部自带有缓冲区。
这可以理解为在系统IO上有封装了一层,进行文件操作时写入C自带缓冲区,满足一定条件再调用系统IO,将缓冲区的内容写入IO缓存区,再到内核的页缓存区,最后到物理的磁盘。
| 特性 | Linux 系统调用 I/O | C 标准库文件操作 |
|---|---|---|
| 头文件 | <unistd.h>, <fcntl.h> |
<stdio.h> |
| 函数示例 | open, read, write, lseek, close |
fopen, fread, fwrite, fseek, fclose, fprintf, fscanf |
| 返回值 | 直接返回字节数、文件描述符等,或 -1 |
返回 FILE* 指针,或 EOF 等错误码 |
| 数据单位 | 以 字节(byte) 为单位 | 以 缓冲区 / 结构化数据 为单位(有缓冲机制) |
| 缓冲 | 无用户态缓冲,直接在用户空间和内核空间间传递数据 | 带缓冲区(stdio 库内部维护缓存,加速小块读写) |
| 层次 | 操作系统内核提供的 底层接口 | 基于系统调用的 封装库函数 |
| 灵活性 | 可操作普通文件、设备文件、socket、管道 | 主要操作普通文件和标准输入输出 |
| 可移植性 | 偏向 Unix/Linux 系统 | 跨平台(符合 ANSI C 标准,Windows/Linux 通用) |
| 典型用途 | 驱动开发、系统编程、精确控制 I/O | 应用层文件读写、文本处理、快速开发 |